美文网首页AndroidAndroid面试二
Android进程管理篇(三)-AMS进程调度

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

作者: Stan_Z | 来源:发表于2019-01-16 21:22 被阅读76次
一、背景介绍

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

二、规则介绍

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

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

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

  • 拥有用户正在交互的 Activity(已调用onResume())
  • 拥有某个 Service,后者绑定到用户正在交互的 Activity
  • 拥有正在“前台”运行的 Service(服务已调用 startForeground())
  • 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
  • 拥有正执行其 onReceive() 方法的 BroadcastReceiver

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

  • 拥有不在前台、但仍对用户可见的 Activity(已调用onPause())。
  • 拥有绑定到可见(或前台)Activity 的 Service

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

  • 正在运行startService()方法启动的服务,且不属于上述两个更高类别进程的进程。

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

  • 对用户不可见的Activity的进程(已调用Activity的onStop()方法)

空进程(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:在进程组件生命周期变化时更新adj,然后分别执行以下两个方法
  • computeOomAdjLocked:计算adj
  • applyOomAdjLocked:应用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

前台

  • 拥有当前用户可见的activity ,则adj=0,procState=2
  • 拥有一个前台service ,则adj=0,procState=4
  • 后台进程,且正在运行receiver ,则adj=0,procState=11
  • 后台进程,且正在运行service ,则adj=0,procState=10
  • 以上条件都不符合,则adj=cachedAdj,procState=16

非前台

  • 当activity可见, 则adj=1,procState=2;
  • 当activity正在暂停或者已经暂停, 则adj=2,procState=2;
  • 当activity正在停止, 则adj=2,procState=13(且activity尚未finish);
  • 以上都不满足,否则procState=14

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

  • 当存在前台service时,则adj=2, procState=4;
  • 当强制前台时,则adj=2, procState=6;

当进程为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时,双重循环遍历:

  • 当service已启动,则procState<=10;
    • 当service在30分钟内活动过,则adj=5,cached=false;
  • 获取service所绑定的connections
    • 当client与当前app同一个进程,则continue;
    • 当client进程的ProcState >=cache,则设置为空进程
    • 当进程存在显示的ui,则将当前进程的adj和ProcState值赋予给client进程
    • 当不存在显示的ui,且service上次活动时间距离现在超过30分钟,则只将当前进程的adj值赋予给client进程
    • 当前进程adj > client进程adj的情况
      • 当service进程比较重要时,则设置adj >= -11
      • 当client进程adj<2,且当前进程adj>2时,则设置adj=2;
      • 当client进程adj>1时,则设置adj = clientAdj
      • 否则,设置adj <= 1;
      • 若client进程不是cache进程,则当前进程也设置为非cache进程
    • 当绑定的是前台进程的情况
      • 当client进程状态为前台时,则设置mayBeTop=true,并设置client进程procState=16
      • 当client进程状态 < 2的前提下:若绑定前台service,则clientProcState=3;否则clientProcState=6
    • 当connections并没有绑定前台service时,则clientProcState >= 7
    • 保证当前进程procState不会必client进程的procState大
  • 当进程adj >0,且activity可见 或者resumed 或 正在暂停,则设置adj = 0

ContentProvider情况

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

  • 当client与当前app同一个进程,则continue;
  • 当client进程procState >=14,则设置成procState =16
  • 没有ui展示,则保证adj >=0
  • 当client进程状态为前台时,则设置mayBeTop=true,并设置client进程procState=16设置为空进程
  • 当client进程状态 < 2时,则clientProcState=3;
  • procState 比client进程值更大时,则取client端的状态值。
  • 当contentprovider存在外部进程依赖(非framework)时,则设置adj =0, procState=6

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

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

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

  • 当cached进程超过上限(cachedProcessLimit),则杀掉该进程
  • 当空进程超过上限(emptyProcessLimit),则杀掉该进程
  • 当空进程超过上限(TRIM_EMPTY_APPS为cachedProcessLimit的一半),且空闲时间超过30分钟,则杀掉该进程

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

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

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

}

四、进程管理的思考

站在系统的角度:

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

  • 对于内存较大的手机,可以尽量多保证后台进程的数量,这样可以保证APP启动速度
  • 对于内存较小手机,减少cache/empty 进程数量,在一定情况下BServices进程可以降级为cache/empty,利于杀死更多进程。启动某些大应用的时候,可以把cache/empty清理掉,比如启动相机、启动某款大型游戏等等。
  • 对于一些热门的APP做白名单来给特权,保障使用体验等等。

站在应用的角度:

肯定是希望自己能存活越久越好,这就牵扯到一些进程保活的方式(这个后续会总结),其中一类就是想办法提高进程的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/

相关文章

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

    一、背景介绍 Android在设计上是有真后台的,理论上是希望应用程序能尽可能长地存活,这样用户体验会更好,毕竟热...

  • Framework之AMS启动流程,简单上手!

    一、AMS介绍 AMS是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调...

  • Android系统从init进程到Launcher(四)

    前言 AMS 是 Android 中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等...

  • ActivityManagerService 通信浅析

    AMS在Android系统中主要负责: 四大组件的启动、切换、调度; 应用进程的管理和调度; 其职责与操作系统中的...

  • AMS启动过程分析

    AMS(ActivityManagerService)主要负责四大组件的启动、切换、调度以及应用进程的管理和调度工...

  • ActivityManagerService简析

    AMS在Android系统中扮演很重要的角色,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作...

  • AMS

    AMS 是系统的引导服务,应用进程的启动、切换和调度、四大组件的启动和管理都需要 AMS 的支持。 AMS 的启动...

  • Android进程管理

    Process ADJ 进程生命周期 相关资料 进程和线程 AMS进程管理

  • Android应用启动、退出分析

    §AMS和应用进程§应用启动流程§应用退出流程§启动、退出消息 AMS和应用进程 应用进程 <- 系统管理 <- ...

  • 面试官:我们谈谈Android中的进程ProcessRecord

    Android系统中用于描述进程的数据结构是ProcessRecord对象,AMS便是管理进程的核心模块。四大组件...

网友评论

    本文标题:Android进程管理篇(三)-AMS进程调度

    本文链接:https://www.haomeiwen.com/subject/faevdqtx.html