Android 开发,跳不过的内存管理
版权声明:
本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。
未经允许,不得转载。
一、前言
在 Android 系统中,当运行的 App 被移动到后台的之后,Android 为了保证下次启动的速度,会将它移入 Cached 的状态,这个时候实际上,该 App 的进程依然存在,但是对应的组件是否存在,就不一定了。而既然该 App 的进程还存活着,下次启动的速度就会很快,这就是常说的热启动。
但是这些被退出到后台的 App ,也并不是完全安全不会被清理掉的,他们可能只是没有持有任何的组件,并且是不占用 CPU 资源的,但是它依然会占据内存空间。而当系统认为内存不足的时候,就会按照优先级清理掉一些优先级不那么高的进程,来回收一些内存空间,供新启动的程序使用,这就是 LowMemoryKiller 的策略。
那么,为了让我们的 App 在后台尽可能活的久一点,无非就是将内存降低,从而使得优先级提高,而实现不被系统回收的功能(这是一个常规的优化方案,而非保活方案)。那么,如果我们能明确当前 App 处于什么状态,就能在此时机,释放一些不需要持有的内存资源,来达到我们的目的。
这个时候,就需要用到 onTrimMemory()
这个回调方法了。
二、什么是 onTrimMemory
1、onTrimMemory 的作用
前面提到,我们可以通过实现 onTrimMemory()
方法,来完成对当前 App 在内存中的优先级的简单管理。
而 onTrimMemory()
回调方法,是 Android Level 14(Android 4.0) 之后提供的一个 API,它主要的作用是提醒开发者,在系统内存不足的时候,应该通过释放部分不重要的内存资源,从而避免被 Android 系统服务杀掉。
可以看到,onTrimMemory()
本质上是一种告知 App 处于系统内存回收的不同阶段的时机,应该在这些时机,合理对自身持有的内存进行释放,以避免被系统直接杀掉,从而让保证下次用户启动 App 时候的速度。
onTrimMemory()
的完整方法签名如下:
public void onTrimMemory(int level)
可以看到,它实际上,会有一个 level 参数来标记当前的 App 在内存中的级别,也就意味着 onTrimMemory()
方法,可能会被多次调用到。
2、 onTrimMemory 回传的参数的意义
既然 onTrimMemory()
方法会传递一个 level 参数,那么就先来看看,各种 level 参数所代表的含义。
- TRIM_MEMORY_UI_HIDDEN:App 的所有 UI 界面被隐藏,最常见的就是 App 被 home 键或者 back 键,置换到后台了。
- TRIM_MEMORY_RUNNING_MODERATE:表示 App 正常运行,并且不会被杀掉,但是目前手机内存已经有点低了,系统可能会根据 LRU List 来开始杀进程。
- TRIM_MEMORY_RUNNING_LOW:表示 App正常运行,并且不会被杀掉。但是目前手机内存已经非常低了。
- TRIM_MEMORY_RUNNING_CRITICAL:表示 App 正在正常运行,但是系统已经开始根据 LRU List 的缓存规则杀掉了一部分缓存的进程。这个时候应该尽可能的释放掉不需要的内存资源,否者系统可能会继续杀掉其他缓存中的进程。
- TRIM_MEMORY_BACKGROUND:表示 App 退出到后台,并且已经处于 LRU List 比较靠后的位置,暂时前面还有一些其他的 App 进程,暂时不用担心被杀掉。
- TRIM_MENORY_MODERATE:表示 App 退出到后台,并且已经处于 LRU List 中间的位置,如果手机内存仍然不够的话,还是有被杀掉的风险的。
- TRIM_MEMORY_COMPLETE:表示 App 退出到后台,并且已经处于 LRU List 比较考靠前的位置,并且手机内存已经极低,随时都有可能被系统杀掉。
其实从 level 值的取名来看,大致可以分为三类:
- UI 置于后台:TRIM_MEMORY_UI_HIDDEN 。
- App 正在前台运行时候的状态:TRIM_MEMORY_RUNNING_Xxx
- App 被置于后台,在 Cached 状态下的回调:TRIM_MEMORY_Xxx
这三类中,通常我们只需要关心 App 被置于 Cached 状态下的情况,因为系统是不会杀掉一个正在前台运行的 App 的(但可能会触发 OOM),但是如果该 App 有一些后台服务正在运行,这个服务也是有被杀的风险的。
而在 Cached 状态下的时候,当收到 TRIM_MEMORY_Xxx 的回调,就需要注意了,这些只是标记了当前 App 处于 LRU List 的位置,也就是说,如果回收了靠前的 App 进程之后,依然达不到内存使用的要求,可能会进一步去杀进程,也就是说,极端情况下,可能从 TRIM_MEMORY_BACKGROUND 到 TRIM_MEMORY_COMPLETE 是瞬间完成的事情,所以我们需要慎重处理它们,尽量对这三个状态都进行判断,然后做统一的回收内存资源的处理。
3、哪些组件可以监听 onTrimMemory
既然说到了 onTrimMemory()
回掉,看样子它是和 App 相关的,所以最少在 Application 中,应该是可以对其进行重写来监听回调的。但是除了 Application,其他的一些组件中,也是可以监听它的。
这些可以监听 onTrimMemory 的组件有:
- Application
- Activity
- Fragment
- Service
- ContentProvider
4、自定义 onTrimMemroy 监听
除了前面提到的系统默认可以监听 onTrimMemory()
的组件之外,我们还可以自定义 onTrimMemory 的监听。
自定义起来也非常的简单,只需要实现 ComponentCallbacks2 接口,然后调用 Application.registerComponentCallbacks()
方法注册即可。
除了 registerComponentCallbacks()
方法进行注册监听之外,如果不使用了的话,还可以使用 unregisterComponentCallbacks()
进行解注。
那么这里是如何实现的呢?让我们来看看 Application 的对应源码。
可以看到,它实际上是通过一个 mComponentCallbacks 的列表进行维护的。
而在 onTrimMemory()
的时候,又从 mComponentCallbacks 中获取到所有的 callbacks 对象,进行消息的分发。
通过这种方式实现了对 onTrimMemory()
的自定义监听。
而 onTrimMemory()
方法同时被标记为 @CallSuper,也就严格要求了重写它的子类,必须调用父类中的 onTrimMemory()
方法,从而保证了消息的分发不会缺失。
5、onLowMemory()
onTrimMemory()
既然是 Android 4.0 才新增加的 Api,那么对于低版本的设备而言,可以监听 onLowMemory()
方法,它大概可以等同于 level 级别为 TRIM_MEMORY_COMPLETE 的回调。
当然,ComponentCallbacks2 接口继承的 ComponentCallback 接口,也是需要实现 onLowMemory()
方法的。
三、onTrimMemory 的一些思考?
1、为什么需要 onTrimMemory()
Android 系统会在自身内存不足的情况下,清理掉一些不重要的进程来释放内存资源,以供优先级更高的进程使用。而这个顺序,主要是按照 LRU List 中的优先级来清理的,但是它也同时会考虑清理掉哪些占用内存较高的进程来让系统更快的释放跟多的内存。
所以,尽可能的让 App 在系统内,占用足够小的内存资源,就可以降低被杀的概率,从而下次启动的时候走热启动的方式,提升用户的体验。
换一个角度来说,让 App 占用较小的内存,也可以优化系统的速度,毕竟系统清理进程释放内存的过程,也是需要占用 CPU 资源的。在大环境下,也是有意义的。
所以,在 onTrimMemory()
的时机,对当前 App 的内存进行释放优化,就尤为重要了。
2、在 onTrimMemory 回调中,应该释放哪些资源
在 onTrimMemory()
回调中,应该在一些状态下清理掉不重要的内存资源。在不考虑内存泄露的情况下,有一些资源是我们主动缓存起来,以便我们在使用的过程中可以快速获取,而这部分资源就是我们清理的重点。
对于这些缓存,只要是读进内存内的都算,例如最常见的图片缓存、文件缓存等。拿图片缓存来说,市场上,常规的图片加载库,一般而言都是三级缓存,所以在内存吃紧的时候,我们就应该优先清理掉这部分图片缓存,毕竟图片是吃内存大户,而且再次回来的时候,虽然内存中的资源被回收掉了,我们依然可以从磁盘或者网络上恢复它。
除了资源缓存之外,还有一些页面相关的资源,也是占据内存的,可以考虑清理掉 Activity Task 中,多余的 Activity,只保留 Root Activity 。
其实核心思想,就是根据 onTrimMemory()
回调的一些信息,来释放我们持有的可被恢复,不那么重要的内存资源,以提高系统性能,已经保证当前 App 的进程不那么容易被系统回收。