6.6.3 AMS进程管理函数分析

在AMS中,和进程管理有关的函数只要有两个,分别是updateLruProcessLocked和updateOomAdjLocked。这两个函数的调用点有多处,本节以attachApplication为切入点,尝试对它们进行分析。

注意 AMS一共定义了3个updateOomAdjLocked函数,此处将其归为一类。

先回顾一下attachApplication函数被调用的情况:AMS新创建一个应用进程,该进程启动后最重要的就是调用AMS的attachApplication。

提示 不熟悉的读者可阅读6.3.3节。

//attachApplication主要工作由attachApplicationLocked完成,故直接分析attachAppli-cationLocked,其相关代码如下:

[—>ActivityManagerService.java:attachApplicationLocked]


private final boolean attachApplicationLocked(IApplicationThread thread,

int pid){

ProcessRecord app;

//根据之前的介绍的内容,AMS在创建应用进程前已经将对应的ProcessRecord保存到

//mPidsSelfLocked中了

……//其他一些处理

//初始化ProcessRecord中的一些成员变量

app.thread=thread;

app.curAdj=app.setAdj=-100;

app.curSchedGroup=Process.THREAD_GROUP_DEFAULT;

app.setSchedGroup=Process.THREAD_GROUP_BG_NONINTERACTIVE;

app.forcingToForeground=null;

app.foregroundServices=false;

app.hasShownUi=false;

……

//调用应用进程的bindApplication,以初始化其内部的Android运行环境

thread.bindApplication(……);

//①调用updateLruProcessLocked函数

updateLruProcessLocked(app, false, true);

app.lastRequestedGc=app.lastLowMemory=SystemClock.uptimeMillis();

……//启动Activity等操作

//②didSomething为false,则调用updateOomAdjLocked函数

if(!didSomething){

updateOomAdjLocked();

}


在以上这段代码中调用了两个重要函数,分别是updateLruProcessLocked函数和update-Oom-AdjLocked函数。

1.updateLruProcessLocked函数分析

根据前文所述,我们知道了系统中所有应用进程(同时包括system_server)的Process-Record信息都保存在mPidsSelfLocked成员中。除此之外,AMS还有一个成员变量mLru-Processes也用于保存ProcessRecord。mLruProcesses的类型虽然是ArrayList,但其内部成员却是按照ProcessRecord的lruWeight大小排序的。在运行过程中,AMS会根据lruWeight的变化调整mLruProcesses成员的位置。

就本例而言,刚连接(attach)上的这个应用进程的ProcessRecord需要通过update-LruProcessLocked函数加入mLruProcesses数组中。下面来看updateLruprocessLocked函数的代码,如下所示:

[—>ActivityManagerService.java:updateLruProcessLocked]


final void updateLruProcessLocked(ProcessRecord app,

boolean oomAdj, boolean updateActivityTime){

mLruSeq++;//每一次调整LRU列表,系统都会分配一个唯一的编号

updateLruProcessInternalLocked(app, oomAdj, updateActivityTime,0);

}


[—>ActivityManagerService.java:updateLruProcessInternalLocked]


private final void updateLruProcessInternalLocked(ProcessRecord app,

boolean oomAdj, boolean updateActivityTime, int bestPos){

//获取app在mLruProcesses中的索引位置,对于本例而言,返回值lrui为-1

int lrui=mLruProcesses.indexOf(app);

//如果之前有记录,则先从数组中删掉,因为此处需要重新调整位置

if(lrui>=0)mLruProcesses.remove(lrui);

//获取mLruProcesses中数组索引的最大值,从0开始

int i=mLruProcesses.size()-1;

int skipTop=0;

app.lruSeq=mLruSeq;//将系统全局的lru调整编号赋给ProcessRecord的lruSeq

//更新lastActivityTime值,其实就是获取一个时间

if(updateActivityTime){

app.lastActivityTime=SystemClock.uptimeMillis();

}

if(app.activities.size()>0){

//如果该app含Activity,则lruWeight为当前时间

app.lruWeight=app.lastActivityTime;

}else if(app.pubProviders.size()>0){

/*

如果有发布的ContentProvider,则lruWeight要减去一个OFFSET。

对此的理解需结合CONTENT_APP_IDLE_OFFSET的定义。读者暂时把它

看做一个常数

*/

app.lruWeight=app.lastActivityTime-

ProcessList.CONTENT_APP_IDLE_OFFSET;

//设置skipTop。这个变量实际上没有用,放在此处让人很头疼

skipTop=ProcessList.MIN_HIDDEN_APPS;

}else{

app.lruWeight=app.lastActivityTime-

ProcessList.EMPTY_APP_IDLE_OFFSET;

skipTop=ProcessList.MIN_HIDDEN_APPS;

}

//从数组最后一个元素开始循环

while(i>=0){

ProcessRecord p=mLruProcesses.get(i);

//下面这个if语句没有任何意义,因为skipTop除了做自减操作外,不影响其他任何内容

if(skipTop>0&&p.setAdj>=ProcessList.HIDDEN_APP_MIN_ADJ){

skipTop—;

}

//将app调整到合适的位置

if(p.lruWeight<=app.lruWeight||i<bestPos){

mLruProcesses.add(i+1,app);

break;

}

i—;

}

//如果没有找到合适的位置,则把app加到队列头

if(i<0)mLruProcesses.add(0,app);

//如果该将app绑定到其他Service,则要对应调整Service所在进程的LRU

if(app.connections.size()>0){

for(ConnectionRecord cr:app.connections){

if(cr.binding!=null&&cr.binding.service!=null

&&cr.binding.service.app!=null

&&cr.binding.service.app.lruSeq!=mLruSeq){

updateLruProcessInternalLocked(cr.binding.service.app,

oomAdj, updateActivityTime, i+1);

}

}

}

//conProviders也是一种Provider,相关信息下一章再介绍

if(app.conProviders.size()>0){

for(ContentProviderRecord cpr:app.conProviders.keySet()){

……//对ContentProvider所在进程做类似的调整

}

}

//在本例中,oomAdj为false,故updateOomAdjLocked不会被调用

if(oomAdj)updateOomAdjLocked();//以后分析

}


由以上代码可知,updateLruProcessLocked的主要工作是根据app的lruWeight值调整它在数组中的位置。lruWeight值越大,其在数组中的位置就越靠后。如果该app和某些Service(仅考虑通过bindService建立关系的那些Service)或ContentProvider有交互关系,那么这些Service或ContentProvider所在的进程也需要调节lruWeight值。

下面介绍第二个重要函数updateOomAdjLocked。

提示 在以上代码中,skipTop变量完全没有实际作用,却给为阅读代码带来了很大干扰。

2.updateOomAdjLocked函数分析

分段来看updateOomAdjLocked函数。

(1)updateOomAdjLocked分析之一

这部分的代码如下:

[—>ActivityManagerService.java:updateOomAdjLocked]


final void updateOomAdjLocked(){

//在一般情况下,resumedAppLocked返回mResumedActivity,即当前正处于前台的Activity

final ActivityRecord TOP_ACT=resumedAppLocked();

//得到前台Activity所属进程的ProcessRecord信息

final ProcessRecord TOP_APP=TOP_ACT!=null?TOP_ACT.app:null;

mAdjSeq++;//oom_adj在进行调节时也会有唯一的序号

mNewNumServiceProcs=0;

/*

下面这几句代码的作用如下:

1.根据hidden adj划分级别,一共有9个级别(即numSlots值)

2.根据mLruProcesses的成员个数计算平均落在各个级别的进程数(即factor值)。但是这里

的魔数(magic number)4却令人头疼不已。如有清楚该内容的读者,不妨分享一下研究结果

*/

int numSlots=ProcessList.HIDDEN_APP_MAX_ADJ-

ProcessList.HIDDEN_APP_MIN_ADJ+1;

int factor=(mLruProcesses.size()-4)/numSlots;

if(factor<1)factor=1;

int step=0;

int numHidden=0;

int i=mLruProcesses.size();

int curHiddenAdj=ProcessList.HIDDEN_APP_MIN_ADJ;

//从mLruProcesses数组末端开始循环

while(i>0){

i—;

ProcessRecord app=mLruProcesses.get(i);

//①调用另外一个updateOomAdjLocked函数

updateOomAdjLocked(app, curHiddenAdj, TOP_APP, true);

//updateOomAdjLocked函数会更新app的curAdj

if(curHiddenAdj<ProcessList.HIDDEN_APP_MAX_ADJ

&&app.curAdj==curHiddenAdj){

/*

这段代码的目的其实很简单。即当某个adj级别的ProcessRecord处理个数超过均值后,

就跳到下一级别进行处理。注意,这段代码的结果会影响updateOomAdjLocked的第二个参数

*/

step++;

if(step>=factor){

step=0;

curHiddenAdj++;

}

}//if(curHiddenAdj<ProcessList.HIDDEN_APP_MAX_ADJ……)判断结束

//app.killedBackground初值为false

if(!app.killedBackground){

if(app.curAdj>=ProcessList.HIDDEN_APP_MIN_ADJ){

numHidden++;

//mProcessLimit初始值为ProcessList.MAX(值为15),

//可通过setProcessLimit函数对其进行修改

if(numHidden>mProcessLimit){

app.killedBackground=true;

//如果后台进程个数超过限制,则会杀死对应的后台进程

Process.killProcessQuiet(app.pid);

}

}

}//if(!app.killedBackground)判断结束

}//while循环结束


updateOomAdjLocked第一阶段的工作看起来很简单,但是其中也包含一些较难理解的内容,具体如下:

处理hidden adj,划分9个级别。

根据mLruProcesses中进程个数计算每个级别平均会存在多少进程。在这个计算过程中出现了一个魔数4令人极度费解。

利用一个循环从mLruProcesses末端开始对每个进程执行另一个updateOomAdj-Locked函数。关于这个函数的内容,我们放到下一节再讨论。

判断处于Hidden状态的进程数是否超过限制,如果超过限制,则会杀死一些进程。接着来看updateOomAdjLocked下一阶段的工作。

(2)updateOomAdjLocked分析之二

这部分的代码如下:

[—>ActivityManagerService.java:updateOomAdjLocked]


mNumServiceProcs=mNewNumServiceProcs;

//numHidden表示处于hidden状态的进程个数

//当Hidden进程个数小于7时(15/2的整型值),执行if分支

if(numHidden<=(ProcessList.MAX_HIDDEN_APPS/2)){

……

/*

我们不讨论这段缺乏文档及使用魔数的代码,但这里有个知识点要注意:

该知识点和Android 4.0新增接口ComponentCallbacks2有关,主要是通知应用进程进行

内存清理,ComponentCallbacks2接口定义了一个函数onTrimMemory(int level),

而四大组件除BroadcastReceiver外,均实现了该接口。系统定义了4个level以通知进程

做对应处理:

TRIM_MEMORY_UI_HIDDEN,提示进程当前不处于前台,故可释放一些UI资源

TRIM_MEMORY_BACKGROUND,表明该进程已加入LRU列表,此时进程可以对一些简单的资源

进行清理

TRIM_MEMORY_MODERATE,提示进程可以释放一些资源,这样其他进程的日子会好过些。

即所谓的“我为人人,人人为我”

TRIM_MEMORY_COMPLETE,该进程需尽可能释放一些资源,否则当内存不足时,它可能会被杀死

*/

}else{//假设hidden进程数超过7,

final int N=mLruProcesses.size();

for(i=0;i<N;i++){

ProcessRecord app=mLruProcesses.get(i);

if((app.curAdj>ProcessList.VISIBLE_APP_ADJ||app.systemNoUi)

&&app.pendingUiClean){

if(app.trimMemoryLevel<

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN

&&app.thread!=null){

try{//调用应用进程ApplicationThread的scheduleTrimMemory函数

app.thread.scheduleTrimMemory(

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);

}……

}//if(app.trimMemoryLevel……)判断结束

app.trimMemoryLevel=

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;

app.pendingUiClean=false;

}else{

app.trimMemoryLevel=0;

}

}//for循环结束

}//else结束

//Android 4.0中设置有一个开发人员选项,其中有一项用于控制是否销毁后台的Activity

//读者可自行研究destroyActivitiesLocked函数

if(mAlwaysFinishActivities)

mMainStack.destroyActivitiesLocked(null, false,"always-finish");

}


通过上述代码,可获得两个信息:

Android 4. 0增加了新的接口类ComponentCallbacks2,其中只定义了一个函数onTrimMemory。从以上描述中可知,它主要通知应用进程进行一定的内存释放。

Android 4. 0 Settings新增了一个开放人员选项,通过它可控制AMS对后台Activity的操作。

这里和读者探讨一下ComponentCallbacks2接口的意义。此接口的目的是通知应用程序根据情况做一些内存释放,但笔者觉得,这种设计方案的优劣尚有待考证,这主要是出于以下几种考虑:

不是所有应用程序都会实现该函数。原因有很多,主要原因是,该接口只是SDK 14才有的,之前的版本没有这个接口。另外,应用程序都会尽可能抢占资源(在不超过允许范围内)以保证运行速度,不应该考虑其他程序的事情。

无法区分在不同的level下到底要释放什么样的内存。代码中的注释也是含糊其辞。到底什么样的资源可以在TRIM_MEMORY_BACKGROUND级别下释放,什么样的资源不可以在TRIM_MEMORY_BACKGROUND级别下释放?

既然系统加了这些接口,读者不妨参考源码中的使用案例来开发自己的程序。

建议 真诚希望Google能给出一个明确的文档,说明这几个函数该怎么使用。

接下来分析在以上代码中出现的针对每个ProcessRecord都调用的updateOomAdjLocked函数。

3.第二个updateOomAdjLocked分析[1]

这部分的代码如下:

[—>ActivityManagerService.java:updateOomAdjLocked]


private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj,

ProcessRecord TOP_APP, boolean doingAll){

//设置该app的hiddenAdj

app.hiddenAdj=hiddenAdj;

if(app.thread==null)return false;

final boolean wasKeeping=app.keeping;

boolean success=true;

//下面这个函数的调用极其关键。从名字上看,它会计算该进程的oom_adj及调度策略

computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll);

