“一文读懂”系列:AMS是如何动态管理进程的?

79c76bdb9231fc812bf6eaed8a2caca9.jpeg

/   今日科技快讯   /

12月20日,马斯克在推特发文称:“一旦我找到足够愚蠢的人接受这份工作,我就会辞去(推特)首席执行官的职务。在那之后,我只会管理软件和服务器团队。” 

12月19日,马斯克于推特就“是否辞去推特CEO”一事发起投票,称将遵守投票结果。马斯克写道:“我是否应该卸任Twitter的负责人?我将遵守这次投票的结果。”推文显示,他给了用户两个答案选项,“是” 或“不是”。截至投票通道关闭,赞成票占比57.5%,反对票占比42.5%。

/   作者简介   /

本篇文章转载自小余的自习室的博客,文章主要分享了AMS如何动态管理进程的,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

原文地址:

https://juejin.cn/post/7174713775944138809

/   前言   /

之前文章介绍了关于WMS在整个Android体系中的作用,主要可以划分为四类职责:

  1. 窗口管理

  2. 窗口动画

  3. Surface管理

  4. 输入事件中转站。

如果把WMS比作古代将军,那么这四类职责就是将军手下几元大将,而AMS作为Android整个体系的统筹者,理所当然的就是古代的皇帝。

而今天要讲的是Android体系中比较重要的一个概念:AMS进程管理。

传统的进程是指程序执行的载体,进程退出也就意味着程序退出了,而在Android中,进程的概念被弱化了,进程成为一个运行组件的容器。如应用中Service,即可以在宿主进程中运行也可以在服务进程中运行,服务进程退出,只是某个Service的退出,并非应用退出。

在Android中,谷歌将进程的管理和调度封装在了AMS中,应用层无需关心进程是如何工作的。

AMS对进程的管理主要体现在两个方面:

  1. 进程LRU列表动态更新:动态调整进程在mLruProcesses列表的位置

  2. 进程优先级动态调整:实际是调整进程oom_adj的值。

这两项调整和系统进行自动回收有关,当内存不足时,系统会关闭一些进程来释放内存,下面笔者就依据这两方面来看下AMS是如何管理进程的。

/   目录   /

bfb4eb5ea7945fd77e2fe58df639b1cf.png

/   进程LRU 列表动态更新  /

如果你进程看Android源码,应该会常常看看下面这个方法:updateLruProcessLocked。当时可能只是了解有这么个方法做了个缓存进程的事,但是具体是如何实现的并不知晓,总感觉看代码少了点什么,下面我们会围绕这个方法展开。

AMS中的updateLruProcessLocked实现了对进程LRU列表动态更新:在讲解updateLruProcessLocked方法前,我们先来讲解下mLruProcesses进程列表在AMS中的模型。

LRU进程列表数据结构

AMS进程的LRU列表mLruProcesses:

final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();

AMS启动的每个进程都会被添加到LRU列表中,这个LRU列表不是随意排序的或者仅仅根据先后顺序排序的,而是根据具体规则进行计算,以及进程的当前状态进行改变的、 LRU列表中存储的是一个个ProcessRecord,AMS中使用ProcessRecord来代表一个进程、内部存储了一个进程所有的信息。

LRU列表被分为3段:

  1. hasActivity:带Activity的进程

  2. hasService:带Service的进程

  3. other:其他进程。

这三段使用两个字段分割开:mLruProcessServiceStart和mLruProcessActivityStart,分别表示hasActivity段的开始位置以及hasService段的开始位置。

大概模型如下:

4cb6d105a170dbe9fbf49bfd040b6113.png

每次优先级较高的进程,如带前台Activity的进程就会优先被放到尾部,所以进程优先级由头到尾。

有了上面这个模型基础,下面我们从源码角度来看LRU列表就更轻松了。

关键方法详解

