Android进程管理总结

目录

概述

Android系统的进程管理理念是希望应用进程能够尽量长时间的存活,提升用户体验。Android的应用进程在首次启动的时候会比较慢,因为第一次启动的时候包含了进程的创建以及Application等信息的初始化,这个过程会消耗一定的时间,所以应用在启动以后,不会轻易被杀死掉,以达到在下一次启动的时候加快速度。同样APP自己也会使用一些手段,希望自己长时间存活。设备的内存以及一些资源是有限的,不可能承载无限多的进程运行,当进程达到一定的数量,大量消耗设备内存后,手机设备性能就会下降,如果放任所有进程一直存活下去,设备内存很快就会消耗完毕。

所以系统需要杀死一些进程对内存进行回收,确保系统可以稳定的一直运行下去。但是哪些进程应该被杀掉呢?Android系统会根据进程中的组件状态来决定一个进程的优先级adj值,优先级最低的进程会最先被杀掉,依次类推,来确保系统正常运转。

AMS对进程的描述

Android 中AMS服务负责进程的创建和销毁,Android系统对用户屏蔽了进程的概念,用户在开发自己的应用的时候无需太关系进程相关的处理。

进程在AMS中由一个ProcessRecord来表示,该对象记录了一个进程的所有信息,但是ProcessRecord在AMS服务中只是代表一个进程,真正的应用进程运行在独立的进程中,主线程是ActivityThread。ProcessRecord的变量IApplicationThread thread用来关联真正的进程,IApplicationThread是一个binder类,当他不为空的时候,thread持有了Binder的代理端,而服务端实现在ActivityThread中。

Untitled Diagram (9).png

AMS可以通过IApplicationThread通知对应的进程做一些对应的操作。
ProcessRecord详情可参考ProcessRecord分析

AMS服务创建进程

既然AMS负责管理进程,那么AMS是怎么创建一个进程的呢?在什么场景下会创建新的进程呢?

进程的创建场景

  1. 启动Activity的时候,如果当前Activity的宿主进程尚未创建,则需要先创建对应的进程

ActivityStackSupervisor::startSpecificActivityLocked()

  1. 启动一个Service组件的时候,Service组件的宿主进程尚未创建,需要先创建对应的进程

ActiveServices::bringUpServiceLocked()

  1. 获取一个ContentProvider组件连接的时候,ContentProvider宿主进程尚未创建且ContentProvider组件必须运行在其宿主进程中,需要先创建对应的进程

ActivityManagerService::getContentProviderImpl()

  1. 如果发送一个广播的时候,发现接收该广播的Receiver是静态注册的广播,且注册该广播的进程尚未启动,则需要先创建对应的进程

BroadcastQueue::processNextBroadcast()

  1. backup组件

ActivityManagerService::bindBackupAgent()

  1. systemServer进程启动的时候,persistent进程会被启动

addAppLocked()

进程创建流程

  1. 从AMS的进程列表中查询对应的ProcessRecord信息
  2. 如果AMS进程列表中未找到对应的ProcessRecord信息,说明进程还未启动,需要创建新的进程
  3. 创建一个ProcessRecord来描述要创建进程的信息,并进程初始化,保存到AMS的ProcessRecord列表中,此时的ProcessRecord值保存了基本的信息,尚未和一个真正的进程进行关联
  4. 为新启动进程设置必须的参数
  5. 调用Process.start来启动一个新的进程
  6. 将参数通过socket发送给Zygote进程,Zygote进程fork出一个新的进程,返回新创建的进程pid
  7. 将进程的Pid保存到ProcessRecord对象的pid变量中. AMS端创建进程的逻辑就执行完成了。
  8. Zygote fork出的新进程会执行ActivityThread.main方法,初始化新的进程,此时一个新的进程真正的运行起来了,同时也创建了一个IApplicationThread的服务端用于接收AMS的消息.
  9. 新进程调用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只能杀死自己进程 系统 应用

以上方法提供了杀死进程的接口,用于应用或者系统主动杀死一些进程。这些进程会调用一些方法

  1. handleAppDiedLocked() 进程被杀死后的善后处理
    参考binderDied()过程分析
  2. forceStopPackageLocked() 强制杀死对应进程

AMS自己会根据一些逻辑来杀死部分进程, App也会主动调用ActivityManager的接口来杀死指定进程,当进程侥幸逃脱AMS或者应用的屠刀后,就会被保存到内存中,此时这些进程就会交由LowMemoryKiller来监控了,这些进程的生死就转移到了LMK的手上.

LMK进程监控查杀

需要监控的进程,ProcessList会通过socket将他的进程id添加到lmkd的进程列表中,同时,如果进程被杀死了,则需要通ProcessList通过socket接口通过lmkd从列表中移除。这个列表就是lmkd监控的进程列表,详情参考LowMemoryKiller的实现

Android设备原始的adj阀值如图:


原始内存阀值

Android进程分为了6个等级,对应不同内存阀值, 当lmkd监控的系统内存小于某一个阀值的时候,就会开始查杀小于或等于对应优先级的进程,当释放的内存达到最大阀值之后就停止查杀。

  1. 前台进程 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正处于活动状态。

  1. 可见进程 VISIBLE_APP_ADJ (100)

