理解onTrimMemory(int level)

时间:2013-10-16 08:44:57

标签: android memory-management android-fragments android-activity

我最近在 Managing Your App's Memory 上阅读了这篇文章,我强烈建议您阅读它,如果您是AndroidDev并且从未这样做过。

有许多好的做法,我从未碰巧知道的一件事是系统在每个Activity / Fragment上调用的onTrimMemory(int level)方法,用于通知应该或可以释放哪些内存的事件。

以下是该文章的引用:

  

请注意,您的应用会收到onTrimMemory()回调   TRIM_MEMORY_UI_HIDDEN仅适用于您应用的所有UI组件   进程对用户隐藏。这不同于   onStop()回调,在Activity实例变为时调用   隐藏,即使用户移动到另一个活动时也会发生   你的应用。所以虽然你应该实现onStop()来释放   活动资​​源,例如网络连接或取消注册   广播接收器通常不应该释放您的UI资源   直到您收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)。这确保了   如果用户从您应用中的其他活动导航回来,   您的UI资源仍可用于快速恢复活动。

我真的很有兴趣在我的应用程序中实现良好的内存管理,所以我期待以正确的方式实现onTrimMemory()

我只有几个问题:

  • 在onStop()之后调用onTrimMemory(TRIM_MEMORY_UI_HIDDEN)吗?

  • “释放您的UI资源”意味着什么?只是为了清理Bitmap缓存,或者实际删除并销毁View树中的每个View?我通常会破坏 onDestroy() onDestroyView()方法中的视图,我现在想知道我是否正确行事。

  • 是否有{/ 3}}的双人/通讯回叫?例如 onCreate-onDestroy onStart-onStop onCreateView-onDestroyView 。我要求了解在调用onTrimMemory(TRIM_MEMORY_UI_HIDDEN)后调用活动/片段之后我应该在何处以及如何恢复UI状态。

3 个答案:

答案 0 :(得分:29)

    具有TRIM_MEMORY_UI_HIDDEN级别的
  • onTrimMemory实际上是在onStop之前调用的。当onStop被调用时,它意味着活动真的停止了,如果需要,Android操作系统可能会立即将其杀掉,所以除了onRestart和有时onDestroy之外,你不应该期待在此之后再调用该活动的回调。 / p>

  • “释放您的UI资源”实际上是关于缓存之类的东西。您通常不必担心管理视图或UI组件,因为操作系统已经这样做了,这就是为什么存在创建,启动,暂停,停止和销毁活动的所有回调的原因。但是,有时为了提高性能,您必须增加内存使用量,例如缓存活动使用的一些数据。这是调用onTrimMemory时应该释放的资源类型,因此您的应用程序使用的内存较少,即使它会影响性能。你应该担心内存泄漏。如果您的活动停止,请确保不要保留对其视图的任何引用,因为这样可以防止活动被垃圾收集,从而无法收集整个上下文,这很糟糕,主要是因为您希望保持应用程序正常运行几小时或几天(比如实施服务时)。

  • 不,onTrimMemory没有相应的回调。但是,你不应该需要一个。正如我之前所说,如果你保留一些资源的缓存来提高性能,那就把它清空,如果需要,让它再次增长。如果内存级别保持低,则可以很快再次调用onTrimMemory,具有相同的内存级别。顺便说一下,请记住,将使用几个不同的内存级别调用onTrimMemory,而不仅仅是TRIM_MEMORY_UI_HIDDEN。

答案 1 :(得分:15)

示例实施