AMS使用updateLruProcessLocked方法对进程列表进行更新操作。updateLruProcessLocked()方法在ActivityStack类中有3处可能被调用。其中2处调用位置都处于ActivityStack类中的resumeTopActivityInnerLocked()方法:

  • pausing:通过home键返回或者back键退出一个Activity,此时进程中不止一个Activity、

  • resume:热启动Activity

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
      //省略。。
      if (pausing && !resumeWhilePausing) {
          if (next.app != null && next.app.thread != null) {
              mService.updateLruProcessLocked(next.app, true, null);
          }
      }

    //省略
    if (next.app != null && next.app.thread != null) {
        mService.updateLruProcessLocked(next.app, true, null);
        next.app.thread.scheduleResumeActivity(next.appToken....);
    }
    //省略

}

1处位于destroyActivityLocked()方法:如按back键退出最后一个Activity的时候。

final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
      if (hadApp) {
        if (r.app.activities.isEmpty()) {
            mService.updateLruProcessLocked(r.app, false, null);
            mService.updateOomAdjLocked();
        }
    }
    r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,..;
}

下面具体来看下该方法:

final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
        ProcessRecord client) {
    //1.判断该进程是否存在Activity
    final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
            || app.treatLikeActivity;
    //2.判断进程是否存在Service
    final boolean hasService = false; // not impl yet. app.services.size() > 0;

    //3.给LRU的序列号+1
    mLruSeq++;
    //4.如果hasActivity为true
    if (hasActivity) {
        final int N = mLruProcesses.size();
        //如果当前进程有Activity且mLruProcesses最尾部的元素是当前进程,则什么都不用处理,直接退出
        if (N > 0 && mLruProcesses.get(N-1) == app) {
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app);
            return;
        }
    } else {
        //如果当前进程没有Activity且在Other段的top元素是当前进程,则也不处理,直接退出。
        if (mLruProcessServiceStart > 0
                && mLruProcesses.get(mLruProcessServiceStart-1) == app) {
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app);
            return;
        }
    }
    //5.获取当前进程在mLruProcesses中的索引
    int lrui = mLruProcesses.lastIndexOf(app);
    //6.如果是persistent永久进程,且索引不为0,则直接退出不处理
    if (app.persistent && lrui >= 0) {

        return;
    }
    //7.索引大于等于0的情况下,对mLruProcessActivityStart和mLruProcessServiceStart进行更改并删除列表对应的索引上的进程
    if (lrui >= 0) {
        if (lrui < mLruProcessActivityStart) {
            mLruProcessActivityStart--;
        }
        if (lrui < mLruProcessServiceStart) {
            mLruProcessServiceStart--;
        }

        mLruProcesses.remove(lrui);
    }

    int nextIndex;

    if (hasActivity) {
        final int N = mLruProcesses.size();
        //8.如果hasActivity为true但是app.activities.size为0,其实就是1处的第二种判断app.hasClientActivities为true,且mLruProcessActivityStart分割点没超过列表进程数
        if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
            //9.将进程添加到mLruProcesses列表的倒数第二个位置,因为倒数第一个位置是提供给有Activity的进程使用。切记带索引的add方法只是插入不会覆盖,被顶替的元素自动后移
            mLruProcesses.add(N - 1, app);

            final int uid = app.info.uid;
            //10.为了防止当前进程创建很多Client端的进程,导致进程被滥用,将当前进程的子进程Client往重要性低处的列表排序,直到碰到不是当前进程的子进程Client端为止。
            for (int i = N - 2; i > mLruProcessActivityStart; i--) {
                ProcessRecord subProc = mLruProcesses.get(i);
                if (subProc.info.uid == uid) {

                    if (mLruProcesses.get(i - 1).info.uid != uid) {
                        //交换i和i-1位置的进程元素
                        ProcessRecord tmp = mLruProcesses.get(i);
                        mLruProcesses.set(i, mLruProcesses.get(i - 1));
                        mLruProcesses.set(i - 1, tmp);
                        i--;
                    }
                } else {
                    // A gap, we can stop here.
                    //如果出现一个uid不一致的退出for循环交换
                    break;
                }
            }
        } else {
            //11.对于有Activity的进程,则直接将进程添加到末尾。        
            mLruProcesses.add(app);
        }
        //设置nextIndex为mLruProcessServiceStart
        nextIndex = mLruProcessServiceStart;
    } else if (hasService) {
        //12.如果是有Service的进程,则将进程插入到hasService段的末尾,也就是hasActivity段的开头位置
        mLruProcesses.add(mLruProcessActivityStart, app);
        //设置nextIndex为mLruProcessServiceStart
        nextIndex = mLruProcessServiceStart;
        //将mLruProcessActivityStart hasActivity的起始索引+1;
        mLruProcessActivityStart++;
    } else  {
        // Process not otherwise of interest, it goes to the top of the non-service area.
        int index = mLruProcessServiceStart;
        //方法的第三个参数client一般都为null,这里不进入
        if (client != null) {
            //省略。。
        }
        //13.对于其他也没Activity也没Service的情况,则将进程对象下添加到Other字段末尾:此时index = mLruProcessServiceStart,也就是Other字段的末尾。
        mLruProcesses.add(index, app);
        //插入的索引的前一个索引位置
        nextIndex = index-1;
        //mLruProcessActivityStart和mLruProcessServiceStart索引均向后移动1位。
        mLruProcessActivityStart++;
        mLruProcessServiceStart++;
    }

    //对于有Service和ContentProvider的情况,也需要将Service的进程和ContentProvider的进程对象也插入到列表中。
    for (int j=app.connections.size()-1; j>=0; j--) {
        ConnectionRecord cr = app.connections.valueAt(j);
        if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
                && cr.binding.service.app != null
                && cr.binding.service.app.lruSeq != mLruSeq
                && !cr.binding.service.app.persistent) {
            nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
                    "service connection", cr, app);
        }
    }
    for (int j=app.conProviders.size()-1; j>=0; j--) {
        ContentProviderRecord cpr = app.conProviders.get(j).provider;
        if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {
            nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
                    "provider reference", cpr, app);
        }
    }
}

