66

我最近读了这篇关于管理你的应用程序内存的文章,如果你是一个 AndroidDev 并且从未读过,我强烈建议你阅读它。

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

这是那篇文章的引述:

请注意,只有当您的应用进程的所有 UI 组件对用户隐藏时,您的应用才会收到带有 TRIM_MEMORY_UI_HIDDEN 的 onTrimMemory() 回调。这与 onStop() 回调不同,后者在 Activity 实例隐藏时调用,即使用户移动到应用程序中的另一个 Activity 也会发生这种情况。因此,尽管您应该实现 onStop() 来释放活动资源(例如网络连接或取消注册广播接收器),但通常在收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN) 之前不应释放 UI 资源。这确保了如果用户从您应用中的另一个活动导航回来,您的 UI 资源仍然可用于快速恢复活动。

我对在我的应用程序中实现良好的内存管理非常感兴趣,因此我期待以正确的方式实现onTrimMemory()

我对此只有几个问题:

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

  • 在这种情况下,“释放你的 UI 资源”是什么意思?例如清理位图缓存,或者实际删除和销毁视图树中的每个视图?我通常在onDestroy()onDestroyView()方法中销毁视图,我现在想知道我是否做得对。

  • 是否有对onTrimMemory(TRIM_MEMORY_UI_HIDDEN)的 Twin/correspondent 回调?比如onCreate-onDestroyonStart-onStoponCreateView-onDestroyView。我要求了解在调用onTrimMemory(TRIM_MEMORY_UI_HIDDEN)后将 Activity/Fragment 置于前台后我应该在何处以及如何恢复 UI 状态。

4

3 回答 3

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

  • “释放你的 UI 资源”实际上是关于缓存之类的。您通常不必担心管理视图或 UI 组件,因为操作系统已经这样做了,这就是为什么有所有用于创建、启动、暂停、停止和销毁活动的回调的原因。但是,有时为了提高性能,您必须增加内存使用量,例如缓存活动使用的一些数据。这是调用 onTrimMemory 时应该释放的资源类型,因此您的应用使用更少的内存,即使它会影响性能。不过,您应该担心内存泄漏。如果您的活动停止,请确保不要保留对其视图的任何引用,因为这会阻止活动被垃圾收集,这会阻止整个上下文被收集,这很糟糕,

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

于 2013-11-01T03:53:49.310 回答
24

示例实现

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 您的应用的 UI 不再可见,因此这是释放仅由您的 UI 使用的大型资源的好时机。

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

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

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

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

于 2015-01-29T08:43:27.217 回答
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);
}
于 2016-02-11T10:54:41.837 回答