“一文读懂”系列:AMS是如何动态管理进程的?
前言
前面一篇文章介绍了关于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是如何管理进程的。
目录
进程LRU列表动态更新
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段的开始位置。
大概模型如下:
每次优先级较高的进程,如带前台Activity的进程就会优先被放到尾部,所以进程优先级由头到尾
有了上面这个模型基础,下面我们从源码角度来看LRU列表就更轻松了。
关键方法详解
AMS使用updateLruProcessLocked方法对进程列表进行更新操作。
updateLruProcessLocked()方法在ActivityStack类中有3处可能被调用。
其中2次调用位置都处于ActivityStack类中的resumeTopActivityInnerLocked()方法:
1.pausing:通过home键返回或者back键退出一个Activity,此时进程中不止一个Activity、
2.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.判断该进程是否存在Activityfinal boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities|| app.treatLikeActivity;//2.判断进程是否存在Servicefinal boolean hasService = false; // not impl yet. app.services.size() > 0;//3.给LRU的序列号+1mLruSeq++;//4.如果hasActivity为trueif (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为mLruProcessServiceStartnextIndex = mLruProcessServiceStart;} else if (hasService) {//12.如果是有Service的进程,则将进程插入到hasService段的末尾,也就是hasActivity段的开头位置mLruProcesses.add(mLruProcessActivityStart, app);//设置nextIndex为mLruProcessServiceStartnextIndex = 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);}}
}
方法每个步骤已经在代码中做了说明,如果你仔细对照前面说的模型去看,一定能看懂。
这里额外说明下两点:
- 1.对于永久性的进程即设置了persistent标志的进程在列表中的位置不会更改。
- 2.mLruProcessActivityStart和mLruProcessServiceStart会随着列表的改变而改变,而不是固定的。
- 3.为了防止某些进程自己又没Activity,却可能创建很多Client端的进程,导致进程被滥用的情况。会将当前进程的子进程Client往重要性低处的列表排序,直到碰到不是当前进程的子进程Client端为止。
- 4.对于有Service和ContentProvider的情况,也需要将Service的进程和ContentProvider的进程对象也插入到LRU列表中。
看图说话:
好了,关于进程列表的动态更新就讲到这里。下面我们来讲解进程优先级动态调整。
进程优先级动态调整
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.testadb 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
用来表示当前进程所在的进程调度组序列。
schedGroup | 值 | 含义 |
---|---|---|
SCHED_GROUP_BACKGROUN | 0 | 后台进程组 |
SCHED_GROUP_RESTRICTED | 1 | |
SCHED_GROUP_DEFAULT | 2 | 前台进程组 |
SCHED_GROUP_TOP_APP | 3 | TOP进程组 |
SCHED_GROUP_TOP_APP_BOUND | 4 | TOP进程组 |
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交互图:
有了上面的基础,我们再来具体看下updateOomAdjLocked是如何进行动态更新adj的。
6.关键方法详解
前面说过,当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
- 1.computeOomAdjLocked:计算adj,返回计算后RawAdj值
- 2.applyOomAdjLocked:将计算后的adj写入lmkd,当需要杀掉目标进程则返回false;否则返回true。
computeOomAdjLocked:
该方法会传入需要更新adj的进程描述符ProcessRecord,然后根据参数计算出当前进程甚至关联客户端进程的优先级,进程状态,进程组等信息。
由于这个方法较长,这里列出代码流程。
- 1.通过mAdjSeq字段判断此轮更新是否已经计算过adj,是的话直接返回当前app.curRawAdj
- 2.判断进程的客户端线程是否存在,不存在,则:将adj设置为CACHED_APP_MAX_ADJ。
- 3.判断是否是前台进程,如果不是:则根据TOP_APP,app.hasTopUi,activitiesSize,systemNoUi等参数计算adj。
- 4.前台进程继续往下,初始化一些前台进程相关的默认值,后续再根据具体情况细化。
- 5.根据是否为TOP_APP,是否有正在接受的动画,是否有正在执行的服务,是否有正在运行的Activity以及Activity的状态等对adj等参数赋值。
- 6.对可见进程或者拥有可感知的前台服务或者后台服务等参数设置adj
- 7.对后台进程设置优先级
- 8.遍历在进程上运行的Service,根据Service的状态进一步更新adj等值。
- 9.同Service。**遍历进程上的ContentProvider,**根据ContentProvider的状态进一步更新adj等值。
- 10.根据cache进程运行状态,细分出cache进程还有empty进程。
- 11.将计算好的adj等值赋值给对应的进程属性
代码就不列出来了,笔者根据代码,画了个流程图,方便大家查看,感兴趣的可以根据这个图自行去阅读源码。
applyOomAdjLocked:
这个方法主要有三个作用:
- 1.设置进程优先级:将前面计算好的curAdj传递给LMKD服务
- 2.设置进程状态:将curProcState线程状态回传给应用进程ApplicationThread
- 3.设置进程的调度策略:将schedGroup设置为对应的进程调度组。
1.设置进程优先级
在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文件系统中更新进程优先级信息。
2.设置进程状态
代码片段:
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效率低,但是不会产生碎片。
具体可以参考下面这篇文章:
[ART运行时Foreground GC和Background GC切换过程分析](罗生阳)
3.设置进程调度策略
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 RTif (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {mVrController.onTopProcChangedLocked(app);if (mUseFifoUiScheduling) {//...} else {// Boost priority for top app UI and render threadssetThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);//2if (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做了详细介绍。
作为应用开发可能我们平时用不到这些,但是在做一些性能优化,进程保活
的操作时,这些储备知识却是必备的。一些高阶用法,需要你去了解更深层次的东西,而不仅局限于表面
笔者公众号:“小余的自习室” 欢迎关注哦
“一文读懂”系列:AMS是如何动态管理进程的?相关推荐
- 福利 | 一文读懂系列文章精选集发布啦!
大数据时代已经悄然到来,越来越多的人希望学习一定的数据思维和技能来武装自己,虽然各种介绍大数据技术的文章每天都扑面而来,但纷繁又零散的知识常常让我们不知该从何入手:同时,为了感谢和回馈读者朋友对数据派 ...
- “一文读懂“系列:Android中的硬件加速
浅谈 前几天有个朋友问我"了不了解关于手机硬件加速方面的知识?",嗯?其实我也想知道... 于是笔者就去网上搜罗了文章再结合自己对源码的理解,总结了这篇关于硬件加速的理解. 关于屏 ...
- “一文读懂”系列:无处不在的WMS
/ 今日科技快讯 / 近日,高德地图公布了基于北斗卫星导航系统的应用相关数据:截至2022年11月,高德地图调用北斗卫星日定位量已超过2100亿次,且在定位时北斗的调用率已超越了GPS等其他卫 ...
- 一文读懂 | Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
点击蓝字 关注我们 因公众号更改推送规则,请点"在看"并加"星标"第一时间获取精彩技术分享 来源于网络,侵删 栈是什么?栈有什么作用? 首先,栈 (stack) ...
- 一文读懂图卷积GCN
" 本文的内容包括图卷积的基础知识以及相关辅助理解的知识点,相信同学们看完后一定能平滑上手理解GCN!" 作者:苘郁蓁 来源:知乎专栏 郁蓁的机器学习笔记. 编辑:happyGir ...
- gcn 图卷积神经网络_复制一文读懂图卷积GCN
首发于郁蓁的机器学习笔记 写文章 一文读懂图卷积GCN 苘郁蓁 阿里巴巴 算法工程师 关注她 唯物链丶.小小将等 480 人赞同了该文章本文的内容包括图卷积的基础知识以及相关辅助理解的知识点,希 ...
- 原创 | 一文读懂机器学习中的shapley值方法
作者:贾恩东本文约2000字,建议阅读9分钟本文为你介绍更公平分配利益权重的一种算法--Shapley值方法. 本篇文章是数据派一文读懂系列的新年第一篇原创,在这里祝贺大家新年学业有新成就,生活有新气 ...
- 一文读懂:Kafka(分布式消息队列)的基础概念,教程
[提前声明] 文章由作者:张耀峰 结合自己生产中的使用经验整理,最终形成简单易懂的文章 写作不易,转载请注明,谢谢! 代码案例地址: ?https://github.com/Mydreamandrea ...
- 一文读懂FTP的主动模式和被动模式
文章目录 一文读懂FTP的主动模式和被动模式 一.引子 二.主动模式和被动模式结合TCP的三次握手详解 一文读懂FTP的主动模式和被动模式 一.引子 又来到了一文读懂系列文章,哈哈,昨天刚整理完FTP ...
最新文章
- 属于哪种虚拟化类型_经络淤堵,疲乏无力?有3种疲劳类型,你属于哪种,快来自测一下...
- 【蓝桥杯】历届试题 地宫取宝
- c# 访问hbase_大数据技术 windows下C#通过Thrift操作HBase
- This is a Blog Test
- 秋招面试我去了拼多多,直接被问JVMGC底层原理和算法,我吊打面试官
- 正则邮箱_自己写一个通用的邮箱正则表达式
- 作者:高富平(1963-),男,博士,华东政法大学二级教授、博士生指导小组负责人...
- 收藏 | 机器学习中需要了解的 5 种采样方法
- 礼橙专车、青菜拼车今日起改名啦!
- python匿名函数使用
- lisp 标注螺纹孔_螺纹的表示法和标注
- 第二部分 Automake的标准工程组织
- 软件开发中协议制定的注意事项
- HTML语法结构及规范
- Java牛客项目课_仿牛客网讨论区_已经看完的部分
- Win10使用系统自带命令diskpart格式化U盘
- Python | 小白的 Asyncio 教程
- 网页版短信平台发送教程【图文】
- 常用电脑硬件检测工具下载
- Maven中不能引入ojdbc解决方法:com.oracle:ojdbc6:jar:11.2.0.3
热门文章
- Python入门学习笔记第五章——if条件句~~~
- 量子领域又有新突破:量子态持续时间可超5秒
- 一男老师每日百词转载+连载(3)
- 港科夜闻|香港科技大学国际管理理学硕士(MIMT)课程连续第二年跻身全球管理教育联盟 (CEMS) 2021年度最佳学院前三甲...
- 第15课:ul,添加新闻信息列表ol,添加图书销售排行榜
- JAVA虚拟机栈的主要特点
- linux之进程观察命令:ps和top
- Rockchip RK3288型号获取
- c#:File.open()的使用
- 什么是HotSpot虚拟机