方法每个步骤已经在代码中做了说明,如果你仔细对照前面说的模型去看,一定能看懂。这里额外说明下两点:

  • 对于永久性的进程即设置了persistent标志的进程在列表中的位置不会更改。

  • mLruProcessActivityStart和mLruProcessServiceStart会随着列表的改变而改变,而不是固定的。

  • 为了防止某些进程自己又没Activity,却可能创建很多Client端的进程,导致进程被滥用的情况。会将当前进程的子进程Client往重要性低处的列表排序,直到碰到不是当前进程的子进程Client端为止。

  • 对于有Service和ContentProvider的情况,也需要将Service的进程和ContentProvider的进程对象也插入到LRU列表中。

看图说话:

953f48aa54f5d863417f5b029a3c6f26.png

好了,关于进程列表的动态更新就讲到这里。下面我们来讲解进程优先级动态调整。

/   进程优先级动态调整   /

AMS中的updateOomAdjLocked方法实现了进程优先级的动态更新。在讲解updateOomAdjLocked方法前,我们先来了解下与进程相关的几个重要概念。

进程优先级(OOM_ADJ)

OOM_ADJ定义在ProcessList.java文件,大概划分为20个级。

  ADJ级别

adjString

  取值

  解释

UNKNOWN_ADJ


1001

预留的最低级别,一般对于缓存的进程才有可能设置成这个级别

CACHED_APP_MAX_ADJ


999

不可见进程的adj最大值,在内存不足的情况下就会优先被kill。

CACHED_APP_LMK_FIRST_ADJ


950

lowmem 查杀的最小等级

CACHED_APP_MIN_ADJ

cch

900

不可见进程的adj最小值,在内存不足的情况下就会优先被kill

SERVICE_B_ADJ

svcb

800

非活跃进程,B List中的Service(运行时间较长、使用可能性更小)

PREVIOUS_APP_ADJ

prev

700

