AndroidAndroid面试二

Android进程管理篇(三)-AMS进程调度

2019-01-16  本文已影响76人  Stan_Z
一、背景介绍

Android在设计上是有真后台的,理论上是希望应用程序能尽可能长地存活,这样用户体验会更好,毕竟热启动肯定比冷启动要快。但是系统内存是有限的,不可能让所有应用一视同仁地存活着,所以需要系统制定一套规则来统一管理,决定在什么场景下哪些应用要保证它的使用体验,哪些应用需要被杀掉腾出内存空间来。在Android framework层中,ActivityManagerService简称AMS,就主要负责进程及其组件的调度和管理。

二、规则介绍

那具体怎么管理?最简单的想法就是通过优先级来控制。

那么按优先级来划分进程,粗分为5类:

前台进程(Foreground process):用户当前操作所必需的进程。

可见进程(Visible process): 没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。

服务进程(Service process): 尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。

后台进程(Background process) 后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。

空进程(Empty process)只有一个进程壳子,存在的意义仅仅是缩短启动时间。

当然系统划分不会这么糙,分别通过adj和procState来给进程贴优先级标签,其中adj定义在ProcessList.java中,procState定义在ActivityManager.java中,如下所示:

ADJ:

ADJ级别 取值 含义
NATIVE_ADJ -1000 native进程
SYSTEM_ADJ -900 仅指system_server进程
PERSISTENT_PROC_ADJ -800 系统persistent进程
PERSISTENT_SERVICE_ADJ -700 关联着系统或persistent进程
FOREGROUND_APP_ADJ 0 前台进程
VISIBLE_APP_ADJ 100 可见进程
PERCEPTIBLE_APP_ADJ 200 可感知进程,比如后台音乐播放
BACKUP_APP_ADJ 300 备份进程
HEAVY_WEIGHT_APP_ADJ 400 重量级进程
SERVICE_ADJ 500 服务进程
HOME_APP_ADJ 600 Home进程
PREVIOUS_APP_ADJ 700 上一个进程
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可见进程的adj最小值
CACHED_APP_MAX_ADJ 906 不可见进程的adj最大值

ProcState:

state级别 取值 解释
PROCESS_STATE_CACHED_EMPTY 16 进程处于cached状态,且为空进程
PROCESS_STATE_CACHED_ACTIVITY_CLIENT 15 进程处于cached状态,且为另一个cached进程(内含Activity)的client进程
PROCESS_STATE_CACHED_ACTIVITY 14 进程处于cached状态,且内含Activity
PROCESS_STATE_LAST_ACTIVITY 13 后台进程,且拥有上一次显示的Activity
PROCESS_STATE_HOME 12 后台进程,且拥有home Activity
PROCESS_STATE_RECEIVER 11 后台进程,且正在运行receiver
PROCESS_STATE_SERVICE 10 后台进程,且正在运行service
PROCESS_STATE_HEAVY_WEIGHT 9 后台进程,但无法执行restore,因此尽量避免kill该进程
PROCESS_STATE_BACKUP 8 后台进程,正在运行backup/restore操作
PROCESS_STATE_IMPORTANT_BACKGROUND 7 对用户很重要的进程,用户不可感知其存在
PROCESS_STATE_IMPORTANT_FOREGROUND 6 对用户很重要的进程,用户可感知其存在
PROCESS_STATE_TOP_SLEEPING 5 与PROCESS_STATE_TOP一样,但此时设备正处于休眠状态
PROCESS_STATE_FOREGROUND_SERVICE 4 拥有一个前台Service
PROCESS_STATE_BOUND_FOREGROUND_SERVICE 3 拥有一个前台Service,且由系统绑定
PROCESS_STATE_TOP 2 拥有当前用户可见的top Activity
PROCESS_STATE_PERSISTENT_UI 1 persistent系统进程,并正在执行UI操作
PROCESS_STATE_PERSISTENT 0 persistent系统进程
PROCESS_STATE_NONEXISTENT -1 不存在的进程
三、调度策略分析

标签定义好了,那么如何去为进程设置adj和procState值,以及如何决定在哪些场景下杀掉哪些进程呢?那么来看看AMS的调度策略

AMS中与adj相关的有三个方法:

updateOomAdjLocked只是一个调用的入口而已,实际干活的是computeOomAdjLocked和applyOomAdjLocked,那么来分别看看,代码不贴了,总结下主要的功能点。

updateOomAdjLocked有无参、一参、五参三个重载方法,主要看看无参方法:

