🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
在AMS中,和进程管理有关的函数只要有两个,分别是updateLruProcessLocked和updateOomAdjLocked。这两个函数的调用点有多处,本节以attachApplication为切入点,尝试对它们进行分析。 >[info]** 注意**:AMS一共定义了3个updateOomAdjLocked函数,此处将其归为一类。 先回顾一下attachApplication函数被调用的情况:AMS新创建一个应用进程,该进程启动后最重要的就是调用AMS的attachApplication。 * * * * * **提示**:不熟悉的读者可阅读6.3.3节的第5小节。 * * * * * 其相关代码如下: **ActivityManagerService.java::attachApplicationLocked** ~~~ //attachApplication主要工作由attachApplicationLocked完成,故直接分析它 private final booleanattachApplicationLocked(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和updateOomAdjLocked。 1. updateLruProcessLocked函数分析 根据前文所述,我们知道了系统中所有应用进程(同时包括SystemServer)的ProcessRecord信息都保存在mPidsSelfLocked成员中。除此之外,AMS还有一个成员变量mLruProcesses也用于保存ProcessRecord。mLruProcesses的类型虽然是ArrayList,但其内部成员却是按照ProcessRecord的lruWeight大小排序的。在运行过程中,AMS会根据lruWeight的变化调整mLruProcesses成员的位置。 就本例而言,刚连接(attach)上的这个应用进程的ProcessRecord需要通过updateLruProcessLocked函数加入mLruProcesses数组中。来看它的代码,如下所示: **ActivityManagerService.java::updateLruProcessLocked** ~~~ final void updateLruProcessLocked(ProcessRecordapp, boolean oomAdj, boolean updateActivityTime) { mLruSeq++;//每一次调整LRU列表,系统都会分配一个唯一的编号 updateLruProcessInternalLocked(app, oomAdj, updateActivityTime, 0); } ~~~ **ActivityManagerService.java::updateLruProcessInternalLocked** ~~~ private final voidupdateLruProcessInternalLocked(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; intskipTop = 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函数分析 (1) updateOomAdjLocked分析之一 分段来看updateOomAdjLocked函数。 **ActivityManagerService.java::updateOomAdjLocked()** ~~~ final void updateOomAdjLocked() { //在一般情况下,resumedAppLocked返回 mResumedActivity,即当前正处于前台的Activity finalActivityRecord TOP_ACT = resumedAppLocked(); //得到前台Activity所属进程的ProcessRecord信息 finalProcessRecord 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却令人头疼不已。如有清楚该内容的读者,不妨让分享一下 研究结果 */ intnumSlots = 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; intnumHidden = 0; int i =mLruProcesses.size(); intcurHiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ; //从mLruProcesses数组末端开始循环 while (i> 0) { i--; ProcessRecordapp = 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末端开始对每个进程执行另一个updateOomAdjLocked函数。关于这个函数的内容,我们放到下一节再讨论。 - 判断处于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, finalint 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结束 //Android4.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分析 **ActivityManagerService.java::updateOomAdjLocked** ~~~ private final boolean updateOomAdjLocked( ProcessRecordapp, int hiddenAdj, ProcessRecord TOP_APP, boolean doingAll) { //设置该app的hiddenAdj app.hiddenAdj = hiddenAdj; if(app.thread == null) return false; finalboolean wasKeeping = app.keeping; booleansuccess = 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); }...... } ...... } } returnsuccess; } ~~~ 上面的代码还算简单,主要完成两项工作: - 调用computeOomAdjLocked计算获得某个进程的oom_adj和调度策略。 - 调整进程的调度策略和oom_adj。 * * * * * **建议**:思考一个问题:为何AMS只设置进程的调度策略,而不设置进程的调度优先级? * * * * * 看来AMS调度算法的核心就在computeOomAdjLocked中。 4. computeOomAdjLocked分析 这段代码较长,其核心思想是综合考虑各种情况以计算进程的oom_adj和调度策略。建议读者阅读代码时聚焦到AMS关注的几个因素上。computeOomAdjLocked的代码如下: **ActivityManagerService.java::computeOomAdjLocked** ~~~ private final intcomputeOomAdjLocked(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 intactivitiesSize = app.activities.size(); //如果maxAdj小于FOREGROUND_APP_ADJ,基本上没什么工作可以做了。这类进程优先级相当高 if(app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) { ......//读者可自行阅读这块代码 return(app.curAdj=app.maxAdj); } finalboolean hadForegroundActivities = app.foregroundActivities; app.foregroundActivities = false; app.keeping = false; app.systemNoUi = false; int adj; intschedGroup; //如果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"; } elseif (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,我们以后面的 //mHomeProcess判断为例 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和schedGroupde的值 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; ...... returnapp.curRawAdj; } ~~~ computeOomAdjLocked的工作比较琐碎,实际上也谈不上什么算法,仅仅是简单地根据各种情况来设置几个值。随着系统的改进和完善,这部分代码变动的可能性比较大。 5. updateOomAdjLocked调用点统计 updateOomAdjLocked调用点很多,这里给出其中一个updateOomAdjLocked函数的调用点统计,如图6-23所示。 :-: ![](http://img.blog.csdn.net/20150803123523362?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图6-23 updateOomAdjLocked函数的调用点统计图 注意,图6-23统计的是updateOomAdjLocked(ProcessRecord)函数的调用点。从该图可知,此函数被调用的地方较多,这也说明AMS非常关注应用进程的状况。 >[warning] **提示**:笔者觉得,AMS中这部分代码不是特别高效,不知各位读者是否有同感,?