上一个App的进程(上一个stopActivity的进程/20s内刚被使用的provider进程)

HOME_APP_ADJ

home

600

Home进程

SERVICE_ADJ

svc

500

服务进程(Service process)

HEAVY_WEIGHT_APP_ADJ

hvy

400

后台的重量级进程

BACKUP_APP_ADJ

bkup

300

备份进程

PERCEPTIBLE_LOW_APP_ADJ

prcl

250

由系统(或其他应用程序)绑定的进程,它比服务更重要,但不易察觉(clientAdj<200通过BIND_NOT_PERCEPTIBLE bind)

PERCEPTIBLE_APP_ADJ

prcp

200

可感知进程,比如后台音乐播放 (前台服务/display an overlay UI/currently used for toasts/clientAdj<200通过BIND_NOT_VISIBLE bind)

VISIBLE_APP_ADJ(VISIBLE_APP_LAYER_MAX200-100-1)

vis

100

可见进程(Visible process) ,一般是100+当前可见的layer数:activity不在前台,但是确实可见的或者正在运行远程动画

PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ


50

应用有前台服务,从前台切换到前台service,且在15s内到过前台

FOREGROUND_APP_ADJ

fg

0

前台进程(Foreground process):应用本身就是在前台或者正在接收处理广播isReceivingBroadcastLocked或者服务执行过程中

PERSISTENT_SERVICE_ADJ

psvc

-700

关联着系统或persistent进程(由startIsolatedProcess()方式启动的进程,或者是由system_server或者persistent进程所绑定的服务进程)

PERSISTENT_PROC_ADJ

pers

-800

系统persistent进程,比如telephony(一般不会被杀,即使被杀或crash,立即重启)

SYSTEM_ADJ

sys

-900

系统进程(system_server进程)

NATIVE_ADJ

ntv

-1000

native进程(由init进程fork出的进程,并不受system管控)

获取oom_adj:

adb shell ps|grep com.android.yuhb.test

adb shell cat /proc/21375/oom_adj

每个等级的进程又有对应的优先级,使用oom_adj值来表示,进程回收机制就是根据这个adj值来进行的 前台进程adj值最低,代表进程优先级最高,空进程adj值越高,最容易被kill,对于相等优先级的进程:使用的内存越多越容易被杀死。

进程state级别(ProcState)

ProcState定义在ActivityManager.java文件,大概划分为22类。用来表示当前进程的一组状态。

  state级别

procStateString

取值

  解释

PROCESS_STATE_NONEXISTENT

NONE

20

不存在的进程

PROCESS_STATE_CACHED_EMPTY

CEM

19

处于cached状态的空进程

PROCESS_STATE_CACHED_RECENT

CRE

18

有activity在最近任务列表的cached进程

PROCESS_STATE_CACHED_ACTIVITY_CLIENT

CACC

17

进程处于cached状态,且为另一个cached进程(内含Activity)的client进程

PROCESS_STATE_CACHED_ACTIVITY

CAC

16

进程处于cached状态(内含Activity)

PROCESS_STATE_LAST_ACTIVITY

LAST

15

后台进程(拥有上一次显示的Activity)

PROCESS_STATE_HOME

HOME

14

后台进程(拥有home Activity)

PROCESS_STATE_HEAVY_WEIGHT

HVY

13

后台进程(但无法执行restore,因此尽量避免kill该进程)

PROCESS_STATE_TOP_SLEEPING

TPSL

12

与PROCESS_STATE_TOP一样,但此时设备正处于休眠状态

PROCESS_STATE_RECEIVER

RCVR

11

后台进程,且正在运行receiver

PROCESS_STATE_SERVICE

SVC

10

后台进程,且正在运行service

PROCESS_STATE_BACKUP

BKUP

9

后台进程,正在运行backup/restore操作

PROCESS_STATE_TRANSIENT_BACKGROUND

TRNB

8

后台进程

PROCESS_STATE_IMPORTANT_BACKGROUND

IMPB

7

