文章目录
- 前言
- 一、原理篇
- 1. 什么是 Android LMK
- 2. OOM
- 3. oom_adj 的值是如何赋予的
- 4. LMK 的工作机制
- 5. Android进程优先级
- 5.1 Android进程的优先级
- 5.2. Android 进程的回收策略
- 5.3 保活的方法
- 二、方法篇
- 三、总结
前言
上文主要介绍了Andorid 内存的管理机制,本文对其中的 LMK 机制进行深入扩展总结。
我们知道出现 Crash 应用闪退和崩溃一般有三个原因:ANR(程序无响应)、Exception(异常)、LMK(低内存杀死机制)。本文重点介绍 LMK 机制。
目的: 通过阅读本文,可以了解 Android LMK 机制,探究进程保活的方案和程序异常处理的方法。
一、原理篇
1. 什么是 Android LMK
KMK,(Low Memory Killer )低内存杀死机制。由于 Android 应用的沙箱机制,每个应用程序都运行在一个独立的进程中,各自拥有独立的 Dalvik 虚拟机实例,系统默认分配给虚拟机的内存是有限度的,当系统内存太低依然会触发 LMK 机制,即出现闪退、崩溃现象。
不同厂商不同,如:华为 mate7,192M ;小米4,128M ;红米,128M 。而在,Android4.0以后,可以通过在application
节点中设置属性android:largeHeap=”true”
来设置最大可分配多少内存空间就可以突破一定限制。
2. OOM
OOM(OutOfMemoryError)内存溢出错误 。导致 OOM 的两个主要原因:
1、内存泄漏,大量无用对象未及时回收,导致后续申请内存失败。
2、BitMap大对象,几个大图同时加载很容易触发 OOM。
为了空出足够的内存供前台进程使用,Android会定时进行CHECK进程树,然后杀死优先级别不高的进程。而进程的优先级别是按照属性 oom_adj 来判断的。oom_adj数值越低,越不会被杀死。
3. oom_adj 的值是如何赋予的
oom_adj
的数字大致为这几种:
FOREGROUND_APP_ADJ | 0 | 前台进程,正在活动的Activity或者使用startForeground的Service |
VISIBLE_APP_ADJ | 1 | 可见进程,不可操作的Activity,但是可见 |
SECONDARY_SERVER_ADJ | 2 | 拥有后台服务器的进程 |
HIDDEN_APP_MIN_ADJ | 7 | Activity没有完全退出,直接采用 moveTaskToBack 到HOME的进程 |
CONTENT_PROVIDER_ADJ | 14 | 内容提供进程 |
EMPTY_APP_ADJ | 15 | 空程序,既不提供服务,也不提供内容 |
CORE_SERVER_ADJ | -12 | 系统进程 |
SYSTEM_ADJ | -16 | 系统核心服务(进程永远不会被杀掉) |
当系统的内存不足的时候,那么就会杀死发送KILL SIGNAL
, 杀死一些优先级别低的进程,用来提供足够的内存给前台进程使用。
用命令可以查看进程的优先级的值:
adb shell dumpsys activity|grep oom_adj
查看进程命令:
adb shell dumpsys activity processes
# 获取当前正在运行的服务列表及其进程 ID
(进程 ID 可能会随着时间和系统状态的变化而变化,因此获取的进程 ID 可能不是永久有效的。)
adb shell dumpsys activity services
#根据你感兴趣的服务,在输出中找到对应的进程 ID(PID)。运行以下命令以查看该进程的 OOM_ADJ 值
adb shell cat /proc/<PID>/oom_score_adj
注意:
- Low memory killer 是定时进行检查。
- Low memory killer 主要是通过进程的 oom_adj 来判定进程的重要程度。这个值越小,程序越重要,被杀的可能性越低。
- oom_adj 的大小和进程的类型以及进程被调度的次序有关。
4. LMK 的工作机制
LMK 开始工作时,首先根据阈值表确定当前的警戒级数,则高于警戒级数的进程是待杀的范围。
然后遍历所有进程的 oom_adj 值,找到大于 min_adj 的进程,若找到多个,则把占用进程最大的进程存放在 selected 中。最关键的一步就是,发送 SIGKILL 信息,杀掉该进程。
5. Android进程优先级
5.1 Android进程的优先级
一般情况下,Android 会尽可能的保持应用进程,但在特定的场景会对进程进行Kill,例如为了清除旧进程来回收内存等。为了区分哪些进程最先被回收清理,而哪些不会,有一个优先级别,这就是 Android 的进程优先级,具体包括以下 5 种(优先级从高到低)。
- Foreground/Activate process 前台进程。用户当前操作的进程,包括用户正在交互的 Activity,绑定用户正在交互 Activity 的Service,使用 startForeground 的 Service,正在执行 onReceive 的 BroadcastReceiver 等。
- Visible process 可见进程。会影响用户所见内容的进程,如 onPause 状态的 Activity 等。
- Service process 服务进程。后台服务,如正在运行 startService 启动的 Service。
- Background process 后台进程。对用户交互无影响,如 onStop 状态的 Activity 等。
- Empty process 空进程。一般用作缓存以缩短下次启动时间,系统往往会终止这些空进程。
5.2. Android 进程的回收策略
Android 主要通过LMK(Low Memory Killer)来对进程进行回收管理,LMK 是在 Android 系统内存不足而选择 kill 部分进程释放空间,生死大权的决定者,其基于 Linux 的 OOM 机制,LMK 通过 oom_ad j与占用内存的大小决定要杀死的进程,oom_adj 值越小,越不容易被杀死。上面的几种进程形态对于的不同的 oom_adj 值。前台进程的优先级为 0,普通 service 的进程优先级是8。
5.3 保活的方法
一方面提高进程优先级,降低被系统 kill 的概率。另一方面,在 App 被杀死以后进行拉活。知道了原理我们就可以采用一定的策略来提高应用的存活几率。
二、方法篇
进程保活说白了就是保证自己 App 进程不死,App被杀死有以下几种可能:
- 被系统杀死
- 被用户杀死
- 被竞争对手杀死
了解 LMK 机制后,就可以对系统杀死的情况做相对应的优化。
- 通过在
androidmanifest.xml
中的application标签中加入android:persistent="true"
属性后的确就能够达到保证该应用程序所在进程不会被LMK杀死。
但有个前提就是应用程序必须是系统应用,也就是说应用程序不能采用通常的安装方式。必须将应用程序的apk包直接放到 /system/app 目录下。而且必须重启系统后才能生效。
应用场景:定制TV端、平板端和手机端预装系统软件。
关键词:加入白名单、成为系统软件。
有些手机厂商把这些知名的 app 放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、百度全家桶等在小米的白名单中)。如果从白名单中移除,他们终究还是和普通 app 一样躲避不了被杀的命运。
- 在程序中考虑异常处理,如恢复数据状态
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// 保存用户自定义的状态
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// 调用父类交给系统处理,这样系统能保存视图层次结构状态
super.onSaveInstanceState(savedInstanceState);
}
说明:Activity 被系统回收了,在回收之前保存当前状态。
重写onSaveInstanceState()
方法,在此方法中保存需要保存的数据,该方法会在 Activity 被回收前调用。
通过重写onRetoreInstanceState()
方法可以从中提取保存好的数据。
- 程序退出时 做类似
Home
处理。将程序退到后台而不是kill程序。
在根 Activity 中重写后退按钮响应事件,当按后退按钮的时候把 Activity 退置到后台
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
moveTaskToBack(true);
return true;
}
return super.onKeyUp(keyCode, event);
}
其中的moveTaskToBack
/**
* Move the task containing this activity to the back of the activity
* stack. The activity's order within the task is unchanged.
*
* @param nonRoot If false then this only works if the activity is the root
* of a task; if true it will work for any activity in
* a task.
*
* @return If the task was moved (or it was already at the
* back) true is returned, else false.
*/
public boolean moveTaskToBack(boolean nonRoot) {
try {
return ActivityManager.getService().moveActivityTaskToBack(
mToken, nonRoot);
} catch (RemoteException e) {
// Empty
}
return false;
}
说明:当 nonRoot 为 false 时,当前 activity 必须为栈底,也就是最底层的 activity,如果其他 activity 没有及时 finish 掉,就会出现异常,导致崩溃等情况的发生;nonRoot 为 true 时,不需要考虑当前 activity 是否在栈底。
- 程序性能优化,回归问题的本质。
系统统内存不足的时候肯定优先杀死这些占用内存高的进程来腾出资源。所以,为了尽量避免后台 UI 进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的。对此,我们要想程序不被杀死,必须当前程序的内存占空间有比其他程序小才有竞争力不被优化 kill 掉。
最后在进一步总结 LMK 的机制和实际运用的意义时,看看这个博主是如何总结的:
关于了解LMK之后的内存管理建议:
- 我个人是长期在手机厂商从事 APP 和 framework 开发的。从外部来看,如果一个应用的内存总是不够用而又经常被回收,那么是可以从各个层面来缓解这种情况的——设置应用进程的 oom_adj 为更低(这种修改不仅可在底层也可在 app 层进行),修改各等级阀值等等,这是在不涉及应用本身修改的情况下,能做出的有限修改。可惜对于日常的应用开发者来说,这绝对不是康庄大道。
- 对于一般的应用开发者而言,安卓设备的底层已经封闭,甚至 ROOT 权限也无法获得,他们能做的更多是从应用自身进行优化,来使得应用更不容易被杀死回收。要做到这一点,就要深刻理解 LMK 的权重系统。比如运行那些需要后台运行的任务,用service 而不要用后台进程;又比如如果你在开发输入法等应用,你自然能通过某些设置使得它变成高优先级的可视进程而不是后台进程。
- 当然,既然有所谓阀值,最治本的方法当然是减少进程的内存占用。作为普通应用开发者,你不能默认自己的应用能始终在前台,更不可能无限制地重启自己的进程(最好别这么干),提高应用各层级的阀值,锲而不舍地减少应用各个部分所占用的内存,并更顺畅地帮助内存的释放(是的。。。JAVA),才是开发内存占用小,运行稳定顺畅的好应用的优秀开发者素质。
三、总结
了解 Android LMK 机制后,有助于在开发程序时 对系统的内存回收机制采取一定的措施来提高程序的体验性,同时最核心的问题还是在程序的开发内功上做指导意义,毕竟,如何管理好内存,是我们一直需要探讨和实践的问题。未完待续……
参考资料:
1.onSaveInstanceState()和onRestoreInstanceState()使用详解
2.Android之进程回收机制LMK(Low Memory Killer)
3.Android APP开发的内存管理与优化之一 ——LowMemory Killer