Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)

Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)
本篇是Android后台杀死系列的第三篇,前⾯两篇已经对,,本篇主要讲解的是Android后台杀死原理。相对于后台杀死恢
复,LowMemoryKiller原理相对简单,并且在⽹上还是能到不少资料的,不过,由于Android不同版本在框架层的实现有⼀些不同,⽹上的分析也多是针对⼀个Android版本,本⽂简单做了以下区分对⽐。LowMemoryKiller(低内存杀⼿)是Andorid基于oomKiller原理所扩展的⼀个多层次oomKiller,OOMkiller(Out Of Memory Killer)是在Linux系统⽆法分配新内存的时候,选择性杀掉进程,到oom的时候,系统可能已经不太稳定,⽽LowMemoryKiller是⼀种根据内存阈值级别触发的内存回收的机制,在系统可⽤内存较低时,就会选择性杀死进程的策略,相对OOMKiller,更加灵活。在详细分析其原理与运⾏机制之前,不妨⾃⼰想⼀下,假设让你设计⼀个LowMemoryKiller,你会如何做,这样⼀个系统需要什么功能模块呢?
进程优先级定义:只有有了优先级,才能决定先杀谁,后杀谁
进程优先级的动态管理:⼀个进程的优先级不应该是固定不变的,需要根据其变动⽽动态变化,⽐如前台进程切换到后台优先级肯定要降低
进程杀死的时机,什么时候需要挑⼀个,或者挑多个进程杀死
如何杀死
以上⼏个问题便是⼀个MemoryKiller模块需要的基本功能,Android底层采⽤的是Linux内核,其进程管理都是基于Linux内
核,LowMemoryKiller也相应的放在内核模块,这也意味着⽤户空间对于后台杀死不可见,就像AMS完全不知道⼀个APP是否被后台杀死,只有在AMS唤醒APP的时候,才知道APP是否被LowMemoryKiller杀死过。其实LowmemoryKiller的原理是很清晰的,先看⼀下整体流程图,再逐步分析:
先记住两点 :
1. LowMemoryKiller是被动杀死进程
2. Android应⽤通过AMS,利⽤proc⽂件系统更新进程信息
Android应⽤进程优先级及oomAdj
Android会尽可能长时间地保持应⽤存活,但为了新建或运⾏更重要的进程,可能需要移除旧进程来回收内存,在选择要Kill的进程的时候,系统会根据进程的运⾏状态作出评估,权衡进程的“重要性“,其权衡的依据主要是四⼤组件。如果需要缩减内存,系统会⾸先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。在Android中,应⽤进程划分5级():Android中APP的重要性层次⼀共5级:
前台进程(Foreground process)
可见进程(Visible process)
服务进程(Service process)
后台进程(Background process)
空进程(Empty process)
前台进程
⽤户当前操作所必需的进程。如果⼀个进程满⾜以下任⼀条件,即视为前台进程:
包含正在交互的Activity(resumed
包含绑定到正在交互的Activity的Service
包含正在“前台”运⾏的Service(服务已调⽤startForeground())
包含正执⾏⼀个⽣命周期回调的Service(onCreate()、onStart() 或 onDestroy())
包含⼀个正执⾏其onReceive()⽅法的BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不⾜以⽀持它们同时继续运⾏这⼀万不得已的情况下,系统才会终⽌它们。 此时,设备往往已达到内存分页状态,因此需要终⽌⼀些前台进程来确保⽤户界⾯正常响应。
可见进程
没有任何前台组件、但仍会影响⽤户在屏幕上所见内容的进程。 如果⼀个进程满⾜以下任⼀条件,即视为可见进程:
包含不在前台、但仍对⽤户可见的 Activity(已调⽤其 onPause() ⽅法)。例如,如果前台 Activity 启动了⼀个对话框,允许在其后显⽰上⼀Activity,则有可能会发⽣这种情况。
包含绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除⾮为了维持所有前台进程同时运⾏⽽必须终⽌,否则系统不会终⽌这些进程。
服务进程
正在运⾏已使⽤ startService() ⽅法启动的服务且不属于上述两个更⾼类别进程的进程。尽管服务进程与⽤户所见内容没有直接关联,但是它们通常在执⾏⼀些⽤户关⼼的操作(例如,在后台播放⾳乐或从⽹络下载数据)。因此,除⾮内存不⾜以维持所有前台进程和可见进程同时运⾏,否则系统会让服务进程保持运⾏状态。
后台进程
包含⽬前对⽤户不可见的 Activity 的进程(已调⽤ Activity 的 onStop() ⽅法)。这些进程对⽤户体验没有直接影响,系统可能随时终⽌它们,以回收内存供前台进程、可见进程或服务进程使⽤。 通常会有很多后台进程在运⾏,因此它们会保存在 LRU (最近最少使⽤)列表中,以确保包含⽤户最近查看的 Activity 的进程最后⼀个被终⽌。如果某个 Activity 正确实现了⽣命周期⽅法,并保存了其当前状态,则终⽌其进程不会对⽤户体验产⽣明显影响,因为当⽤户导航回该 Activity 时,Activity会恢复其所有可见状态。 有关保存和恢复状态、或者异常杀死恢复可以参考前两篇 ⽂章。
空进程
不含任何活动应⽤组件的进程。保留这种进程的的唯⼀⽬的是⽤作缓存,以缩短下次在其中运⾏组件所需的启动时间,这就是所谓热启动气体收集
。为了使系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终⽌这些进程。
根据进程中当前活动组件的重要程度,Android会将进程评定为它可能达到的最⾼级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,⽽不是服务进程。此外,⼀个进程的级别可能会因其他进程对它的依赖⽽有所提⾼,即服务于另⼀进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为⾄少与进程B同样重要。
通过Google⽂档,对不同进程的重要程度有了⼀个直观的认识,下⾯看⼀下量化到内存是什么样的呈现形式,这⾥针对不同的重要程度,做了进⼀步的细分,定义了重要级别ADJ,并将优先级存储到内核空间的进程结构体中去,供LowmemoryKiller参考:
ADJ优先级优先级对应场景
UNKNOWN_ADJ16⼀般指将要会缓存进程,⽆法获取确定值CACHED_APP_MAX_ADJ15不可见进程的adj最⼤值(不可见进程可能在任何时候被杀死)
CACHED_APP_MIN_ADJ9不可见进程的adj最⼩值(不可见进程可能在任何时候被杀死)SERVICE_B_AD8  B List中的Service(较⽼的、使⽤可能性更⼩)PREVIOUS_APP_ADJ7上⼀个App的进程(⽐如APP_A跳转APP_B,APP_A不可见的时候,A就是属于PREVIOUS_APP_ADJ) HOME_APP_ADJ6Home进程
SERVICE_ADJ5服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ4后台的重量级进程,system/⽂件中设置BACKUP_APP_ADJ3备份进程(这个不太了解)
PERCEPTIBLE_APP_ADJ2可感知进程,⽐如后台⾳乐播放
VISIBLE_APP_ADJ1可见进程(可见,但是没能获取焦点,⽐如新进程仅有⼀个悬浮Activity,Visible process) FOREGROUND_APP_ADJ0前台进程(正在展⽰是APP,存在交互界⾯,Foreground process)
PERSISTENT_SERVICE_ADJ-11关联着系统或persistent进程
PERSISTENT_PROC_ADJ-12系统persistent进程,⽐如telephony
SYSTEM_ADJ-16系统进程
NATIVE_ADJ-17native进程(不被系统管理)
以上介绍的⽬的只有⼀点:Android的应⽤进程是有优先级的,它的优先级跟当前是否存在展⽰界⾯,以及是否能被⽤户感知有关,越是被⽤户感知的的应⽤优先级越⾼(系统进程不考虑)。
Android应⽤的优先级是如何更新的
APP中很多操作都可能会影响进程列表的优先级,⽐如退到后台、移到前台等,都会潜在的影响进程的优先级,我们知道Lowmemorykiller是通过遍历内核的进程结构体队列,选择优先级低的杀死,那么APP操作是如何写⼊到内核空间的呢?Linxu有⽤户间跟内核空间的区分,⽆论是APP还是系统服务,
都是运⾏在⽤户空间,严格说⽤户控件的操作是⽆法直接影响内核空间的,更不⽤说更改进程的优先级。其实这⾥是通过了Linux中的⼀个proc⽂件体统,proc⽂件系统可以简单的看多是内核空间映射成⽤户可以操作的⽂件系统,当然不是所有进程都有权利操作,通过proc⽂件系统,⽤户空间的进程就能够修改内核空间的数据,⽐如修改进程的优先级,在Android家族,5.0之前的系统是AMS进程直接修改的,5.0之后,是修改优先级的操作被封装成了⼀个独⽴的服务-lmkd,lmkd服务位于⽤户空间,其作⽤层次同AMS、WMS类似,就是⼀个普通的系统服务。我们先看⼀下5.0之前的代码,这⾥仍然⽤4.3的源码看⼀下,模拟⼀个场景,APP只有⼀个Activity,我们主动finish掉这个Activity,APP就回到了后台,这⾥要记住,虽然没有可⽤的Activity,但是APP本⾝是没哟死掉的,这就是所谓的热启动,先看下⼤体的流程:
现在直接去AMS看源码:
ActivityManagerService
public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
...
synchronized(this) {
final long origId = Binder.clearCallingIdentity();
boolean res = questFinishActivityLocked(token, resultCode,
resultData, "app-request", true);
...四甲基环丁烷
}
}
⼀开始的流程跟startActivity类似,⾸先是先暂停当前resume的Activity,其实也就是⾃⼰,
final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode,
Intent resultData, String reason, boolean immediate, boolean oomAdj) {
...
if (mPausingActivity == null) {
if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
startPausingLocked(false, false);
}
...
}
pause掉当前Activity之后,还需要唤醒上⼀个Activity,如果当前APP的Activity栈⾥应经空了,就回退
到上⼀个应⽤或者桌⾯程序,唤醒流程就不在讲解了,因为在AMS恢复异常杀死APP的那篇已经说过,这⾥要说的是唤醒之后对这个即将退回后台的APP的操作,这⾥注意与startActivity不同的地⽅,看下⾯代码:
ActivityStack
private final void completePauseLocked() {
ActivityRecord prev = mPausingActivity;
升华仪if (prev != null) {
if (prev.finishing) {
1、不同点
发泡工艺
<!--主动finish的时候,⾛的是这个分⽀,状态变换的细节请⾃⼰查询代码-->
prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
}
...
2、相同点
if (!mService.isSleeping()) {
resumeTopActivityLocked(prev);
}opnet
看⼀下上⾯的两个关键点1跟2,1是同startActivity的completePauseLocked不同的地⽅,主动finish的prev.finishing是为true的,因此会执⾏finishCurrentActivityLocked分⽀,将当前pause的Activity加到mStoppingActivities队列中去,并且唤醒下⼀个需要⾛到到前台的Activity,唤醒后,会继续执⾏stop:
private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
int index, int mode, boolean oomAdj) {
if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
if (!ains(r)) {
mStoppingActivities.add(r);
...
}
....
return r;
}
...
}
让我们再回到resumeTopActivityLocked继续看,resume之后会回调completeResumeLocked函数,继续执⾏stop,这个函数通过向Handler发送IDLE_TIMEOUT_MSG消息来回调activityIdleInternal函数,最终执⾏destroyActivityLocked销毁ActivityRecord,
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
...
if (next.app != null && next.app.thread != null) {                  ...
try {
。。。
next.app.thread.scheduleResumeActivity(next.appToken,
mService.isNextTransitionForward());
..。
try {
销子材料next.visible = true;
completeResumeLocked(next);
}
....
}
在销毁Activity的时候,如果当前APP的Activity堆栈为空了,就说明当前Activity没有可见界⾯了,这个时候就需要动态更新这个APP的优先级,详细代码如下:

本文发布于:2024-09-23 14:36:28,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/278266.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:进程   可见   系统   后台   前台
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议