对用户很重要的进程,用户不可感知其存在

PROCESS_STATE_IMPORTANT_FOREGROUND

IMPF

6

对用户很重要的进程,用户可感知其存在

PROCESS_STATE_BOUND_FOREGROUND_SERVICE ,

BFGS

5

通过系统绑定拥有一个前台Service

PROCESS_STATE_FOREGROUND_SERVICE

FGS

4

拥有一个前台Service

PROCESS_STATE_BOUND_TOP

BTOP

3

绑定到top应用的进程

PROCESS_STATE_TOP

TOP

2

拥有当前用户可见的top Activity

PROCESS_STATE_PERSISTENT_UI

PERU

1

persistent系统进程,并正在执行UI操作

PROCESS_STATE_PERSISTENT

PER

0

persistent系统进程

PROCESS_STATE_UNKNOWN


-1

UNKNOWN进


进程组schedGroup

用来表示当前进程所在的进程调度组序列。

c3d39072e50f8fe3eaf5e89eb80caa2b.png

LMK机制

LMK 全称 Low Memory Killer。

在Android中,即使当用户退出应用程序后,应用进程也还会存在内存中,方便下次可以快速进入应用而不需要重新创建进程。这样带来的直接影响就是由于进程数量越来越多,系统内存会越来越少,这个时候就需要杀死一部分进程来缓解内存压力。至于哪些进程会被杀死,这个时候就需要用到Low Memory Killer机制来进行判定。

Android的Low Memory Killer基于Linux的OOM机制, 在Linux中,内存是以页面为单位分配的,当申请页面分配时如果内存不足会通过以下流程选择bad进程来杀掉从而释放内存。

alloc_pages -> out_of_memory() -> select_bad_process() -> badness()

LMK驱动层在用户空间指定了一组内存临界值及与之一一对应的一组oom_adj值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的oom_adj值大于或等于这个临界值对应的oom_adj值就会被杀掉。

使用命令:cat /sys/module/lowmemorykiller/parameters/minfree来查看某个手机的内存阈值。

18432,23040,27648,32256,36864,46080

注意这些数字的单位是page. 1 page = 4 kb.上面的六个数字对应的就是(MB): 72,90,108,126,144,180如数180代表内存低于180M时会清除优先级最低的空进程。

LMK还维护着一个管理系统中所有进程及其adj信息的双向链表数组,这个双向链表数组的每一个元素都是一个双向链表,一个数组元素中的双向链表里面的元素,都是adj相同的进程。在系统可用内存较低时,就会选择性杀死进程的策略。防止内存过低影响系统运行。LMK杀死进程的两个指标:1.oom_adj  2.内存占用大小。

而AMS通过四大组件的运行状态更新这些组件相关联的进程的oom_adj(包括adj,proc_state,schedule_group等值),AMS计算好每个进程的oom_adj,通过socket向lmkd服务发送请求,让lmkd去更新进程的优先级,lmkd收到请求后,会通过/proc文件系统去更新内核中的进程优先级。这样AMS就可以间接通过LMK实现对进程的动态管理。

LMKD与AMS交互图:

0c4af38e3f0dbdc493d21159f92f7cf0.jpeg

有了上面的基础,我们再来具体看下updateOomAdjLocked是如何进行动态更新adj的。

/   关键方法详解   /

前面说过,当AMS需要更新进程的优先级时,就会调用它的updateOomAdjLocked方法,这里只提取方法的updateOomAdjLocked的一些核心代码:

final void updateOomAdjLocked() {
    //省略。。。
    for (int i=N-1; i>=0; i--) {
        ProcessRecord app = mLruProcesses.get(i);
        if (!app.killedByAm && app.thread != null) {
            app.procStateChanged = false;
            computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
            //...
            applyOomAdjLocked(app, true, now, nowElapsed);
            //...
        }
    }

}

可以看到updateOomAdjLocked内部主要是对LUR进程列表中的每个进程调用computeOomAdjLocked以及applyOomAdjLocked处理。

