目录
概述
Android系统的进程管理理念是希望应用进程能够尽量长时间的存活,提升用户体验。Android的应用进程在首次启动的时候会比较慢,因为第一次启动的时候包含了进程的创建以及Application等信息的初始化,这个过程会消耗一定的时间,所以应用在启动以后,不会轻易被杀死掉,以达到在下一次启动的时候加快速度。同样APP自己也会使用一些手段,希望自己长时间存活。设备的内存以及一些资源是有限的,不可能承载无限多的进程运行,当进程达到一定的数量,大量消耗设备内存后,手机设备性能就会下降,如果放任所有进程一直存活下去,设备内存很快就会消耗完毕。
所以系统需要杀死一些进程对内存进行回收,确保系统可以稳定的一直运行下去。但是哪些进程应该被杀掉呢?Android系统会根据进程中的组件状态来决定一个进程的优先级adj值,优先级最低的进程会最先被杀掉,依次类推,来确保系统正常运转。
AMS对进程的描述
Android 中AMS服务负责进程的创建和销毁,Android系统对用户屏蔽了进程的概念,用户在开发自己的应用的时候无需太关系进程相关的处理。
进程在AMS中由一个ProcessRecord来表示,该对象记录了一个进程的所有信息,但是ProcessRecord在AMS服务中只是代表一个进程,真正的应用进程运行在独立的进程中,主线程是ActivityThread。ProcessRecord的变量IApplicationThread thread用来关联真正的进程,IApplicationThread是一个binder类,当他不为空的时候,thread持有了Binder的代理端,而服务端实现在ActivityThread中。
AMS可以通过IApplicationThread通知对应的进程做一些对应的操作。
ProcessRecord详情可参考ProcessRecord分析
AMS服务创建进程
既然AMS负责管理进程,那么AMS是怎么创建一个进程的呢?在什么场景下会创建新的进程呢?
进程的创建场景
- 启动Activity的时候,如果当前Activity的宿主进程尚未创建,则需要先创建对应的进程
ActivityStackSupervisor::startSpecificActivityLocked()
- 启动一个Service组件的时候,Service组件的宿主进程尚未创建,需要先创建对应的进程
ActiveServices::bringUpServiceLocked()
- 获取一个ContentProvider组件连接的时候,ContentProvider宿主进程尚未创建且ContentProvider组件必须运行在其宿主进程中,需要先创建对应的进程
ActivityManagerService::getContentProviderImpl()
- 如果发送一个广播的时候,发现接收该广播的Receiver是静态注册的广播,且注册该广播的进程尚未启动,则需要先创建对应的进程
BroadcastQueue::processNextBroadcast()
- backup组件
ActivityManagerService::bindBackupAgent()
- systemServer进程启动的时候,persistent进程会被启动
addAppLocked()
进程创建流程
- 从AMS的进程列表中查询对应的ProcessRecord信息
- 如果AMS进程列表中未找到对应的ProcessRecord信息,说明进程还未启动,需要创建新的进程
- 创建一个ProcessRecord来描述要创建进程的信息,并进程初始化,保存到AMS的ProcessRecord列表中,此时的ProcessRecord值保存了基本的信息,尚未和一个真正的进程进行关联
- 为新启动进程设置必须的参数
- 调用Process.start来启动一个新的进程
- 将参数通过socket发送给Zygote进程,Zygote进程fork出一个新的进程,返回新创建的进程pid
- 将进程的Pid保存到ProcessRecord对象的pid变量中. AMS端创建进程的逻辑就执行完成了。
- Zygote fork出的新进程会执行ActivityThread.main方法,初始化新的进程,此时一个新的进程真正的运行起来了,同时也创建了一个IApplicationThread的服务端用于接收AMS的消息.
- 新进程调用AMS.attachApplicationLocked方法,将IApplicationThread的代理端发送给了AMS, AMS查找到对应的ProcessRecord对象,将Binder代理端保存到ProcessRecord的thread变量中,此时ProcessRecord对象和一个进程就关联起来了。
AMS服务查杀进程
杀死进程的方法有那些?
方法名称 | 方法描述 | 调用角色 |
---|---|---|
System.exit(0) | 退出虚拟机 | 应用 |
killProcessGroup(int uid, int pid) | 杀死pid所在进程组内的所有进程 | 系统 应用 |
killPids(int[] pids, String pReason, boolean secure) | 只有系统UID可以调用,根据指定的pid信息计算一个worstType, 小于某一个adj的进程会被杀死,不能保证指定的pid进程一定被杀掉 | 系统 |
killUid(int appId, int userId, String reason) | 杀死UID下所有的进程, system_server和native进程除外 | 系统 应用 |
killApplication(String pkg, int appId, int userId, String reason) | 只有系统Uid可以调用,强制杀死某一个应用 | 系统 |
killApplicationProcess(String processName, int uid) | 系统uid才能调用,通过进程见调用通知进程自杀 | 系统 |
killAllBackgroundProcesses() | 杀死所有优先级小于CACHE的进程,需要权限 | 系统 |
killBackgroundProcesses(final String packageName, int userId) | 杀死指定Package下所有优先级小于Service的进程 | 系统 |
killProcessesBelowForeground(String reason) | 杀死优先级小于FOREGROUND的进程 | 系统 |
killProcessesBelowAdj(int belowAdj, String reason) | 杀死优先级小于指定值的进程 | 系统 |
killPackageDependents(String packageName, int userId) | 杀死指定Package所有优先级小于FOREGROUND的进程 | 系统 |
killPackageProcessesLocked | 杀死指定package下小于指定优先级的进程,非系统UID只能杀死自己进程 | 系统 应用 |
以上方法提供了杀死进程的接口,用于应用或者系统主动杀死一些进程。这些进程会调用一些方法
- handleAppDiedLocked() 进程被杀死后的善后处理
参考binderDied()过程分析 - forceStopPackageLocked() 强制杀死对应进程
AMS自己会根据一些逻辑来杀死部分进程, App也会主动调用ActivityManager的接口来杀死指定进程,当进程侥幸逃脱AMS或者应用的屠刀后,就会被保存到内存中,此时这些进程就会交由LowMemoryKiller来监控了,这些进程的生死就转移到了LMK的手上.
LMK进程监控查杀
需要监控的进程,ProcessList会通过socket将他的进程id添加到lmkd的进程列表中,同时,如果进程被杀死了,则需要通ProcessList通过socket接口通过lmkd从列表中移除。这个列表就是lmkd监控的进程列表,详情参考LowMemoryKiller的实现
Android设备原始的adj阀值如图:
Android进程分为了6个等级,对应不同内存阀值, 当lmkd监控的系统内存小于某一个阀值的时候,就会开始查杀小于或等于对应优先级的进程,当释放的内存达到最大阀值之后就停止查杀。
- 前台进程 FOREGROUND_APP_ADJ (0)
前台进程是指那些有组件正和用户进行交互的应用程序的进程,也称为Active进程。这些都是Android尝试通过回收其他应用程序来使其保持相应的进程。这些进程的数量非常少,只有等到最后关头才会终止这些进程,是用户最不希望终止的进程。例如:而当你运行浏览器这类应用时,它们的界面就会显示在前台,它们就属于前台进程,当你按home键回到主界面,他们就变成了后台程序。
如果一个进程满足以下任一条件,即视为前台进程:
(1)托管处于活动状态的Activity,也就是说,它们位于前台并对用户事件进行响应,此时的情形为响应了Activity中的onResume()生命周期方法,但没有响应onPause()。
(2)托管正在执行onReceive()方法处理事件程序的BroadcastReceiver。
(3)托管正在执行onStart()、onCreate()或onDestroy()事件处理程序的Service。
(4)托管正在运行且被标记为在前台运行的Service,即调用了该Service的startForeground()方法。
(5)托管某个Service,且该Service正绑定在用户正在交互的Activity的Service,即该Activity正处于活动状态。
- 可见进程 VISIBLE_APP_ADJ (100)
没有任何前台组件、但仍然会影响用户在屏幕上所见内容的进程。如果一个进程满足以下任一条件,即视为可见进程:
(1)托管不在前台、但仍对用户可见的Activity(已调用其onPause()方法)。例如:如果前台Acitivty启动了一个对话框,或者启动了一个非全屏,亦或是一个透明的Activity,允许在其后显示上一个Activity,则可能会发生这种情况,这类Activity不在前台运行,也不能对用户事件作出反应。
(2)托管绑定到可见Activity的Service
可见进程被视为是极其重要的进程,这类进程的数量也很少,只有在资源极度匮乏的环境下,为保证前台进程继续执行时才会终止。
- 可感知进程 PERCEPTIBLE_APP_ADJ (200)
用户可以感知到的进程,如后台播放音乐的进程
- 备份进程 BACKUP_APP_ADJ (300)
处于备份过程中的进程
- CACHED_APP_MIN (900)
缓存进程ADJ最小值
- CACHED_APP_MAX (906)
缓存进程ADJ的最大值
lmkd根据以上6个等级对进程进程查杀,只要内存阀值达到某个等级,小于该优先级等级的进程都属于查杀对象。
当进程被LMK杀死后,binder死亡通知会通知AMS, AMS执行handleAppDiedLocked来进程善后处理。清理该进程的信息,并将该进程从AMS中移除。
LMK对进程的查杀是依赖adj等级的,但是进程adj如何进行分配的?这就要看下AMS对进程adj的管理策略。
AMS对保存在内存中的进程管理策略
Android对进程管理是最大限度的将进程保留在内存中,在内存不够的时候杀死一些不重要的进程,如何决定哪些进程是不重要的,哪些进程是重要的,以及如何杀死进程,这个就是Android进程管理的策略。
优先级分类
ADJ取值 | 进程类别 | 含义 |
---|---|---|
-1000 | NATIVE_ADJ | native进程 |
-900 | SYSTEM_ADJ | System_server进程 |
-800 | PERSISTENT_PROC_ADJ | 系统persistent进程 |
-700 | PERSISTENT_SERVICE_ADJ | 系统或者persistent进程绑定的进程 |
0 | FOREGROUND_APP_ADJ | 前台进程 :和用户交互的进程,不到万不得已不能杀死 |
100 | VISIBLE_APP_ADJ | 可见进程:该进程的某个UI组件是可以被用户看见的,但是没有和用户进行交互,不能随便杀死,影响用户体验 |
200 | PERCEPTIBLE_APP_ADJ | 可感知进程:该进程的某个组件可以被用户感知到,如后台音乐播放 |
300 | BACKUP_APP_ADJ | 备份进程:不可轻易打断,否则容易引起不可修复的数据错误 |
400 | HEAVY_WEIGHT_APP_ADJ | 重量级进程 |
500 | SERVICE_ADJ | 服务进程 |
600 | HOME_APP_ADJ | Lanucher进程 |
700 | PREVIOUS_APP_ADJ | 上一个访问的进程 |
800 | SERVICE_B_ADJ | B list中的进程 |
900 | CACHED_APP_MIN_ADJ | 不可见进程adj最小值 |
906 | CACHED_APP_MAX_ADJ | 不可见进程adj最大值 |
1001 | UNKNOWN_ADJ | 错误的adj值 |
Android将进程的优先级分为以上几种,从表格中可以看出,值越小优先级越高,小于0的优先级的进程基本上不会被杀死的,这些都是系统的重要进程,杀死之后会影响到整个系统的运行。
进程的优先级是如何分配的呢?
Android的ProcessRecord中有adj变量,代表当前进程的优先级,每当系统中的进程组件发生变化,就会调整某个或者调整全部进程的adj优先级。
进程的LRU列表管理
在AMS服务中对于进程管理有一系列保存进程信息ProcessRecord的容器,其中mLruProcesses列表用于按照进程的最近的使用情况,对进程进行排序保存.
final ArrayList mLruProcesses = new ArrayList();
AMS将mLruProcesses列表分成了三个区域,使用两个变量来记录三个区域的分割点
- mLruProcessServiceStart 表示从这个位置之后存放包含Service相关组件的进程信息
- mLruProcessActivityStart 表示从这个位置之后存放包含Activity相关组件的进程信息
LRU列表中进程位置调整主要遵循的策略如下:
- 如果当前正在和用户交互的进程放在列表的尾部
- 如果包含Activity的进程,但是却不是Top进程的,放到尾部倒数第二位置
- 如果只包含Service的进程,放到Service部分的尾部
- 如果其他进程,则放到其他进程部分的尾部
调整完当前进程的位置后,还需要调整当前进程所依赖进程的优先级,比如依赖另一个进程的Service或者Provider,则不希望其进程被kill掉。调整依赖进程优先级的逻辑总体如下:
- 如果其他进程的位置被自己进程位置靠后,说明自己依赖进程优先级本身就是比自己进程高的,这时候不需要处理
- 如果依赖进程的是包含有Acitivity组件的,也不需要处理
- 如果依赖进程的位置比自己靠前,优先级被自己低,则要适当的将其位置往后调,因为越靠近LRU列表尾部的进程,说明该进程刚刚运行过,重要性比较高.
根据以上一系列逻辑之后,进程在LRU列表中的位置就调整完了,但是,不是要调整进程的优先级adj么?调整LRU做什么呢?LRU Cache中存放的进程是根据最近使用过的来排顺序的,越是最近使用的越是靠近列表尾部,当然分为三个区域的。LRU列表调整完成之后,adj调整的时候就会根据LRU列表尾部开始循环遍历计算进程的adj值, 最近使用的进程adj值会被先计算。
并不是所有的进程都会被计算adj值,保存到内存中,在内存中保存到进程是有限制的,比如系统会限制Cache类型的进程和Empty类型的进程每种最多保存16个,也就是最多32个. 超过32个之后的进程会被直接杀死。也就是说LRU列表中靠近底部的很久未使用的进程很可能会被AMS直接杀死,根本不会保存到内存中。
进程优先级计算
- 优先级 < 0的系统重要进程
- NATIVE_ADJ(-1000):是由init进程fork出来的Native进程,并不受system管控;
- SYSTEM_ADJ(-900):是指system_server进程;
- PERSISTENT_PROC_ADJ(-800): 是指在AndroidManifest.xml中申明android:persistent=”true”的系统(即带有FLAG_SYSTEM标记)进程,persistent进程一般情况并不会被杀,即便被杀或者发生Crash系统会立即重新拉起该进程。
- PERSISTENT_SERVICE_ADJ(-700):是由startIsolatedProcess()方式启动的进程,或者是由system_server或者persistent进程所绑定(并且带有BIND_ABOVE_CLIENT或者BIND_IMPORTANT)的服务进程
以上进程都是系统重要进程,其adj优先级是在启动的时候就设置好,无需重新计算其优先级。
- FOREGROUND_APP_ADJ (0) 前台进程
1:满足以下任一条件的进程都属于FOREGROUND_APP_ADJ(0)优先级:
正处于resumed状态的Activity
正执行一个生命周期回调的Service(比如执行onCreate,onStartCommand,onDestroy等)
正执行onReceive()的BroadcastReceiver
通过startInstrumentation()启动的进程
场景2: 当客户端进程activity里面调用bindService()方法时flags带有BIND_ADJUST_WITH_ACTIVITY参数,并且该activity处于可见状态,则当前服务进程也属于前台进程,源码如下:
场景3: 对于provider进程,还有以下两个条件能成为前台进程:
当Provider的客户端进程ADJ<=FOREGROUND_APP_ADJ时,则Provider进程ADJ等于FOREGROUND_APP_ADJ
当Provider有外部(非框架)进程依赖,也就是调用了getContentProviderExternal()方法,则ADJ至少等于FOREGROUND_APP_ADJ
- VISIBLE_APP_ADJ (100) 可见进程
当ActivityRecord的visible=true,也就是Activity可见的进程。
可见进程VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)可感知进程之间有99个槽,用于细化可见进程的adj值.
调用rankTaskLayersIfNeed对TaskRecord进行排序,按照TaskRecord对VisibleApp进行细化
- PERCEPTIBLE_APP_ADJ(200) 可感知进程
foregroundServices非空:前台服务进程,执行startForegroundService()方法
app.forcingToImportant非空:执行setProcessImportant()方法,比如Toast弹出过程。
hasOverlayUi非空:非activity的UI位于屏幕最顶层,比如显示类型TYPE_APPLICATION_OVERLAY的窗口
- BACKUP_APP_ADJ(300) 备份进程
执行bindBackupAgent()过程,设置mBackupTarget值;
执行clearPendingBackup()或unbindBackupAgent()过程,置空mBackupTarget值;
- HEAVY_WEIGHT_APP_ADJ(400) 重量级进程
realStartActivityLocked()过程,当应用的privateFlags标识PRIVATE_FLAG_CANT_SAVE_STATE,设置mHeavyWeightProcess值;
finishHeavyWeightApp(), 置空mHeavyWeightProcess值
- SERVICE_ADJ(500) 服务进程
没有启动过Activity,并且30分钟之内活跃过的服务进程
- HOME_APP_ADJ(600) Launcher进程
当类型为ACTIVITY_TYPE_HOME的应用启动后会设置mHomeProcess,比如桌面APP。
- PREVIOUS_APP_ADJ(700) 上一个活动的进程
用户上一个使用的包含UI的进程,为了给用户在两个APP之间更好的切换体验,将上一个进程ADJ设置到PREVIOUS_APP_ADJ的档次。 当activityStoppedLocked()过程会更新上一个应用
当provider进程,上一次使用时间不超过20S的情况下,优先级不低于PREVIOUS_APP_ADJ
- SERVICE_B_ADJ(800) B类服务进程
A类Service占比过高:当A类Service个数 > Service总数的1/3时,则加入到B类Service。
内存紧张&&A类Service占用内存较高:当系统内存紧张级别(mLastMemoryLevel)高于ADJ_MEM_FACTOR_NORMAL,且该应用所占内存lastPss大于或等于CACHED_APP_MAX_ADJ级别所对应的内存阈值的1/3
以上内存参考 解读Android进程优先级ADJ算法, 该文章分析更加详细
- Cached和Empty进程 (900 ~ 906)
Android默认对cached进程和Empty进程有最大数量限制为32个,Cached进程和Empty进程默认上限分别都是16个。
系统将900 ~ 906 这几个adj的值分成6个卡槽,分别用来放缓存进程和空进程
分别计算当前共有多少个空进程和缓存进程,分别平均放到对应的卡槽中,每个卡槽中可能会放多个进程. 每种类型的进程超过16个之后就直接kill掉,不再为其分配adj有lmk监控了.
进程优先级设置
ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
其他调整
Android进程优先级ADJ的每一个ADJ级别往往都有多种场景,使用adjType完美地区分相同ADJ下的不同场景; 不同ADJ进程所对应的schedGroup不同,从而分配的CPU资源也不同,schedGroup大体分为TOP(T)、前台(F)、后台(B); ADJ跟AMS中的procState有着紧密的联系。
adj:通过调整oom_score_adj来影响进程寿命(Lowmemorykiller杀进程策略);
schedGroup:影响进程的CPU资源调度与分配;
procState:从进程所包含的四大组件运行状态来评估进程状态,影响framework的内存控制策略。比如控制缓存进程和空进程个数上限依赖于procState,再比如控制APP执行handleLowMemory()的触发时机等。