if(app.curRawAdj!=app.setRawAdj){

if(wasKeeping&&!app.keeping){

……//统计电量

app.lastCpuTime=app.curCpuTime;

}

app.setRawAdj=app.curRawAdj;

}

//如果新旧oom_adj不同,则重新设置该进程的oom_adj

if(app.curAdj!=app.setAdj){

if(Process.setOomAdj(app.pid, app.curAdj))//设置该进程的oom_adj

app.setAdj=app.curAdj;

……

}

//如果新旧调度策略不同,则需重新设置该进程的调度策略

if(app.setSchedGroup!=app.curSchedGroup){

app.setSchedGroup=app.curSchedGroup;

//waitingToKill是一个字符串,用于描述杀掉该进程的原因

if(app.waitingToKill!=null&&

app.setSchedGroup==Process.THREAD_GROUP_BG_NONINTERACTIVE){

Process.killProcessQuiet(app.pid);//

success=false;

}else{

if(true){//强制执行if分支

long oldId=Binder.clearCallingIdentity();

try{//设置进程调度策略

Process.setProcessGroup(app.pid, app.curSchedGroup);

}……

}……

}

}

return success;

}


上面的代码还算简单,主要完成两项工作:

调用computeOomAdjLocked计算获得某个进程的oom_adj和调度策略。