核心方法分别是computeOomAdjLocked和applyOomAdjLocked。

  • computeOomAdjLocked:计算adj,返回计算后RawAdj值

  • applyOomAdjLocked:将计算后的adj写入lmkd,当需要杀掉目标进程则返回false;否则返回true。


computeOomAdjLocked

该方法会传入需要更新adj的进程描述符ProcessRecord,然后根据参数计算出当前进程甚至关联客户端进程的优先级,进程状态,进程组等信息。

由于这个方法较长,这里列出代码流程。

  • 通过mAdjSeq字段判断此轮更新是否已经计算过adj,是的话直接返回当前app.curRawAdj

  • 判断进程的客户端线程是否存在,不存在,则将adj设置为CACHED_APP_MAX_ADJ。

  • 判断是否是前台进程,如果不是,则根据TOP_APP,app.hasTopUi,activitiesSize,systemNoUi等参数计算adj。

  • 前台进程继续往下,初始化一些前台进程相关的默认值,后续再根据具体情况细化。

  • 根据是否为TOP_APP,是否有正在接受的动画,是否有正在执行的服务,是否有正在运行的Activity以及Activity的状态等对adj等参数赋值。

  • 对可见进程或者拥有可感知的前台服务或者后台服务等参数设置adj

  • 对后台进程设置优先级

  • 遍历在进程上运行的Service,根据Service的状态进一步更新adj等值。

  • 同Service。遍历进程上的ContentProvider,根据ContentProvider的状态进一步更新adj等值。

  • 根据cache进程运行状态,细分出cache进程还有empty进程。

  • 将计算好的adj等值赋值给对应的进程属性

代码就不列出来了,笔者根据代码,画了个流程图,方便大家查看,感兴趣的可以根据这个图自行去阅读源码。

c7c37743376818f153f32c2ec84dae38.png


applyOomAdjLocked

这个方法主要有三个作用:

  • 设置进程优先级:将前面计算好的curAdj传递给LMKD服务

  • 设置进程状态:将curProcState线程状态回传给应用进程ApplicationThread

  • 设置进程的调度策略:将schedGroup设置为对应的进程调度组。


设置进程优先级

在applyOomAdjLocked方法中比较重要的一段代码:

if (app.curAdj != app.setAdj) {
    ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
    if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
            "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
            + app.adjType);
    app.setAdj = app.curAdj;
    app.verifiedAdj = ProcessList.INVALID_ADJ;
}

继续看ProcessList的setOomAdj方法:

public static final void setOomAdj(int pid, int uid, int amt) {
    if (amt == UNKNOWN_ADJ)
        return;
    long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
    if ((now-start) > 250) {
        Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
                + " = " + amt);
    }
}
private static void writeLmkd(ByteBuffer buf) {

    for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
                if (openLmkdSocket() == false) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ie) {
                    }
                    continue;
                }
        }

        try {
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
        } catch (IOException ex) {
            Slog.w(TAG, "Error writing to lowmemorykiller socket");

            try {
                sLmkdSocket.close();
            } catch (IOException ex2) {
            }

            sLmkdSocket = null;
        }
    }
}

private static boolean openLmkdSocket() {
    try {
        sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
        sLmkdSocket.connect(
            new LocalSocketAddress("lmkd",
                    LocalSocketAddress.Namespace.RESERVED));
        sLmkdOutputStream = sLmkdSocket.getOutputStream();
    } catch (IOException ex) {
        Slog.w(TAG, "lowmemorykiller daemon socket open failed");
        sLmkdSocket = null;
        return false;
    }
    return true;
}

可以看到最终将adj,pid,uid写入名为lmkd的Socket通道中。之后的进程adj更新就是由lmkd来负责了。lmkd根据传入的参数,去Proc文件系统中更新进程优先级信息。

设置进程状态

代码片段:

if (app.repProcState != app.curProcState) {
    app.repProcState = app.curProcState;
    if (app.thread != null) {
        try {
            app.thread.setProcessState(app.repProcState);
        } catch (RemoteException e) {
        }
    }
}

