简介
ActivityManagerService(后续简称为ams)是Android系统框架层中的一个很重要的服务,负责Android四大组建的启动和管理,进程的创建和调度。接下来,咱们今天就来聊聊ams对进程的创建和调度。参考于Android P代码。
进程
Android系统将尽可能的长时间的保持应用进程,当Android系统内存不足的时候,为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存,Android可能会决定在某一时刻关闭某一进程,决定终止哪一个进程时,Android系统将权衡它们对用户的相对重要程度。例如:相对于托管可Activity的进程而言,它更有可能关闭托管屏幕上不再可见的Activity的进程。因此,是否终止某个进程的决定取决与该进程中所运行组件的状态。
当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。
如何创建启动进程
当我们首次启动Android的四大组建时,ams会判断系统中是否存在承载该组件的进程,如果不存在则通过startProcessLocked方法进行创建,以下就是启动四大组件,进程创建的时机(略去大部分代码,只留取了关键代码)
//启动Service时创建process
//frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting, boolean permissionsReviewRequired)
throws TransactionTooLargeException {
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
hostingType, r.name, false, isolated, false, r.latestCallerName)) == null) {
String msg = "Unable to launch app "
+ r.appInfo.packageName + "/"
+ r.appInfo.uid + " for service "
+ r.intent.getIntent() + ": process is bad";
Slog.w(TAG, msg);
bringDownServiceLocked(r);
return msg;
}
if (isolated) {
r.isolatedProc = app;
}
}
}
//启动Activity时,创建process
//frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
getLaunchTimeTracker().setLaunchTime(r);
if (app != null && app.thread != null) {
try {
if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
|| !"android".equals(r.info.packageName)) {
// Don't add this if it is a platform component that is marked
// to run in multiple processes, because this is actually
// part of the framework so doesn't make sense to track as a
// separate apk in the process.
app.addPackage(r.info.packageName, r.info.applicationInfo.longVersionCode,
mService.mProcessStats);
}
// When resumeTopActivityInnerLocked is called, app is not decided yet.
// Thus, set app member here.
app.bindAppByVisible = false;
app.prevAdjType = app.adjType;
realStartActivityLocked(r, app, andResume, checkConfig);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting activity "
+ r.intent.getComponent().flattenToShortString(), e);
}
// If a dead object exception was thrown -- fall through to
// restart the application.
}
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false, true,
(null != r.launchedFromPackage ? r.launchedFromPackage : "NA"));
}
//发送广播时,创建process
//frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
if ((r.curApp=mService.startProcessLocked(targetProcess,
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
"broadcast", r.curComponent,
(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false,
cause)) == null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
Slog.w(TAG, "Unable to launch app "
+ info.activityInfo.applicationInfo.packageName + "/"
+ receiverUid + " for broadcast "
+ r.intent + ": process is bad");
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
r.state = BroadcastRecord.IDLE;
return;
}
}
//contentprovider处理过程中,创建进程
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
String name, IBinder token, boolean stable, int userId) {
// Use existing process if already started
checkTime(startTime, "getContentProviderImpl: looking for process record");
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
if (proc != null && proc.thread != null && !proc.killed) {
if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER,
"Installing in existing process " + proc);
if (!proc.pubProviders.containsKey(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
proc.pubProviders.put(cpi.name, cpr);
try {
proc.thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
}
}
} else {
checkTime(startTime, "getContentProviderImpl: before start process");
String cause = r != null ? r.processName : "";
proc = startProcessLocked(cpi.processName,
cpr.appInfo, false, 0, "content provider",
new ComponentName(cpi.applicationInfo.packageName,
cpi.name), false, false, false, cause);
checkTime(startTime, "getContentProviderImpl: after start process");
if (proc == null) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
+ name + ": process is bad");
return null;
}
}
}
开发测试中,我们最直观的便是通过log,来确定它是否需要新创建进程,对应关键log如下
2018-08-21 11:45:08.113 1602 1669 I am_proc_start: [0,18743,10013,android.process.acore,content provider:com.google.android.apps.messaging,com.android.providers.contacts/.ContactsProvider2]
2018-08-21 11:45:08.113 1602 1669 I ActivityManager: Start proc 18743:android.process.acore/u0a13 for content provider com.android.providers.contacts/.ContactsProvider2
进程管理
ams对进程的管理主要体现于两个方面,一调整进程在mLruProcesses集合中的位置,二调整进程的oom值依据一些策略进行最基本的内存回收,前者相对简单,后者通过oom值来权衡其对于用户的相对重要程度,决定在某一个时刻关闭某一个进程。Android根据进程的重要程度,将其分为五个层次:
- 前台进程
用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,后者绑定到用户正在交互的 Activity
托管正在“前台”运行的 Service(服务已调用 startForeground())
托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver - 可见进程
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
托管绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。 - 服务进程
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。 - 后台进程
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。 - 空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
针对以上五层重要性层次结构,Android更以细分为ADJ阀值,配合ADJAndroid在内核空间有lowmemorykiller进行内存的回收。在framework层只负责计算更新进程的oom值,下面是我手上一台小米8的oom等级数据
OOM levels:
-900: SYSTEM_ADJ ( 73,728K)
-800: PERSISTENT_PROC_ADJ ( 73,728K)
-700: PERSISTENT_SERVICE_ADJ ( 73,728K)
0: FOREGROUND_APP_ADJ ( 73,728K)
100: VISIBLE_APP_ADJ ( 92,160K)
200: PERCEPTIBLE_APP_ADJ ( 110,592K)
300: BACKUP_APP_ADJ ( 129,024K)
400: HEAVY_WEIGHT_APP_ADJ ( 221,184K)
500: SERVICE_ADJ ( 221,184K)
600: HOME_APP_ADJ ( 221,184K)
700: PREVIOUS_APP_ADJ ( 221,184K)
800: SERVICE_B_ADJ ( 221,184K)
900: CACHED_APP_MIN_ADJ ( 221,184K)
906: CACHED_APP_MAX_ADJ ( 322,560K)
前台进程ADJ=0,可见进程ADJ=100,服务进程ADJ=500,后台进程ADJ>700,空进程900~906
ams通过updateLruProcessLocked更新进程在LRU集合中的位置,通过updateOomAdjLocked,computeOomAdjLocked,applyOomAdjLocked来更新设置进程对应的oom值。下面我们便详细分析这四个关键方法。
核心方法
updateLruProcessLocked,下面是该方法的调用,可以通过android-studio全局搜索获取: (以下对LRU集合的优先级描述特指某个进程在LRU集合中的位置,该进程的索引越大,优先级越大)
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
bindServiceLocked(IApplicationThread, IBinder, Intent, String, IServiceConnection, int, String, ...)
realStartServiceLocked(ServiceRecord, ProcessRecord, boolean)
removeConnectionLocked(ConnectionRecord, ProcessRecord, ActivityRecord)
setServiceForegroundInnerLocked(ServiceRecord, int, Notification, int)
unbindServiceLocked(IServiceConnection)
updateServiceClientActivitiesLocked(ProcessRecord, ConnectionRecord, boolean)
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
addAppLocked(ApplicationInfo, String, boolean, boolean, String)
attachApplicationLocked(IApplicationThread, int, int, long)
getContentProviderImpl(IApplicationThread, String, IBinder, boolean, int)
setSystemProcess()
updateLruProcessLocked(ProcessRecord, boolean, ProcessRecord)
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
destroyActivityLocked(ActivityRecord, boolean, String)
resumeTopActivityInnerLocked(ActivityRecord, ActivityOptions)
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
realStartActivityLocked(ActivityRecord, ProcessRecord, boolean, boolean)
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
realStartActivityLocked(ActivityRecord, ProcessRecord, boolean, boolean)
frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
processCurBroadcastLocked(BroadcastRecord, ProcessRecord, boolean)
通过搜索updateLruProcessLocked调用的地方我们可以发现,四大组件启动创建进程时都会更新mLruProcesses集合,四大组件状态改变时也会更新mLruProcesses集合,对于某些情况下创建的进程也会通过addAppLocked调用updateLruProcessLocked更新集合。下面我们就看看updateLruProcessLocked的具体实现
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
|| app.treatLikeActivity || app.recentTasks.size() > 0;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
//这里可以看出四大组件中持有activity的进程在lru集合中的位置比较靠前
//activityChange为false,并且该进程存在activity的时候,说明进程中没有activity状态改变,无需进行位置调整
// The process has activities, so we are only allowing activity-based adjustments
// to move it. It should be kept in the front of the list with other
// processes that have activities, and we don't want those to change their
// order except due to activity operations.
return;
}
mLruSeq++;//记录updateLruProcessLocked调用的次数
final long now = SystemClock.uptimeMillis();//记录进行位置调整时的开始时间
app.lastActivityTime = now;//记录进程最近一次进行调整的时间
// First a quick reject: if the app is already at the position we will
// put it, then there is nothing to do.
if (hasActivity) {
final int N = mLruProcesses.size();
if (N > 0 && mLruProcesses.get(N-1) == app) {
//有activity情况下,集合的最近使用的app就是当前app,无需进行调整直接返回
if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app);
return;
}
} else {
//lru集合我们可以分为两个部分:
//由启动activity创建的进程,mLruProcessActivityStart标记其索引
//由启动activity创建的进程,mLruProcessServiceStart标记其索引,比mLruProcessActivityStart小
if (mLruProcessServiceStart > 0
&& mLruProcesses.get(mLruProcessServiceStart-1) == app) {
//对于由服务启动的进程,同样如果已经在最集合最高位置即顶部,直接返回
if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app);
return;
}
}//上诉逻辑是无需调整的特殊情况
int lrui = mLruProcesses.lastIndexOf(app);//获取集合中最后一次出现该app的索引,如果不包含值为-1
if (app.persistent && lrui >= 0) {//对于系统中的persistent=true的进程,不做调整
// We don't care about the position of persistent processes, as long as
// they are in the list.
if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, persistent: " + app);
return;
}
if (lrui >= 0) {
//根据lrui对mLruProcessActivityStart,mLruProcessServiceStart进行调整,为什么呢?
//因为我们需要先从列表中移除当前需要的调整的app,所以对应的指引着ActivityStart,ServiceStart就要根据需要-1
if (lrui < mLruProcessActivityStart) {
mLruProcessActivityStart--;
}
if (lrui < mLruProcessServiceStart) {
mLruProcessServiceStart--;
}
int nextIndex;
if (hasActivity) {//该进程存在activity的情况,
final int N = mLruProcesses.size();
if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
&& mLruProcessActivityStart < (N - 1)) {
//根据源码的注释,我们看出该情况应该属于,当前的app中未包含activity,应该属于某个含有activity的服务端进程
//一般情况下,我们可以这么理解,客户端的进程重要程度应该大于服务端,因为客户端相对于用户而言更重要。
//我们把服务端进行放于客户端进程之后
// Process doesn't have activities, but has clients with
// activities... move it up, but one below the top (the top
// should always have a real activity).
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Adding to second-top of LRU activity list: " + app);
mLruProcesses.add(N - 1, app);
// To keep it from spamming the LRU list (by making a bunch of clients),
// we will push down any other entries owned by the app.
final int uid = app.info.uid;
for (int i = N - 2; i > mLruProcessActivityStart; i--) {
ProcessRecord subProc = mLruProcesses.get(i);
if (subProc.info.uid == uid) {
// We want to push this one down the list. If the process after
// it is for the same uid, however, don't do so, because we don't
// want them internally to be re-ordered.
if (mLruProcesses.get(i - 1).info.uid != uid) {
if (DEBUG_LRU) Slog.d(TAG_LRU,
"Pushing uid " + uid + " swapping at " + i + ": "
+ mLruProcesses.get(i) + " : " + mLruProcesses.get(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.
break;
}
}
} else {
// Process has activities, put it at the very tipsy-top.
if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app);
mLruProcesses.add(app);
}
nextIndex = mLruProcessServiceStart;
} else if (hasService) {//目前不进入该逻辑
// Process has services, put it at the top of the service list.
if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU service list: " + app);
mLruProcesses.add(mLruProcessActivityStart, app);
nextIndex = mLruProcessServiceStart;
mLruProcessActivityStart++;
} else {//不包含activity的情况
// Process not otherwise of interest, it goes to the top of the non-service area.
int index = mLruProcessServiceStart;
if (client != null) {//对于bindservice的情况,我们坚持一个原则,客户端的重要性大于服务端
// If there is a client, don't allow the process to be moved up higher
// in the list than that client.
int clientIndex = mLruProcesses.lastIndexOf(client);//获取客户端的索引
if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG_LRU, "Unknown client " + client
+ " when updating " + app);
if (clientIndex <= lrui) {//客户端在集合中的位置低于服务端
// Don't allow the client index restriction to push it down farther in the
// list than it already is.
clientIndex = lrui;//貌似作废了,修改客户端索引为当前进行的索引
}
//之前的操作要保证客户端的索引>=当前进程的索引
if (clientIndex >= 0 && index > clientIndex) {
index = clientIndex;//此处index就是要调整的位置,保证列表中客户端进程重要性大于服务端
}
}
if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding at " + index + " of LRU list: " + app);
//要知道当前需要调整的是服务端进程,在不存在客户端进程的情况下,直接使用
//mLruProcessServiceStart进行调整,存在的情况下,保证客户端的优先级大于服务端
mLruProcesses.add(index, app);
nextIndex = index-1;
mLruProcessActivityStart++;
mLruProcessServiceStart++;
}
// If the app is currently using a content provider or service,
// bump those processes as well.
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);
}
}
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
String what, Object obj, ProcessRecord srcApp) {
app.lastActivityTime = now;
if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
// Don't want to touch dependent processes that are hosting activities.
return index;//当前app存在activity的情况,不做调整,因为activity状态未改变
}
int lrui = mLruProcesses.lastIndexOf(app);
if (lrui < 0) {//lru集合中不存在该进程,并不做调整
Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ what + " " + obj + " from " + srcApp);
return index;
}
if (lrui >= index) {//在集合位置中的位置大于当前调整的index,也不做调整
// Don't want to cause this to move dependent processes *back* in the
// list as if they were less frequently used.
return index;
}
if (lrui >= mLruProcessActivityStart) {//同样持有activity,不做调整
// Don't want to touch dependent processes that are hosting activities.
return index;
}
mLruProcesses.remove(lrui);
if (index > 0) {
index--;//因为当前移除了一个进程,所以自减
}
if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ " in LRU list: " + app);
mLruProcesses.add(index, app);
return index;
}
从更新进程在lru集合中位置的算法中,我们大致可以总结出两个特点:1.持有activity的进程总是优先于持有service的进程,2.对于存在客户端服务端的进程,客户端优先于服务端。阅读源码我们发现往往updateLruProcessLocked和updateOomAdjLocked都是一起调用的,由于updateOomAdjLocked方法代码较多十分庞大,整体逻辑较为好理解,这里就不去陈列代码了,大致介绍一下他的原理(其中涉及很多设置进程相关的细节),后续重开一片文章详细分析。updateOomAdjLocked方法,主要是用来更新进程的oom值和依据一定的策略杀进程进行内存回收。前面我们所说ams对进程的管理大致可分为调整lru列表,更新进程oom。前者用来缓存进程,目的更多的偏向于Android的设计原则“尽可能的使进程活的更长”。但是缓存也是有上限的,以我使用的小米8为例:
CUR_MAX_CACHED_PROCESSES=32 //最大缓存的进程,此值可以在开发者选项中进行设置调整
CUR_MAX_EMPTY_PROCESSES=16 //最大持有的空进程
因此前者只负责调整lru集合各进程的位置,后者在更新oom的同时,也会根据我们所设置的上限和他们的oom值去动态的杀掉多余的进程(当具有相同的oom值时,先杀占用内存多的)。而更新进程oom值,目的也在于配合lowmemorykill在系统内存不足时合理的回收内存。updateOomAdjLocked整个执行流程可以简单描述为:计算进程的oom值,设置进程的oom值。