调整进程的调度策略和oom_adj。

建议 思考一个问题:为何AMS只设置进程的调度策略,而不设置进程的调度优先级?

看来AMS调度算法的核心就在computeOomAdjLocked中。

4.computeOomAdjLocked分析

这段代码较长,其核心思想是综合考虑各种情况以计算进程的oom_adj和调度策略。建议读者阅读代码时聚焦到AMS关注的几个因素上。computeOomAdjLocked的代码如下:

[—>ActivityManagerService.java:computeOomAdjLocked]


private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj,

ProcessRecord TOP_APP, boolean recursed, boolean doingAll){

……

app.adjTypeCode=ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;

app.adjSource=null;

app.adjTarget=null;

app.empty=false;

app.hidden=false;

//该应用进程包含Activity的个数

final int activitiesSize=app.activities.size();

//如果maxAdj小于FOREGROUND_APP_ADJ,基本上就没什么工作可以做了。这类进程优先级相当高

if(app.maxAdj<=ProcessList.FOREGROUND_APP_ADJ){

……//读者可自行阅读这块代码

return(app.curAdj=app.maxAdj);

}

final boolean hadForegroundActivities=app.foregroundActivities;

app.foregroundActivities=false;

app.keeping=false;

app.systemNoUi=false;

int adj;

int schedGroup;

//如果app为前台Activity所在的那个应用进程

if(app==TOP_APP){

adj=ProcessList.FOREGROUND_APP_ADJ;

schedGroup=Process.THREAD_GROUP_DEFAULT;

app.adjType="top-activity";

app.foregroundActivities=true;

}else if(app.instrumentationClass!=null){

……//略过instrumentationClass不为null的情况

}else if(app.curReceiver!=null||

(mPendingBroadcast!=null&&mPendingBroadcast.curApp==app)){

//此情况对应正在执行onReceive函数的广播接收者所在进程,它的优先级也很高

adj=ProcessList.FOREGROUND_APP_ADJ;

schedGroup=Process.THREAD_GROUP_DEFAULT;

app.adjType="broadcast";

}else if(app.executingServices.size()>0){

//正在执行Service生命周期函数的进程

adj=ProcessList.FOREGROUND_APP_ADJ;

schedGroup=Process.THREAD_GROUP_DEFAULT;

app.adjType="exec-service";

}else if(activitiesSize>0){

adj=hiddenAdj;

schedGroup=Process.THREAD_GROUP_BG_NONINTERACTIVE;

app.hidden=true;

app.adjType="bg-activities";

}else{//不含任何组件的进程,即所谓的Empty进程

adj=hiddenAdj;

schedGroup=Process.THREAD_GROUP_BG_NONINTERACTIVE;

app.hidden=true;

app.empty=true;

app.adjType="bg-empty";

}

//下面几段代码将根据情况重新调整前面计算得到的adj和schedGroup,请读者注意下面代码中对Home

进程的特殊处理

if(!app.foregroundActivities&&activitiesSize>0){

//对无前台Activity所在进程的处理

}

if(adj>ProcessList.PERCEPTIBLE_APP_ADJ){

……

}

//如果前面计算出来的adj大于HOME_APP_ADJ,并且该进程又是Home进程,则需要重新调整

if(adj>ProcessList.HOME_APP_ADJ&&app==mHomeProcess){

//重新调整adj和schedGroup的值

adj=ProcessList.HOME_APP_ADJ;

schedGroup=Process.THREAD_GROUP_BG_NONINTERACTIVE;

app.hidden=false;

app.adjType="home";//描述调节adj的原因

}

if(adj>ProcessList.PREVIOUS_APP_ADJ&&app==mPreviousProcess

&&app.activities.size()>0){

……

}

app.adjSeq=mAdjSeq;

app.curRawAdj=adj;

……

//下面这几段代码处理那些进程中含有Service、ContentProvider组件情况下的adj调节

if(app.services.size()!=0&&(adj>ProcessList.FOREGROUND_APP_ADJ

||schedGroup==Process.THREAD_GROUP_BG_NONINTERACTIVE)){

}

if(s.connections.size()>0&&(adj>ProcessList.FOREGROUND_APP_ADJ

||schedGroup==Process.THREAD_GROUP_BG_NONINTERACTIVE)){

}

if(app.pubProviders.size()!=0&&(adj>ProcessList.FOREGROUND_APP_ADJ

||schedGroup==Process.THREAD_GROUP_BG_NONINTERACTIVE)){

……

}

//终于计算完毕

app.curRawAdj=adj;

if(adj>app.maxAdj){

adj=app.maxAdj;

if(app.maxAdj<=ProcessList.PERCEPTIBLE_APP_ADJ)

schedGroup=Process.THREAD_GROUP_DEFAULT;

}

if(adj<ProcessList.HIDDEN_APP_MIN_ADJ)

app.keeping=true;

……

app.curAdj=adj;

app.curSchedGroup=schedGroup;

……

return app.curRawAdj;

}


computeOomAdjLocked的工作比较琐碎,实际上也谈不上什么算法,仅仅是简单地根据各种情况来设置几个值。随着系统的改进和完善,这部分代码变动的可能性比较大。

5.updateOomAdjLocked调用点统计

updateOomAdjLocked调用点很多,这里给出其中一个updateOomAdjLocked(ProcessRecord)函数的调用点统计,如图6-23所示。

6.6.3 AMS进程管理函数分析 - 图1

图 6-23 updateOomAdjLocked(ProcessRecord)函数的调用点统计图

从图6-23中可知,此函数被调用的地方较多,这也说明AMS非常关注应用进程的状况。

提示 笔者觉得,AMS中这部分代码不是特别高效,不知各位读者是否有同感?

[1]有两个名为updateOomAdjLocked的函数,这里分析的是第二个。