这里调用了应用进程的ApplicationThread的setProcessState方法:

public void setProcessState(int state) {
    updateProcessState(state, true);
}

public void updateProcessState(int processState, boolean fromIpc) {
    synchronized (this) {
        if (mLastProcessState != processState) {
            mLastProcessState = processState;
            // Update Dalvik state based on ActivityManager.PROCESS_STATE_* constants.
            final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
            final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
            int dalvikProcessState = DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE;
            // TODO: Tune this since things like gmail sync are important background but not jank perceptible.
            if (processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                dalvikProcessState = DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE;
            }
            VMRuntime.getRuntime().updateProcessState(dalvikProcessState);
            if (false) {
                Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
                        + (fromIpc ? " (from ipc": ""));
            }
        }
    }
}

ApplicationThread的setProcessState方法:判断当前processState是否小余或等于ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND状态值,将其改为虚拟机运行时环境可以识别的DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE值。最终调用到了VMRuntime.getRuntime().updateProcessState(dalvikProcessState),将状态设置到AndroidRuntime运行时环境中。这里其实就是告诉ART运行时当前进程的可感知能力,用来切换虚拟机之间的GC算法,即到底是前台进程GC还是后台进程GC,前台GC算法效率高,但是会产生碎片,后台GC效率低,但是不会产生碎片。

设置进程调度策略
if (app.setSchedGroup != app.curSchedGroup) {
    int oldSchedGroup = app.setSchedGroup;
    app.setSchedGroup = app.curSchedGroup;

    switch (app.curSchedGroup) {
        case ProcessList.SCHED_GROUP_BACKGROUND:
            processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
            break;
        case ProcessList.SCHED_GROUP_TOP_APP:
        case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
            processGroup = THREAD_GROUP_TOP_APP;
            break;
        default:
            processGroup = THREAD_GROUP_DEFAULT;
            break;
    }
    long oldId = Binder.clearCallingIdentity();
    try {
        Process.setProcessGroup(app.pid, processGroup);    //1 
        if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
            // do nothing if we already switched to RT
            if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                mVrController.onTopProcChangedLocked(app);
                if (mUseFifoUiScheduling) {
                    //...
                } else {
                    // Boost priority for top app UI and render threads
                    setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);//2
                    if (app.renderThreadTid != 0) {
                        try {
                            setThreadPriority(app.renderThreadTid,
                                    TOP_APP_PRIORITY_BOOST);
                        } catch (IllegalArgumentException e) {
                            // thread died, ignore
                        }
                    }
                }
            }
        } 
    } catch (Exception e) {                  
    } 
}

这段代码主要做了两件事情:

  1. 调用Process.setProcessGroup(int pid, int group)去设置进程调度策略,原理就是利用linux的cgroup机制,根据进程状态将进程放入预先设定的cgroup分组中,分组中包含了对cpu使用率、cpuset、cpu调频等子资源的配置,以满足特定状态进程对系统资源的需求。

  2. 对schedGroup在某前台和后台之间切换时,调用setThreadPriority方法,切换主线程以及绘制线程的优先级,以提高用户的响应速度。

/   写在最后   /

这篇文章主要讲解了关于Android系统中常见的进程管理相关的知识点:其中对AMS中两个比较常见的方法,updateLruProcessLocked以及updateOomAdjLocked做了详细介绍。

作为应用开发可能我们平时用不到这些,但是在做一些性能优化,进程保活的操作时,这些储备知识却是必备的。一些高阶用法,需要你去了解更深层次的东西,而不仅局限于表面。

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

“一文读懂”系列:无处不在的WMS

Android 13启动速度提升,优化AMS加速启动

欢迎关注我的公众号

学习技术或投稿

ce0d2491ec78d4bb55716f9d5cb80c4d.png

ae4086d9f2b57464d541a9ad48833d2c.jpeg

长按上图,识别图中二维码即可关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值