final void updateOomAdjLocked() {

LruProcesses以Lru的方式保存活着的进程

emptyProcessLimit 与 cachedProcessLimit
设置好empty和cache的数量,可以设置cache和empty的总数mProcessLimit(默认是32,2G内存可能调整为16),一般来说他俩各占一半。再往下是初始化一些变量的操作,这里要重点注意numSlots所表达的意思。ProcessList.CACHED_APP_MAX_ADJ和Process.CACHED_APP_MIN_ADJ常量系统默认的值分别为906和900,表示的是后台进程和empty进程分配的值是在900至906之间,共有7个值。numSloas计算过程中除以2是因为每个槽配置到进程包含cache进程和empty进程两种,两种进程需用不同adj值表示,所以每个槽包含两个adj值的分配空间,所以需要除以二,计算出来的numSlots值为3。emptyFactor表示每个槽中需要放置几个empty进程,是根据当前empty进程总数决定的,cachedFactor即是表示需要放置几个后台进程到每个槽中。为便于理解,结合后面代码逻辑举例来讲,比如后台此时有15个cahcedProcess(后台进程)和12个emptyProcess,则会将15/3=5个cachedProcess设置到在第一个槽(可分配900,901这两个oom_adj值)中,oom_adj设置为900;将12/3=4个emptyProcess设置在第一槽,oom_adj值设置为901;然后再设置5个cachedProcess和4个emptyProcess的oom_adj值分别为902和903,即第二个槽。

从LruProcesses最新加入的元素开始逐个取,更新所有进程状态
先走computeOomAdjLocked

根据进程状态来计算出对应的adj和procState

前台

非前台

adj > 200的情况(可感知的后台进程)

当进程为HeavyWeightProcess,则adj=4, procState=9;

当进程为HomeProcess情况,则adj=6, procState=12;

当进程为PreviousProcess情况,则adj=7, procState=13;

备份进程,则adj=3, procState=7或8

Service情况:

当adj>0 或 schedGroup为后台线程组 或procState>2时,双重循环遍历:

ContentProvider情况

当adj>0 或 schedGroup为后台线程组 或procState>2时,双重循环遍历:

之后再根据一些逻辑调整下adj:d
比如:当A类Service个数 > service/3时,则加入到B类Service

经过computeOomAdjLocked之后,还存在部分进程仍然未分配adj,但是procState是有的。一般剩下的进程只会被分配成cache和empty。

那么接下来再讲讲updateOomAdjLocked之后的进程管理策略:

applyOomAdjLocked(app, true, now, nowElapsed);//应用当前进程设置的adj

这部分其实就是把adj把adj值 通过socket通信发送给lmkd守护进程,并把对应值写入:/proc/<pid>/oom_score_adj

最后就是lowmemorykiller的查杀进程逻辑了。具体可以参考我之前的文章:lowmemorykiller总结

}

四、进程管理的思考

站在系统的角度:

尽量权衡好内存和用户体验两者关系

站在应用的角度:

肯定是希望自己能存活越久越好,这就牵扯到一些进程保活的方式(这个后续会总结),其中一类就是想办法提高进程的adj,但是凡是不是以真正业务需要来强行保活的行为都是耍流氓。

五、对APP开发者的建议
  1. 只有真正需要用户可感知的应用,才调用startForegroundService()方法来启动前台服务,此时ADJ=PERCEPTIBLE_APP_ADJ(200),常驻内存,并且会在通知栏常驻通知提醒用户,比如音乐播放,地图导航。切勿为了常驻而滥用前台服务,这会严重影响用户体验。

  2. 进程中的Service工作完成后,务必主动调用stopService或stopSelf来停止服务,避免占据内存,浪费系统资源;

  3. 不要长时间绑定其他进程的service或者provider,每次使用完成后应立刻释放,避免其他进程常驻于内存;

  4. APP应该实现接口onTrimMemory()和onLowMemory(),根据TrimLevel适当地将非必须内存在回调方法中加以释放。当系统内存紧张时会回调该接口,减少系统卡顿与杀进程频次。

  5. 减少在保活上花心思,更应该在优化内存上下功夫,因为在相同ADJ级别的情况下,系统会选择优先杀内存占用大的进程。

六、一个小Tips

UI进程与Service进程一定要分离,因为对于包含activity的service进程,一旦进入后台就有机会成为cache进程(ADJ>=900),随时可能会被系统回收;而分离后的Service进程服务属于SERVICE_ADJ(500),被杀的可能性相对较小。尤其是系统允许自启动的服务进程必须做UI分离,避免消耗系统较大内存。

if (app.hasShownUi && app != mHomeProcess) {
    // If this process has shown some UI, let it immediately
    // go to the LRU list because it may be pretty heavy with
    // UI stuff.  We'll tag it with a label just to help
    // debug and understand what is going on.
    if (adj > ProcessList.SERVICE_ADJ) {
        app.adjType = "cch-started-ui-services";
    }

修改方案:
1当前进程不运行Activity, 只运行Service
2 采用fg-service

参考:
http://gityuan.com/2018/05/19/android-process-adj/

上一篇下一篇

猜你喜欢

热点阅读