public class AppContext extends Application {
//This my introduce OutOfMemoryException if you don't handle register and removal quiet well, better to replace it with weak reference   
private static List<IMemoryInfo> memInfoList = new ArrayList<AppContext.IMemoryInfo>();

public static abstract interface IMemoryInfo {
        public void goodTimeToReleaseMemory();
    }

@Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
//don't compare with == as intermediate stages also can be reported, always better to check >= or <=
            if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
                try {
                // Activity at the front will get earliest than activity at the
                // back
                for (int i = memInfoList.size() - 1; i >= 0; i--) {
                    try {
                        memInfoList.get(i).goodTimeToReleaseMemory();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

/**
     * 
     * @param implementor
     *            interested listening in memory events
     */
    public static void registerMemoryListener(IMemoryInfo implementor) {
        memInfoList.add(implementor);
    }

    public static void unregisterMemoryListener(IMemoryInfo implementor) {
        memInfoList.remove(implementor);
    }
}

public class ActivityParent extends Activity implements AppContext.IMemoryInfo {

    protected ActivityParent child;


@Override
    protected void onStop() {
        super.onStop();
        try {
            if (child != null)
                AppContext.unregisterMemoryListener(child);
        } catch (Exception e) {

        }
    }
}

public class ActivityChild extends ActivityParent {
@Override
    protected void onCreate(Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);
        child = this;
    }

        /---move following onResume() in parent as following eg:
/*
*@Override
*       protected void onResume() {     
*           super.onResume();
*           if(null != child){
*           AppContext.registerMemoryListener(this);
*           }
*       }
*/
        @Override
        protected void onResume() {     
            super.onResume();
            AppContext.registerMemoryListener(this);
        }

@Override
public void goodTimeToReleaseMemory() { 
    super.goodTimeToReleaseMemory();
//remove your Cache etc here
}
//--NO Need because parent implementation will be called first, just for the sake of clarity 
@Override
    protected void onStop() {
        super.onStop();
        try {
            if (null != child)
                AppContext.unregisterMemoryListener(child);
        } catch (Exception e) {

        }
    }

更多信息:

当您的应用运行时: TRIM_MEMORY_RUNNING_MODERATE 设备开始内存不足。您的应用正在运行且无法播放。

TRIM_MEMORY_RUNNING_LOW 设备的内存运行速度要低得多。您的应用正在运行且无法播放,但请释放未使用的资源以提高系统性能(这会直接影响应用的性能)。

TRIM_MEMORY_RUNNING_CRITICAL 设备内存运行极低。您的应用程序尚未被视为可填充进程,但如果应用程序未释放资源,系统将开始终止后台进程,因此您应立即释放非关键资源以防止性能下降。

当您的应用可见性发生变化时: TRIM_MEMORY_UI_HIDDEN 您的应用的用户界面不再可见,因此现在是发布仅由您的用户界面使用的大量资源的好时机。

当您的应用进程位于后台LRU列表中时: TRIM_MEMORY_BACKGROUND 系统内存不足,您的进程接近LRU列表的开头。虽然您的应用程序进程没有被杀死的高风险,但系统可能已经在LRU列表中查杀进程,因此您应该释放易于恢复的资源,这样您的进程将保留在列表中并恢复当用户返回您的应用时,请快速。

TRIM_MEMORY_MODERATE 系统内存不足,您的进程接近LRU列表的中间位置。如果系统进一步受限于内存,那么您的进程可能会被杀死。

TRIM_MEMORY_COMPLETE 系统内存不足,如果系统现在没有恢复内存,那么您的进程是最先杀死的进程之一。你应该绝对释放对恢复你的app状态不重要的一切。 要支持低于14的API级别,您可以使用onLowMemory()方法作为大致相当于TRIM_MEMORY_COMPLETE级别的后备。

http://developer.android.com/reference/android/content/ComponentCallbacks2.html

答案 2 :(得分:1)

我正在强制关闭显示器时从未调用onTrimMemory()的问题。 因此,我尝试使用ActivityLifecycleCallbacks解决方法: 我用了一个简单的计数器:

onActivityStarted(){
    i++;
}

onActivityStopped(){
    i--;
    if(i==0) // no more open activities, thus screen probably turned off
}

它对我有用,但我不确定它是否安全。 RFC

更新:启动相机意图,应用程序也关闭,因此它的行为并不完全正常。

使用此代码。工作得很好:

private void registerBroadcastReceiver() {
    final IntentFilter theFilter = new IntentFilter();
    theFilter.addAction(Intent.ACTION_SCREEN_OFF);

    BroadcastReceiver screenOnOffReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String strAction = intent.getAction();
            if (strAction.equals(Intent.ACTION_SCREEN_OFF)) {
                // do sth
            }
        }
    };
    getApplicationContext()
            .registerReceiver(screenOnOffReceiver, theFilter);
}