这一篇博客,我们将继续学习Activity的启动流程。
在启动Activity的过程:一中,我们的流程最终分析到AMS通过zygote启动Activity对应的进程,现在我们看看后续的过程如何进行。
关于zygote启动进程的流程,可以参考Android6.0 SystemServer进程。这篇文章中,分析了zygote如何启动SystemServer进程和普通进程。虽然分析的是Android M的代码,但与Android N的思路基本一致。
一、ActivityThread的main函数
通过zygote启动进程时,传入的className为android.app.ActivityThread。
因此,当zygote通过反射调用进程的main函数时,ActivityThread的main函数将被启动:
public static void main(String[] args) {
..................
//开始信息采样
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
//和调试有关
CloseGuard.setEnabled(false);
//得到当前进程的UserEnvironment
Environment.initForCurrentUser();
...............
// Make sure TrustedCertificateStore looks in the right place for CA certificates
// 确保进程能够得到CA证书的路径
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
//准备主线程的Looper
Looper.prepareMainLooper();
//创建当前进程的ActivityThread
ActivityThread thread = new ActivityThread();
//调用attach函数
thread.attach(false);
if (sMainThreadHandler == null) {
//保存进程对应的主线程Handler
sMainThreadHandler = thread.getHandler();
}
.........
//进入主线程的消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
从上述代码可以看出,ActivityThread的main函数最主要工作是:
1、创建出一个Looper,并将主线程加入到消息循环中。
2、创建出ActivityThread,并调用其attach函数。
在介绍AMS的启动过程时,我们就提到了ActivityThread。
当时从代码中,我们知道SystemServer进程,为了融入Android体系,调用createSystemContext函数。
在createSystemContext函数中,SystemServer进程创建了自己的ActivityThread,并调用了attach函数。
通过比对代码容易看出,SystemServer进程作为系统进程,attach参数为true,而普通进程传入的参数为false。
现在,我们重新看看ActivityThread的attach函数,这次侧重于普通进程的部分:
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
//JIT-Just in time
//JIT技术主要是对多次运行的代码进行编译,当再次调用时使用编译之后的机器码,而不是每次都解释,以节约时间
//JIT原理:
//每启动一个应用程序,都会相应地启动一个dalvik虚拟机,启动时会建立JIT线程,一直在后台运行。
//当某段代码被调用时,虚拟机会判断它是否需要编译成机器码,如果需要,就做一个标记。
//JIT线程在后台检测该标记,如果发现标记被设定,就把对应代码编译成机器码,并将其机器码地址及相关信息保存起来
//当进程下次执行到此段代码时,就会直接跳到机器码执行,而不再解释执行,从而提高运行速度
//这里开启JIT,应该是为了提高android绘制的速度
ensureJitEnabled();
}
});
//设置在DDMS中看到的进程名为"<pre-initialized>"
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", UserHandle.myUserId());
//设置RuntimeInit的mApplicationObject参数
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
//与AMS通信,调用其attachApplication接口
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// Watch for getting close to heap limit.
// 监控GC操作; 当进程内的一些Activity发生变化,同时内存占用量较大时
// 通知AMS释放一些Activity
BinderInternal.addGcWatcher(new Runnable() {
public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
//判断内存占用量是否过大
if (dalvikUsed > ((3*dalvikMax)/4)) {
...........
mSomeActivitiesChanged = false;
try {
//通知AMS释放一些Activity,以缓解内存紧张
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
});
} else {
.............
}
........
}
在分析Activity启动过程的第一部分时,我们提到过AMS创建一个应用进程后,会设置一个超时时间。
如果超过这个时间,应用进程还没有和AMS交互,AMS就认为该进程创建失败。
因此,应用进程启动后,需要尽快和AMS交互。
上述代码中,attachApplication就是应用进程与AMS交互的接口。
二、AMS的attachApplication函数
我们进入AMS看看attachApplication相关的流程。
public final void attachApplication(IApplicationThread thread) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
//进一步调用attachApplicationLocked函数
attachApplicationLocked(thread, callingPid);
Binder.restoreCallingIdentity(origId);
}
}
attachApplicationLocked函数比较长,分段来看一下。
1 Part-I
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
ProcessRecord app;
//根据pid查找对应的ProcessRecord对象
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
} else {
app = null;
}
//如果进程由AMS启动,则它在AMS中一定有对应的ProcessRecord
//此处app为null,则表示AMS没有该进程的记录,故需要kill掉此异常进程
if (app == null) {
................
if (pid > 0 && pid != MY_PID) {
//pid大于0且不是系统进程,则直接kill掉
//将调用android_util_Process.cpp中的android_os_Process_sendSignalQuiet函数
//最终通过kill函数杀死进程,kill函数要求pid > 0
Process.killProcessQuiet(pid);
} else {
try {
//pid < 0时,fork进程失败,因此仅上层完成清理工作即可
//调用ApplicationThread的scheduleExit函数
//应用进程将进行一些扫尾工作,例如结束消息循环,然后退出运行
thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
return false;
}
// If this application record is still attached to a previous
// process, clean it up now.
// 判断pid对应processRecord的IApplicationThread是否为null
// AMS创建ProcessRecord后,在attach之前,正常情况下IApplicationThread应该为null
// 特殊情况下:如果旧应用进程被杀死,底层对应的pid被释放,在通知到达AMS之前(AMS在下面的代码里注册了“讣告”接收对象),
// 用户又启动了一个新的进程,新进程刚好分配到旧进程的pid时
// 此处得到的processRecord可能就是旧进程的,于是app.thread可能不为null,因此需要作判断和处理
if (app.thread != null) {
handleAppDiedLocked(app, true, true);
}
...................
final String processName = app.processName;
try {
//创建一个“讣告”接收对象,注册到应用进程的ApplicationThread中
//当应用进程退出时,该对象的binderDied将被调用,这样AMS就能做相应的处理
//binderDied函数将在另一个线程中被调用,其内部也会调用handleAppDiedLocked函数
AppDeathRecipient adr = new AppDeathRecipient(
app, pid, thread);
thread.asBinder().linkToDeath(adr, 0);
app.deathRecipient = adr;
} catch (RemoteException e) {
app.resetPackageList(mProcessStats);
startProcessLocked(app, "link fail", processName);
return false;
}
设置app的一些变量,例如调度优先级和oom_adj相关的成员
.................
//启动成功,从消息队列中移除PROC_START_TIMEOUT_MSG
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
.................
至此,attachApplicationLocked的第一部分介绍完毕。
这部分代码的核心功能比较简单,其实就是:
1、判断进程的有效性,同时注册观察者监听进程的死亡信号。
2、设置pid对应的ProcessRecord对象的一些成员变量,例如和应用进程交互的IApplicationThread对象、进程调度的优先级等。
3、进程注册成功,AMS从消息队列中移除PROC_START_TIMEOUT_MSG。
2 Part-II
现在,我们看看attachApplicationLocked第二部分的代码:
..............
//AMS正常启动后,mProcessesReady就已经变为true了
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
//generateApplicationProvidersLocked将通过PKMS查询定义在进程中的ContentProvider,并将其保存在AMS的数据结构中
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
//这里应该是处理:加载ContentProvider时,启动进程的场景
//checkAppInLaunchingProvidersLocked主要将当前启动进程的ProcessRecord,和AMS中mLaunchingProviders的ProcessRecord进行比较
//当判断出该进程是由于启动ContentProvider而被加载的,那么就发送一个延迟消息(10s)
//通过这里可以看出,当由于加载ContentProvider启动进程时,在进程启动后,ContentProvider在10s内要完成发布
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
msg.obj = app;
mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
}
...........
try {
...........
//回调进程ApplicationThread的bindApplication接口
thread.bindApplication(..........);
//更新进程调度策略
updateLruProcessLocked(app, false, null);
..............
} catch () {
............
app.resetPackageList(mProcessStats);
app.unlinkDeathRecipient();
//这里的策略比较激进,当bindApplicaiton失败后,将直接重新启动这个进程
startProcessLocked(app, "bind fail", processName);
return false;
}
从代码来看,第二阶段最核心工作就是:
调用进程ApplicationThread的bindApplication函数,接下来我们分析一下该函数。
2.1 ApplicationThread的bindApplication函数
从之前的代码,我们知道应用进程由zygote fork得到,然后调用ActivityThread的main函数,进入到Java世界。
但是截至目前,该进程并没有融入到Android的体系中,因此仅能被称为一个Java进程,甚至连进程名也只是“敷衍”地定义为“pre-initialized”。
我们之前分析AMS启动过程时,介绍了SystemServer的createSystemContext函数。
在该函数中,SystemServer在自己的进程中,创建出Android运行环境,才摇身一变成为了Android进程。
同样,此处的bindApplication函数,就是在新进程中创建并初始化对应的Android运行环境。
现在,我们看看bindApplication函数的主要流程:
//参数较多,无需深究,重要的从代码流程中就能知道含义
public final void bindApplication(.......) {
//按照string-IBinder的方式,保存AMS传递过来的系统service的Binder接口
//这样进程与系统服务通信时,就不需要先通过SystemServer查询了
if (services != null) {
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
}
//内部发送H.SET_CORE_SETTINGS消息
//由handleSetCoreSettings进行处理,主要用于保存新的信息
setCoreSettings(coreSettings);
//用AppBindData对象保存参数对应的信息
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
..................
//发送消息
sendMessage(H.BIND_APPLICATION, data);
}
由以上代码可知,ApplicationThread的接口被AMS调用后,会将参数保存到AppBindData对象中,然后发送消息让ActivityThread的主线程处理。
由此可以看出,对应用进程而言,ApplicationThread只是与AMS通信的接口,实际的工作一般还是会交给ActivityThread来完成。
ActivityThread中处理该消息的实际函数为handleBindApplication,我们看看这个函数的内容。
2.2 handleBindApplication函数
private void handleBindApplication(AppBindData data) {
// Register the UI Thread as a sensitive thread to the runtime.
VMRuntime.registerSensitiveThread();
.................
//初始化性能统计对象
mProfiler = new Profiler();
if (data.initProfilerInfo != null) {
mProfiler.profileFile = data.initProfilerInfo