没有任何前台组件、但仍然会影响用户在屏幕上所见内容的进程。如果一个进程满足以下任一条件,即视为可见进程:
(1)托管不在前台、但仍对用户可见的Activity(已调用其onPause()方法)。例如:如果前台Acitivty启动了一个对话框,或者启动了一个非全屏,亦或是一个透明的Activity,允许在其后显示上一个Activity,则可能会发生这种情况,这类Activity不在前台运行,也不能对用户事件作出反应。
(2)托管绑定到可见Activity的Service
可见进程被视为是极其重要的进程,这类进程的数量也很少,只有在资源极度匮乏的环境下,为保证前台进程继续执行时才会终止。

  1. 可感知进程 PERCEPTIBLE_APP_ADJ (200)

用户可以感知到的进程,如后台播放音乐的进程

  1. 备份进程 BACKUP_APP_ADJ (300)

处于备份过程中的进程

  1. CACHED_APP_MIN (900)

缓存进程ADJ最小值

  1. 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<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();

AMS将mLruProcesses列表分成了三个区域,使用两个变量来记录三个区域的分割点

  1. mLruProcessServiceStart 表示从这个位置之后存放包含Service相关组件的进程信息
  2. mLruProcessActivityStart 表示从这个位置之后存放包含Activity相关组件的进程信息
LRU列表

LRU列表中进程位置调整主要遵循的策略如下:

  1. 如果当前正在和用户交互的进程放在列表的尾部
  2. 如果包含Activity的进程,但是却不是Top进程的,放到尾部倒数第二位置
  3. 如果只包含Service的进程,放到Service部分的尾部
  4. 如果其他进程,则放到其他进程部分的尾部
    调整完当前进程的位置后,还需要调整当前进程所依赖进程的优先级,比如依赖另一个进程的Service或者Provider,则不希望其进程被kill掉。调整依赖进程优先级的逻辑总体如下:
  • 如果其他进程的位置被自己进程位置靠后,说明自己依赖进程优先级本身就是比自己进程高的,这时候不需要处理
  • 如果依赖进程的是包含有Acitivity组件的,也不需要处理
  • 如果依赖进程的位置比自己靠前,优先级被自己低,则要适当的将其位置往后调,因为越靠近LRU列表尾部的进程,说明该进程刚刚运行过,重要性比较高.

根据以上一系列逻辑之后,进程在LRU列表中的位置就调整完了,但是,不是要调整进程的优先级adj么?调整LRU做什么呢?LRU Cache中存放的进程是根据最近使用过的来排顺序的,越是最近使用的越是靠近列表尾部,当然分为三个区域的。LRU列表调整完成之后,adj调整的时候就会根据LRU列表尾部开始循环遍历计算进程的adj值, 最近使用的进程adj值会被先计算。

并不是所有的进程都会被计算adj值,保存到内存中,在内存中保存到进程是有限制的,比如系统会限制Cache类型的进程和Empty类型的进程每种最多保存16个,也就是最多32个. 超过32个之后的进程会被直接杀死。也就是说LRU列表中靠近底部的很久未使用的进程很可能会被AMS直接杀死,根本不会保存到内存中。

进程优先级计算

  1. 优先级 < 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优先级是在启动的时候就设置好,无需重新计算其优先级。

  1. 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

  1. VISIBLE_APP_ADJ (100) 可见进程

当ActivityRecord的visible=true,也就是Activity可见的进程。
可见进程VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)可感知进程之间有99个槽,用于细化可见进程的adj值.

调用rankTaskLayersIfNeed对TaskRecord进行排序,按照TaskRecord对VisibleApp进行细化


ActivityStack.png
  1. PERCEPTIBLE_APP_ADJ(200) 可感知进程

foregroundServices非空:前台服务进程,执行startForegroundService()方法
app.forcingToImportant非空:执行setProcessImportant()方法,比如Toast弹出过程。
hasOverlayUi非空:非activity的UI位于屏幕最顶层,比如显示类型TYPE_APPLICATION_OVERLAY的窗口

  1. BACKUP_APP_ADJ(300) 备份进程

执行bindBackupAgent()过程,设置mBackupTarget值;
执行clearPendingBackup()或unbindBackupAgent()过程,置空mBackupTarget值;

  1. HEAVY_WEIGHT_APP_ADJ(400) 重量级进程

realStartActivityLocked()过程,当应用的privateFlags标识PRIVATE_FLAG_CANT_SAVE_STATE,设置mHeavyWeightProcess值;
finishHeavyWeightApp(), 置空mHeavyWeightProcess值

  1. SERVICE_ADJ(500) 服务进程

没有启动过Activity,并且30分钟之内活跃过的服务进程

  1. HOME_APP_ADJ(600) Launcher进程

当类型为ACTIVITY_TYPE_HOME的应用启动后会设置mHomeProcess,比如桌面APP。

  1. PREVIOUS_APP_ADJ(700) 上一个活动的进程

用户上一个使用的包含UI的进程,为了给用户在两个APP之间更好的切换体验,将上一个进程ADJ设置到PREVIOUS_APP_ADJ的档次。 当activityStoppedLocked()过程会更新上一个应用

当provider进程,上一次使用时间不超过20S的情况下,优先级不低于PREVIOUS_APP_ADJ

  1. 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算法, 该文章分析更加详细

  1. Cached和Empty进程 (900 ~ 906)
    Android默认对cached进程和Empty进程有最大数量限制为32个,Cached进程和Empty进程默认上限分别都是16个。
Untitled Diagram-2.png

系统将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()的触发时机等。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,000评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,825评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,055评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,766评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,639评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,453评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,860评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,492评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,783评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,817评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,624评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,439评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,872评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,075评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,372评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,868评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,075评论 2 338

推荐阅读更多精彩内容