前言
本文主要分析当用户点击 Launcher 上的应用图标,到启动应用程序首个页面的流程。本文基于 Android 9.0 ,结论由调试编译后模拟器得出,aosp 分支 android-9.0.0_r1。
分析源码前,先提出几个问题,带着疑问去看代码
- 应用启动时显示的白屏究竟是什么,如何禁止显示?
- 应用程序进程是如何创建的,创建完做了什么?
- 系统管理 Activity 栈涉及的数据结构有哪些?
- 应用崩溃后,为什么会打印导致崩溃的错误日志?
- 调试应用时,为什么会出现等待提示框,这个框是如何弹出的?
- 在系统设置中设置代理,为什么应用就会走这个代理?
- 免初始化的 SDK 究竟是如何做到的?
- 为什么应用不会退出,不应该方法执行完就退出?
- 应用在后台时点击应用图标,为什么不会再次启动首页?
- 为什么 onCreate、onStart、onResume 都获取不到 View 的宽高?
整体分为以下两个流程
- 用户点击 Launcher 图标到应用程序进行创建前
- 应用程序进程创建后到首个 Activity 展示出来
源码分析
应用程序进程创建前
桌面对应 Launcher3 这个应用程序,其源码位于 /packages/apps/Launcher3。当点击桌面上的图标时会触发ItemClickHandler#onClick。
// ItemClickHandler.java |
接下来就是标准的启动 Activity 流程,不过上述代码的 Intent 的是在哪创建的呢?还得回到 AppInfo 的构造方法。
// AppInfo.java |
这里设置了 action 为 android.intent.action.MAIN、category 为 android.intent.category.LAUNCHER,同时指定了要启动的 component(首个 Activity 的包名其全类名),*这也就解释了为什么应用程序的首个 Activity 必须要设置这个 action 以及 category。***
同时添加了 Intent.FLAG_ACTIVITY_NEW_TASK 这个 Flag,表示开启的 Activity 在一个新的任务栈中运行,不要和 Launcher 运行在同一个任务栈中,接下来分析 startActivity 的具体流程。
// Activity.java |
通过 Instrumentation 去启动 Activity,这个类主要用于监控应用程序和系统的交互,Application、Activity 实例创建以及 Activity 生命周期调用都是通过该类完成的,VirtualApk 也是通过 Hook 该类实现插件化的。
// Instrumentation.java |
IApplicationThread 是一个 aidl 文件,ActivityThread.ApplicationThread 继承了 IApplication.Stub,客户端将该对象传递给系统服务进程,系统服务进程就可以通过该 BinderProxy 对象跨进程调用应用程序对应方法。
IActivityManager 也是一个 aidl 文件,AMS 继承了 IActivityManager.Stub ,在系统服务进程启动的时候会向 ServiceManager 注册服务,包括 AMS,客户端通过 ActivityManager.gerService 获取到一个 AMS 的 BinderProxy 对象,从而跨进程调用 AMS 对应方法。
注:这里已经说明了应用程序进程与系统服务进程(AMS)的通信方式,IApplicationThread 用于系统服务进程与应用程序进程通信(注意 AIDL 中标明了 oneway 表明为非阻塞式调用,这也合理,不然应用程序就能把服务进程卡死),IActivityManager 用于应用程序进程与系统服务进程通信(非 oneway 调用表明为阻塞式调用)。
源码目录:
framework/base/core/java/android/app/IApplicationThread.aidl
framework/base/core/java/android/app/IActivityManager.aidl
AMS.startActivity
接下来执行的 ActivityManagerService#startActivity。注: AMS 运行在 SystemServer 进程。
// ActivityManagerService.java |
AST.execute
接下来就运行到了 ActivityStarter#execute。
// ActivityStarter.java |
ASS.resolveIntent
该方法主要调用 PMS 查询 Intent 所指定的 Activity 信息。
// ActivityStackSupervisor.java |
方法内部逻辑如下:
- 首先从 PMS ( PMS 在应用启动时就会去解析所有系统 App 以及所有三方 App的清单文件,将所有 Activity 信息保持到其成员变量 mActivity 中),查询满足所有满足条件的 Activity 。
- 接着选择一个最佳的,如果有多个那么会返回一个特殊的 ResolveInfo 让用户选择打开哪个,不过对于 Launcher 启动而言一般也就一个。
ASS.resolveActivity
该方法根据 ResolveInfo 来获取启动 Activity 信息。
ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags, |
这里也就是明确指定了将要启动的组件信息。
AST.startActivity
继续看启动流程。
// ActivityStarter.java |
上述一段代码主要进行了显示、隐式启动校验,权限校验,判断是否拦截,接着往下看。
// ActivityStarter.java |
方法内部逻辑如下:
- 首先为 SingleTask 以及 SingleInstance 两种启动模式添加了 Intent.FLAG_NEW_TASK 标记。
- 接着判断当前将要启动的 Activity 是否放到已经存在的 Task 中去,Launcher 冷启动应用不会进入,先忽略。
- 接着处理 SingleTop 以及 SingleTask,由于这两种启动当栈顶已经是该 Activity 实例时不会重新启动。
- 接着创建新的 ActivityStack 以及 TaskRecord 用于存放当前新启动的 Activity。
- 接着调用 ActivityStack. startActivityLocked 启动 Activity,对于新启动的栈,只有可能会启动 StartingWindow。
- 最后调用 ActivityStackSupervisor.resumeFocusedStackTopActivityLocked 真正启动当前 Activity。
注:AMS 管理 Activity 主要通过以下四个数据结构,我们经常说的 Activity,其实是 TaskRecord,而不是 ActivityStack,ActivityStack 存在的目的应该是为了方便栈前后台切换。
- ActivityDisplay 表示的是显示设备,一般也就一个实例。
- ActivityStack 用于管理多个 TaskRecord,主要作用是方便任务栈前后台切换。
- TaskRecord 表示一个 Activity 栈,管理当前栈中所有的 Activity。
- ActivityRecord 表示一个 Activity 记录。
一个 ActivityDisplay 包含多个 ActivityStack
一个 ActivityStack 包含多个 TaskRecord
一个 TaskRecord 包含多个 ActivityRecord
一个 ActivityRecord 唯一标识一个 Activity
AS.startActivityLocked
调用 ActivityStack 启动 Activity。
void startActivityLocked(...) { |
方法内部逻辑如下:
- 将待启动的 Activity 对应的 TaskRecord 放到 ActivityStack 栈顶,并设置其为前台栈。
- 展示 StartingWindow 也就是预览窗口,也就是所谓的白屏。
AR.showStartingWindow
看看预览窗口显示逻辑,以及如何禁止它。
// ActivityRecord.java |
可以看到预览窗口添加的操作是通过 postAtFrontOfQueue 添加到消息队列中的,因此要到本条消息执行完后才会执行,接着看看 mAddStartingWindow 到底做了些什么。
StartingSurface createStartingSurface(AppWindowToken atoken) { |
添加预览窗口的逻辑已经看完了,本质上就是往 WMS 上添加了一个 View,默认该 View 为空白的, 不过可以通过设置 windowSplashscreenContent 来改变其背景颜色,注意 windowBackground 也可以但是会对 Activity 也有影响,如果需要禁止显示,那么可以通过以下设置
android:windowIsTranslucent // 设置为 true |
ASS.resumeFocusedStackTopActivityLocked
调用 ActivityStackSupervisor.resumeFocusedStackTopActivityLocked 来真正的启动 Activity。
// ActivityStackSupervisor.java |
ASS.pauseBackStacks
调用 ActivityStackSupervisor.pauseBackStacks 来调用启动当前 Activity 的 Activity 的 onPause 方法。
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { |
方法内部逻辑如下:
- 调用了 Launcher 应用程序的 ApplicationThread.scheduleTransaction 方法,这个方法只是发了个信息等待 系统服务进程处理完毕后执行。
- 发送一个延时 500 毫秒执行的消息,这个延时是给上个 Activity 调用 onPause 的实际,等到 500 毫秒到了,如果还没完成,那么就不再进行等待。如果启动 Activity 与待启动 Activity 位于同一个进程,那么启动 Activity 的 onPause 会阻塞待启动的 Activity 启动(由于当前进程的主线程被阻塞了),如果两者位于不同进程那么启动 Activity 的 onPause 最多阻塞 500 毫秒待启动的 Activity 。
注:以下代码是 Android 9.0 服务进程与应用程序进程通信的新方式(在这以前都是明确调用 IApplicationThread 的指定方),最终会按序执行 PauseActivityItem 的 preExecute、execute、postExecute 方法。
mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken, |
稍后系统的 startActivity 流程结束后,Launcher 应用程序就可以脱离阻塞来执行 PauseActivityItem 的 preExecute、execute、postExecute 方法
// PauseActivityItem.java |
onPause 调用流程已经理清楚了,现在还存在两条分支,一条是 Launcher 在 500 毫秒内完成了 onPause 操作,并通知了 AMS,另一条是 Launcher 没有在 500 毫秒内通知 AMS 完成。
// 第一种情况,未超时 |
可以看到超不超时,都会调用 ActivityStack.activityPausedLocked 区别在于第二个参数,不超时为 false,超时为 true(其实这个参数之和日志打印有关,可以忽略),因此只要看这个方法就行。
// ActivityStack.java |
可以绕了一大圈最终其实又回到了 ActivityStack.resumeTopActivityInnerLocked 方法,不过这次由于不存在 Resume 状态的 Activity 因此逻辑改变了,最终调用 ActivityStackSupervisor.startSpecificActivityLocked 方法。
ASS.startSpecificActivityLocked
startSpecificActivityLocked 应该要启动进程了。
// ActivityStackSupervisor.java |
最终 AMS 通过 Process 类去开启进程,继续跟进看看究竟如何创建的。
// Process.java |
通过上述代码可以看出,最终会将启动参数通过 Socket 传递给 Zygote 进程,传递的启动参数如下。
跳转到 Zygote 进程,查看其是如何处理这个消息的。
// ZygoteServer.java |
到此为止 AMS 启动 Activity (新启进程)的流程就已经走完了,下面来看 APP 启动流程。
应用程序进程创建后
当应用程序进程 fork 完毕后,会继续执行 ZygoteConnection.processOneCommand 方法。
// ZygoteConnection.java |
根据上文可以看到当应用程序启动完成后,还做了一些初始化动作,包括:
重定向日志输出,System.out、System.err 打印的日志统一通过 Log.println 进行输出。
为所有的线程设置预未捕获异常处理器,当未捕获异常出现时会打印错误日志,比如以下日志,就是 LoggingHandler 打印的。
2021-05-19 15:36:35.344 25610-25610/com.hefuwei.demo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.hefuwei.demo, PID: 25610
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.hefuwei.demo/com.hefuwei.demo.MainActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3324)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3473)
...为所有的线程设置未捕获异常处理器,当未捕获异常出现时会告知 AMS 当前应用已经崩溃,然后杀死当前应用进程。
public void uncaughtException(Thread t, Throwable e) {
try {
// 通知 AMS 当前应用程序崩溃了
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} finally {
// 杀死当前进程
Process.killProcess(Process.myPid());
System.exit(10);
}
}生成供 HttpUrlConnection 使用的 UserAgent 值,并设置给系统属性。
为 Socket 设置 Tag,用于网络流量统计。(native 实现暂不清楚原理)。
当这些初始化工作都完成后,会通过反射调用 ActivityThread 的 main 方法。
// ActivityThread.java |
main 方法主要工作就是以下三点
- 调用 Looper#prepareMainLooper 创建了主线程的 Looper
- 调用了 ActivityThread#attach 准备和 AMS 进行通信
- 调用 Looper#loop 死循环从消息队列取出消息并处理
Handler 相关部分就不展开了,主要说说 ActivityThread#attach 方法的作用,注意第一个参数传入的是 false,只有系统服务进程传入的是 true。
private void attach(boolean system, long startSeq) { |
attach 方法主要工作就是以下两点
调用 AMS 的 attachApplication 方法用于告知当前应用程序进程已经启动。
设置 GC 监听, 当空闲内存低于 1 / 4 通过 AMS 杀死当前部分当前应用 Activity。
内部原理也很简单,也就是新建一个类,重写该类 的 finalize 方法,然后创建一个该类的弱引用实例即可,当 GC 触发时就会调用该类的 finalize 方法,一个简单的 demo。
class GCWatcher {
protected fun finalize() {
gcCallbacks.forEach {
it.run()
}
gcWatcher = WeakReference(GCWatcher())
}
companion object {
private var gcWatcher = WeakReference<GCWatcher>(GCWatcher())
private val gcCallbacks = arrayListOf<Runnable>()
fun addCallback(runnable: Runnable) {
gcCallbacks.add(runnable)
}
}
}
转到 AMS 看看 attachApplication 具体执行了什么操作。
public final void attachApplication(IApplicationThread thread, long startSeq) { |
attachApplication 方法主要工作为以下四点
- 调用 IApplicationThread#bindApplication 通知应用程序进程创建 Application 实例,并进行相应初始化。
- 调用 ActivityStackSupervisor#attachApplicationLocked 检查是否有需要启动的 Activity。
这里主要关注前面两点,首先看看第一点,回到应用程序进程。
// ApplicationThread.java |
这个方法主要也就是两件事
首先将 AMS 下发的服务进程对应的 BinderProxy 对象进行缓存,后续用到直接取就行,免的后续又要跨进程去获取,下图为所有下发的服务名。
将 AMS 下发的其它信息封装成 AppBindData 对象,并发送一个消息到主线程的消息队列中。但是由于此时主线程还没调用 Looper#loop 因此该消息还得不到执行,接着再转回系统服务进程看第二点。
// ActivityStackSupervisor.java |
遍历当前前台 Activity 栈,判断栈顶 Activity 是否需要启动,如果栈顶 ActivityRecord 中的 app 不存在(不存在表示当前 ActivityRecord 还没依附进程)并且其 processName 与当前进程名一样,那么表示该 Activity 现在需要进行启动。
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, ...) { |
这个流程其实上面 AMS 调用 Launcher 的 onPause 方法已经说过,最终调用到 ApplicationThread 的 scheduleTransaction,其也只是发送了一个消息返回。
现在应用程序主线程应该会存在两个消息(实际不止,这里只关注主要的),等到 AMS 执行完毕主线程调用 Looper.loop 后会执行,下面一一进行分析。
H.BIND_APPLICATION
首先执行 H.BIND_APPLICATION 跟进代码。
public void handleMessage(Message msg) { |
方法内部逻辑为
- 如果当前应用需要调试,那么等待调试器连接后继续执行,也就是说在这之前执行的代码是没有办法进行调试的。
- 获取系统网络代理信息,然后设置给当前虚拟机的环境变量,OkHttp 会使用这些环境变量,从而使得代理生效。
- 反射创建 Application 实例,调用其 attach 方法,内部调用 attachBaseContext 方法。
- 创建所有注册的 ContentProvider 实例,并调用其 onCreate 方法。
- 调用创建的 Application 的 onCreate 方法。
LoadedApk.makeApplication
makeApplication 用于创建 Application 实例。
// LoadedApk.java |
可以看到内部通过反射创建的 Application 对象,并调用 attach 依附上一个 ContextImpl 实例,attach 内部又会调用 attachBaseContext。同时由于创建 Application 时方法内部判断了 mApplication 成员是否存在,如果存在就不再创建。如果应用程序有多个进程,也就是说有多个虚拟机实例,就会创建多个 Application 实例,并执行多次 onCreate 方法。
ActivityThread.installContentProviders
installContentProviders 用于创建应用的所有 ContentProvider 实例,并通知 AMS 以及创建成功。
// ActivityThread.java |
可以看到内部通过反射创建了 ContentProvider 实例,然后调用其 onCreate 方法,最终告知 AMS。至于其工作原理暂时先不研究。
综上,可以知道 Application#attachBaseContext、Application#onCreate、ContentProvider#onCreate 的执行顺序,从前到后分别为
- Application#attachBaseContext
- ContentProvider#onCreate
- Application#onCreate
H.EXECUTE_TRANSACTION
接着执行第二个消息 H.EXECUTE_TRANSACTION 跟进代码。
public void handleMessage(Message msg) { |
实际上就是按序进行以下三个操作:
- ActivityThread.handleLaunchActivity
- ActivityThread.handleStartActivity
- ActivityThread.handleResumeActivity
- AMS.activityResumed
AT.handleLaunchActivity
首先看看 ActivityThread.handleLaunchActivity 方法。
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { |
AT.handleStartActivity
接着看看 ActivityThread.handleStartActivity 方法。
public void handleStartActivity(...) { |
AT.handleResumeActivity
最后看看 ActivityThread.handleResumeActivity 方法。
final void handleResumeActivity(...) { |
综上 Activity 启动时回调的生命周期包括 onCreate、onStart、onPostCreate、onResume、onPostResume。不过 handleResumeActivity 后面的代码将 DecorView 添加到 WindowManger 中才是 Activity 能够展示的关键,这一块下一篇文章分析。
总结与展望
本文大体上分析了从用户点击 Launcher 界面点击图标到应用首个 Activity 的 onResume 执行完毕的整个调用流程,不过有些过于粗略,比如 AMS 的 Activity 栈管理等等,后续还可分析的内容有:
- Activity 界面展示流程(包括 View 的三大流程)。
- Android 屏幕刷新机制(结合异步消息同步屏障)。
- ContentProvider、Service、Broadcast、Fragment 实现逻辑。
- Zygote 进程启动流程(这一块暂时不知道怎么调试)。
问题回答
经过源码调试与分支,现在可以解答上述的问题了。
应用启动时显示的白屏究竟是什么,如何禁止显示?
答:应用启动时显示的白屏其实是 StartingWindow 也就是预览窗口,其会在进程启动前就显示,如果需要禁用可以经过以下设置
android:windowIsTranslucent // 设置为 true 或
android:windowDisablePreview // 设置为 true如果需要设置背景颜色,那么需要经过以下设置
android:windowSplashscreenContent // 设置为 color 或者 drawable
应用程序进程是如何创建的,创建完做了什么?
答:应用程序进程由 AMS 通过 Socket 通知 Zygote 进程,后者通过 fork 自身创建的,创建完毕后做了一系列初始化操作,比如设置默认异常处理器等,接着通过反射调用 ActivityThread.main 方法。
系统管理 Activity 栈涉及的数据结构有哪些?
答:
ActivityDisplay 表示的是显示设备,一般也就一个实例。
ActivityStack 用于管理多个 TaskRecord,主要作用是方便任务栈前后台切换。
TaskRecord 表示一个 Activity 栈,管理当前栈中所有的 Activity。
ActivityRecord 表示一个 Activity 在系统服务进程的记录。
应用崩溃后,为什么会打印导致崩溃的错误日志?
答:应用程序进程刚一创建时,就会设置两个默认异常处理器,第一个负责打印最终错误日志,第二个负责将应用 Crash 上报给 AMS 并杀死当前应用。
调试应用时,为什么会出现等待提示框,这个框是如何弹出的?
答:在 ActivityThread 的 handleBindApplication 方法中如果应用需要进行调试,则会跨进程调用 AMS 的 showWaitingForDebugger 方法,第二个参数决定是显示还是隐藏。显示逻辑如下:
// ActivityManagerService.java
public void showWaitingForDebugger(IApplicationThread who, boolean waiting) {
synchronized (this) {
ProcessRecord app = getRecordForAppLocked(who);
Message msg = Message.obtain();
msg.what = WAIT_FOR_DEBUGGER_UI_MSG;
msg.obj = app;
msg.arg1 = waiting ? 1 : 0;
mUiHandler.sendMessage(msg);
}
}
public void handleMessage(Message msg) {
case WAIT_FOR_DEBUGGER_UI_MSG: {
ProcessRecord app = (ProcessRecord)msg.obj;
if (msg.arg1 != 0) {
if (!app.waitedForDebugger) {
Dialog d = new AppWaitingForDebuggerDialog(
ActivityManagerService.this,
mUiContext, app);
app.waitDialog = d;
app.waitedForDebugger = true;
d.show();
}
} else {
if (app.waitDialog != null) {
app.waitDialog.dismiss();
app.waitDialog = null;
}
}
}
}其实也就是个 Dialog 不过是系统服务进程弹出的,我们也可以使用反射调用让其显示,不过没什么意义。
在系统设置中设置代理,为什么应用就会走这个代理?
答:由于应用程序进程在启动后会请求 ConnectivityService 获取代理信息,然后将其设置当前虚拟机的环境变量,而 OkHttp 底层会去读取环境变量,因此就会生效。
免初始化的 SDK (比如 LeakCanary)究竟是如何做到的?
答:通过注册 ContentProvider,其 onCreate 方法会自动在 Application.onCreate 之前执行。
为什么应用不会退出,不应该方法执行完就退出?
答:由于主线程调用了 Looper.loop 使得主线程处理死循环中(不断从消息队列中取消息),因此主线程不会执行完毕,应用也不会退出。
应用在后台时点击应用图标,为什么不会再次启动首页?
答:根据源码发现如果待启动的 Activity 将要存放的 TaskRecord 后台已经存在,并且本次启动的 Intent 与原先启动该 TaskRecord 的 Intent 一致,那么就只会将该 TaskRecord 带到前台,而不进行任何操作(如果设置了 SingleTop 还是会调用 onNewIntent 的)。
// ActivityStarter.java
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
// 将要启动的组件名,是否与启动该栈启动时的组件名一致,Launcher 启动的栈,再次启动这里当然相同
if (mStartActivity.realActivity.equals(intentActivity.getTask().realActivity)) {
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| LAUNCH_SINGLE_TOP == mLaunchMode)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
// 要启动的组件名与启动该栈时的组件名一致,并且设置了 SingleTop,那么不新建 Activity,只是调用 onNewIntent
// In this case the top activity on the task is the same as the one
// being launched, so we take that as a request to bring the task to
// the foreground. If the top activity in the task is the root activity,
// deliver this new intent to it if it desires.
deliverNewIntent(intentActivity);
} else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
// 这种情况我们要再次启动一个 Activity 加入栈,因为不同的 Intent。
// In this case we are launching the root activity of the task, but with a
// different intent. We should start a new instance on top.
mAddingToTask = true;
mSourceRecord = intentActivity;
}
}
}为什么 onCreate、onStart、onResume 都获取不到 View 的宽高?
答:因为 View 的三大流程要在 onResume 回调完毕,并且当前 DecorView 添加到 WindowManager 中才会开始,因此这些生命周期都是拿不到结果的。