Android中的AMS的职责是比较多的,其中一个比较重要的职责就是app进程的管理,比如我们调用startActivity方法启动一个activity的时候,可能对应的那个进程没有启动,因此需要启动那个进程,而且对于这个进程还要有一些必要的管理过程,比如将它放到LRU(least recently used)列表中去等。本文就AMS的进程管理基本逻辑和过程做一个简要的分析,以帮助大家弄清楚AMS的进程管理。
Android的应用进程是什么?
这里我们讲的是android的应用进程,并不是native层的进程,这里需要知道。在android中,进程(Process)的概念是被弱化的,我们知道在传统的系统中进程是静态程序执行的载体,程序的代码段,数据等信息全部都是在进程的管理之中的,而且进程中的多个组件都是在一个进程中的,并且他们的生命周期都是和进程息息相关的。但是在android中,进程只不过是一系列运行组件的容器而已,这些组件可以运行在不同的进程之中,也就是说某个app中的某个组件可以运行在本地进程之中,也可以运行在另外一个进程中;说白了,也就是说android的app中的组件只是一个静态的程序级别的概念,真正运行的时候这些组件可能“各立山头”,互补相关;要做到这一点也很容易,只要在AndroidManifest中的相应组件指定android:process属性就可以了。同时,不同的app中的不同组件也可以运行在一个进程中,可以通过在AndroidManifest指定相应的进程名称就可以了。
虽然在android的开发中,不再强调进程的概念,但是进程毕竟是实际存在于android系统中,只是它和我们认识到的传统进程不太一样,所以我们的AMS还是需要对进程进行管理的。AMS对于进程的管理主要体现在两个方面:第一是动态调整进程再mLruProcess中的位置,第二就是调整进程的oom_adj的值,这两项都和系统的内存自动回收有关系,当系统的内存不足时,系统主要根据oom_adj的值来选择杀死一些进程以释放内存,这个值越大表示进程越容易被杀死。
AMS启动进程流程
在Android ActivityManagerService(AMS)的启动分析一文中我们提到了AMS是调用addAppLocked方法来启动一个进程的,这个方法实现如下:
final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
String abiOverride) {
ProcessRecord app;
// isolated表示当前需要启动的app是不是一个独立的进程,如果是独立的话那就重新
// 创建一个ProcessRecord,如果不是的话那就从正在运行的进程列表中找。
if (!isolated) {
app = getProcessRecordLocked(info.processName, info.uid, true);
} else {
app = null;
}
// 这是一个独立的进程或者在正在运行的列表中没有找到相应的记录
if (app == null) {
// 新建一个ProcessRecord对象
app = newProcessRecordLocked(info, null, isolated, 0);
// 更新lru列表和oom_adj值,下面我们会重点分析这里
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
// This package really, really can not be stopped.
// 这里将当前的app包设置为启动状态,这样这个app就可以接受系统的隐式intent了
try {
AppGlobals.getPackageManager().setPackageStoppedState(
info.packageName, false, UserHandle.getUserId(app.uid));
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ info.packageName + ": " + e);
}
// 如果app中带有persistent标记的话,那个对新建的app对象做相应的标记
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
app.persistent = true;
// 这个值下面我们分析oom_adj中会说明
app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
}
// 如果app中的thread(就是主线程)为空的话
if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
// 实际启动app进程
startProcessLocked(app, "added application", app.processName, abiOverride,
null /* entryPoint */, null /* entryPointArgs */);
}
return app;
}
addAppLocked方法会根据参数isolated来决定这个进程是不是一个独立的进程,如果是那就创建一个新的ProcessRecord对象,如果不是的话,那就调用getProcessRecordLocked方法在当前运行的进程列表中查找进程,我们看下这个方法的定义:
getProcessRecordLocked@ActivityManagerService.java
final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
// 如果是一个系统级别的UID,也就是说这个app是由于system用户启动的进程
if (uid == Process.SYSTEM_UID) {
// The system gets to run in any process. If there are multiple
// processes with the same uid, just pick the first (this
// should never happen).
// 上面英文注释说的很明白,如果有多个system uid的进程的话,那就取第一个
SparseArray<ProcessRecord> procs = mProcessNames.getMap().get(processName);
if (procs == null) return null;
final int procCount = procs.size();
for (int i = 0; i < procCount; i++) {
final int procUid = procs.keyAt(i);
// 如果这是一个合理的app进程,或者uid不是要求的uid的话,那就跳过。
// 原因下面的英文注释说的很清楚。
if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
// Don't use an app process or different user process for system component.
continue;
}
return procs.valueAt(i);
}
}
// 如果不是系统uid的话,就会执行到这里。首先从mProcessNames查找正在运行的进程记录
ProcessRecord proc = mProcessNames.get(processName, uid);
// 上面这个分支永远不会执行,这是一个用户测试的分支。
if (false && proc != null && !keepIfLarge
&& proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
&& proc.lastCachedPss >= 4000) {
// Turn this condition on to cause killing to happen regularly, for testing.
if (proc.baseProcessTracker != null) {
proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
}
proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
// 一般会走这个分支,但是由于我们的addAppLocked传递进来的keepIfLarge是true,因此这个分支也不会走。
} else if (proc != null && !keepIfLarge
&& mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
&& proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
if (DEBUG_PSS) Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + proc.lastCachedPss);
if (proc.lastCachedPss >= mProcessList.getCachedRestoreThresholdKb()) {
if (proc.baseProcessTracker != null) {
proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
}
proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
}
}
// 直接就将找到的记录对象返回。
return proc;
}
getProcessRecordLocked方法的逻辑是比较简单的,他分为两部分:uid是system uid和uid是普通uid两种情况。具体的逻辑上面的注释已经解释,这里就不赘述。需要补充的是,上面如果我们的uid是system uid的话那么会使用UserHandle.isApp来判断这是不是一个app进程,他的实现如下:
isApp@UserHandle.java
/** @hide */
public static boolean isApp(int uid) {
if (uid > 0) {
final int appId = getAppId(uid);
return appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID;
} else {
return false;
}
}
这里的逻辑很简单,主要就是根据uid获得app的id,然后如果app id是在Process.FIRST_APPLICATION_UID和Process.LAST_APPLICATION_UID之间(这是正常app id应该处于的范围)的话,那就是一个合理的app进程。
现在我们回到addAppLocked方法,我们刚才分析了如果不是独立进程的情况的逻辑,总结来说就是通过getProcessRecordLocked查找当前系统中正在运行的进程记录,并且把这个记录保存下来。现在我们看一下如果请求的是一个独立的进程的话(这也是最常见的情形),处理的方式是什么样的:
addAppLocked@ActivityManagerService.java
final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
String abiOverride) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(info.processName, info.uid, true);
} else {
app = null;
}
if (app == null) {
app = newProcessRecordLocked(info, null, isolated, 0);
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
我们看到,如果是一个独立的进程的话,那么首先app肯定就直接赋值为null,接下来我们会调用newProcessRecordLocked新建一个ProcessRecord对象,这个方法的具体处理因为和我们AMS的进程管理不是特别相关,我们就不分析了,感兴趣的读者可以自行分析。新建了一个ProcessRecord对象之后的操作就是最重要的操作了:
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
这两步操作设计AMS管理进程的核心工作,我们稍后详细分析,我们先接下来的逻辑,接下来的逻辑中最重要的就是调用startProcessLocked方法实际启动一个进程:
startProcessLocked(app, "added application", app.processName, abiOverride,
null /* entryPoint */, null /* entryPointArgs */);
在AMS中startProcessLocked方法实现了多态,但是根据这里的参数我们可以确定我们调用的方法是哪一个。startProcessLocked方法比较长,这里我们分部来分析:
startProcessLocked@ActivityManagerService.java
private final void startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
long startTime = SystemClock.elapsedRealtime();
if (app.pid > 0 && app.pid != MY_PID) {
checkTime(startTime, "startProcess: removing from pids map");
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.remove(app.pid);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
}
checkTime(startTime, "startProcess: done removing from pids map");
app.setPid(0);
}
首先需要记录一下app的启动时间,这个数据是给checkTime方法使用的,checkTime方法的定义如下:
checkTime@ActivityManagerService.java
private void checkTime(long startTime, String where) {
long now = SystemClock.elapsedRealtime();
if ((now-startTime) > 1000) {
// If we are taking more than a second, log about it.
Slog.w(TAG, "Slow operation: " + (now-startTime) + "ms so far, now at " + where);
}
}
我们看到,checkTime的逻辑很简单,主要就是看看当前的时间和传递进来的时间是不是相差1000ms,如果相差1s的话那就需要打下log,这部分的逻辑就是这样。我们继续startProcessLocked的分析:
if (app.pid > 0 && app.pid != MY_PID) {
checkTime(startTime, "startProcess: removing from pids map");
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.remove(app.pid);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
}
checkTime(startTime, "startProcess: done removing from pids map");
app.setPid(0);
}
再获得启动开始时间之后,当app的pid大于0并且pid不是本进程PID的话,就要把当前pid的进程从mPidsSelfLocked列表中移除,防止重复,因为后面我们还要加入;然后就是移除PROC_START_TIMEOUT_MSG消息,这个消息是AMS用来控制app启动时间的,如果启动超时了就发出效果消息,下面我们会设置这个消息,现在需要取消之前设置的消息,防止干扰。
接下来的逻辑:
mProcessesOnHold.remove(app);
这里是将app进程从mProcessesOnHold列表中清除,这个列表是什么呢?这个列表是系统中在AMS没有启动之前请求启动的app,这些app当时没有启动,被hold了,当AMS启动完成的时候需要将他们启动;现在如果我们启动的app就在这个列表中的, 那么自然需要将它移除,防止重复启动。我们可以从mProcessesOnHold的定义的地方看到这一点:
/**
* List of records for processes that someone had tried to start before the
* system was ready. We don't start them at that point, but ensure they
* are started by the time booting is complete.
*/
final ArrayList<ProcessRecord> mProcessesOnHold = new ArrayList<ProcessRecord>();
接下来就要更新cpu使用统计信息:
updateCpuStats();
cpu统计使用信息也是AMS的一个重要的任务,这个任务就是通过启动一个独立的线程去获得cpu的使用情况,我们看一下updateCpuStats的实现:
void updateCpuStats() {
final long now = SystemClock.uptimeMillis();
if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) {
return;
}
if (mProcessCpuMutexFree.compareAndSet(true, false)) {
synchronized (mProcessCpuThread) {
mProcessCpuThread.notify();
}
}
}
我们发现它的逻辑很简单,首先是判断当前时间是不是和上次检查时间相差MONITOR_CPU_MIN_TIME(5s)以上,如果是就继续,如果不是就返回,因为不能过于平凡做这件事情,它比较耗电。工作的方式就是唤醒mProcessCpuThread去采集cpu的信息。这个线程的实现我们这里先不分析,这块和我们的进程管理相关不是很密切,我后面的文章会详细分析这块的内容。在startProcessLocked的接下来的逻辑中,主要就是app启动的一些参数设置,条件检查等操作,这里我们直接略过,我们直接看实际进程启动的部分:
// Start the process. It will either succeed and return a result containing
// the PID of the new process, or else throw a RuntimeException.
boolean isActivityProcess = (entryPoint == null);
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
app.processName);