戳蓝字"牛晓伟"关注我哦!
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。
本文摘要
本篇本来是应该继续写Activity管理系列之Activity启动 相关的文章的,但是发现在整理整个大纲结构之前,发现Activity启动设计的知识面非常广,比如会涉及到进程管理、包管理、zygote进程、App端框架等。并且通过一篇文章把Activity启动的全貌都展示出来本身就是需要很大的篇幅。 遂决定先把App端框架 这个相对基础的内容介绍给大家,并且App端框架在后面的Service、BroadcastReceiver、ContentProvider内容中都会涉及到。
关于App端框架 这个名字,其实Android官网是没有这样的一个概念或名字的,这个概念完全是我自己创造的,我个人认为这个框架是一直都存在的,只不过没人给它一个名字而已。大家可以想想,四大组件的初始化及回调方法的执行都是被动的行为,而到底是"谁"在做这个事情呢?很明显就是底层框架 在做这些事情,底层框架对于开发者来说是透明的。因此我给这个底层框架 起了个普通的名字App端框架。
本文采用自述的方式介绍了App端框架是啥,它是如何帮助App进程启动的,以及它是如何帮助Activity启动的。(文中代码基于Android13)
注:文中提到的ATMS是ActivityTaskManagerService的简称,AMS是ActivityManagerService的简称。
四大组件管理系统系列文章:
深度解读ActivityManagerService----Android四大组件系统系列
深入理解ActivityRecord和Task---Activity管理系列
本文大纲
1. App端框架为何物
亲爱的朋友们,大家好啊,我是App端框架,在进入今天的主题之前,我先让大家思考个问题:Android提供了Application和四大组件,并且也指定了一套规则:就是在对应的回调方法里面做相应的事情。比如在Application的onCreate方法里面可以监听App进程的启动;比如在Activity的onCreate方法里面需要设置Content View。而Application和四大组件的实例何时被初始化,以及它们的回调方法何时被执行,这些事情开发者统统都不需要关心。那这些事情是谁来做的呢?
那当然是我App端框架 了,在App进程启动后,我会初始化Application对象,并且调用它的onCreate等回调方法。而四大组件的初始化及相应回调方法的调用也是由我来负责 。而至于何时来执行这些操作 ,其实我也做不了主啊,我还得听从ActivityManagerService 和ActivityTaskManagerService发出相应的命令,它们会通过binder通信发出相应的命令,它们让我干嘛我就干嘛。
我把Application和四大组件的初始化和回调方法的执行过程称为被动执行 ,因为它们没有"自己掌握自己的命运",而是由我来掌握"它们的命运",何时初始化它们何时调用它们的回调方法,都统统由我来管理。设计成如此的好处是非常明显的那就是降低开发者的开发成本 、以及提升开发效率,开发者只需要按照规则开发即可,而至于啥时候被初始化,啥时候Activity进入resume状态,啥时候进入stop状态,只需要被动监听即可。
1.1 我的成员
既然我是一个框架,那肯定离不开各种类的"辅助",它们可是我的成员,我绘制了一幅图,把我的主要成员介绍给大家:
我把我的成员分为两部分上层主要类 和底层主要类 。底层主要类 它们的主要职责就是把框架搭建好,它们对于开发者来说基本都是透明的,或者说开发者根本感觉不到它们的存在,它们默默地做着"幕后的工作"。而上层主要类 则是会被开发者经常使用到的类,如四大组件。因为大家对于上层主要类 已经非常熟悉了,因此我简单介绍下底层主要类。
ActivityThread
ActivityThread在App进程fork后,会执行它的main方法,也可以称该类为App进程的入口类。初看它的名字是不是以为它是一个Thread,那你就大错特错了;再看它的名字中带有Activity是不是以为它和Activity有关系,确实它和Activity有关系,但是它还和其他的三大组件也有关系,甚至和整个App都有关系。ActivityThread这个类名确实是一个糟糕的类名,它的名字没有把它所做的事情体现出来。
ActivityThread除了是一个入口类外,它还管理了App进程中的四大组件,比如Activity的初始化过程,以及它的生命周期方法的调用,以及Application初始化以及它的回调方法调用等。可以说ActivityThread是所有类中最繁忙的一个类。
ApplicationThread
ApplicationThread这个类名也是一个糟糕的类名,它虽然带有Thread,但是该类和Thread没有半毛钱关系。该类像ActivityManagerService一样也是一个binder服务 ,但是不同的是它是一个匿名binder服务 ,而ActivityManagerService是具名binder服务 。即ApplicationThread是不需要把自己注册在ServiceManager服务中的。
而它的使用者是ActivityManagerService/ActivityTaskManagerService,ApplicationThread会接收来自AMS/ATMS的各种消息。
LoadedApk
该类会负责初始化ClassLoader,创建Resource,创建Application子类的实例,还会管理App进程中注册的广播、以及广播消息的分发,管理App进程中绑定的Service等事情。可以把它理解为一个工具类,一般杂活儿、累活儿都交给它来做。
好了,我先暂时介绍上面几个不常见的类,既然我把我的成员介绍给大家认识了,那我把我的运行模型也一同介绍给大家吧。
1.2 运行模型
当App进程启动时,App端框架是有一套自己的运行模型,请看下图:
这个运行模型是不是非常的简单啊,ApplicationThread接收到AMS/ATMS传递过来的消息,通过Handler会把这些消息post到UI线程,ActivityThread再针对消息类型,把消息分发处理。比如收到的是启动某个Activity的消息,则会进行初始化该Activity并且调用它的onCreate、onStart、onResume方法。
1.3 小结
App端框架 运行在App进程,我存在的主要目的就是保证App进程的启动 ,并且管理四大组件的子类对象的初始化以及回调方法的执行 ,当然我还别的作用就不赘述了。那就从App进程的启动 和Activity的启动 来看下我是如何保证它们正常工作的,当然App进程的启动 和Activity的启动都是指的App端。而至于Service、ContentProvider、BroadcastReceiver的内容暂且不在本章介绍。
2. App进程启动
大家都知道要是想知道App进程有没有启动的话,从Application的onCreate方法有没有调用就可以知道。而在onCreate方法被调用之前,我App端框架其实还做了很多的事情,而这些事情对于使用者来说是隐藏的,使用者完全感觉不到它们的存在,那就来看下都做了哪些事情吧。
在App进程启动时,我App端框架是制定了一套规则的,不管是普通App进程 还是系统App进程 的启动都需要经过准备 、告知 、初始化这三个阶段,同样我也特意绘制了一幅图来展示这三个阶段的关系:
那就结合上图来介绍下这三个阶段都做了啥事情吧。
2.1 准备
发出fork (孵化)一个App进程的请求者是位于systemserver进程中的ProcessList类,fork App进程的请求信息通过socket传递到zygote进程 ,而zygote进程在fork出一个App进程后,会执行一个非常重要的操作打开binder驱动 ,不管何种App进程只要被fork出来必须要打开binder驱动,打开binder驱动代表着App进程可以提供binder服务也可以使用别的binder服务了。并且还会去执行ActivityThread的main方法,ActivityThread的main方法是App进程"进入Java世界"的第一个方法,从此App进程就进入了Java世界。这时候也标志着一个App进程的启动阶段的开始。
准备 作为启动的第一阶段,它发生于main方法,因为ActivityThread还没有实例化,因此会实例化一个ActivityThread对象。如果要想保证一个App进程一直运行的话,一般的做法是在一个线程里面不断的循环执行某些操作。而Android中创造了Looper、Handler、Message、MessageQueue,在main方法中会开启Looper,这个Looper会保证当前线程不断的"循环"下去,这里的循环并不是傻傻的循环,而是Looper的MessageQueue中有Message的话去执行,没有Message的话则会处于等待状态。而Looper所处的线程就是UI主线程了,也就是Looper会在UI主线程里面不断的"循环"下去。
经过此阶段后,App进程真正的"活"了,同时此阶段也是后面阶段的基石,因为UI线程的Looper准备好后,其他的线程就可以通过Handler发送消息到UI线程了。
2.2 告知
告知 用简单明了的话说就是App进程告知ActivityManagerService我这边准备好了,为啥要有这一阶段呢?
首先systemserver进程中的ProcessList类虽然是fork App进程的发起方,但是App进程被fork成功以及准备好这些好消息是非常有必要告知ActivityManagerService的,因为ActivityManagerService是一个"大管家",它需要知道这些状态信息
其次在告知的过程中是需要把ApplicationThread对象传递给ActivityManagerService的,为啥要传递呢?因为App进程和ActivityManagerService之间通信是一个双向的过程,App进程是可以从ServiceManager中拿到ActivityManagerService对应的BinderProxy对象来进行通信的,而ActivityManagerService给App发送消息需要通过ApplicationThread这个匿名binder服务。
最后告知的一个非常重要的目的是需要ActivityManagerService把ApplicationInfo、Apk文件路径、so库文件路径、共享库路径、App的包名等等关键信息告知App进程 ,或许你会有疑问啥我作为App进程,我尽然不知道我的包名是啥?我可以很负责任的告诉你确实不知道,其主要原因是App端框架 作为一个框架,它只是一个框架,该框架不管是运行在哪个App进程里面都是一样的,不一样的是框架里的数据 ,而这些数据 就指的是上面提到的App的包名、Apk文件路径、Apk的版本号等等这些信息。用一句话总结就是不管是啥App进程,App端框架都是一模一样的,唯一不同的是填充框架的数据 ,而数据是需要从ActivityManagerService传递过来的。
因为在zygote进程fork App进程的时候已经打开的binder驱动,因此这时候是可以使用ActivityManagerService的attachApplication方法的,通过该方法就可以把App进程已经准备好了这个好消息告知ActivityManagerService了。
经过此阶段后,App进程处于等待ActivityManagerService的回复结果中。
2.3 初始化
ActivityManagerService会经过各种安全校验、检查,如果全部都通过了则会把processName、ApplicationInfo等关键信息通过binder调用传递到ApplicationThread的bindApplication方法中,而ApplicationInfo中可是包含了很多很多有用的信息,比如上面提到的App的包名、Apk的版本信息、Apk的文件路径等信息。
ApplicationThread拿到这些信息后会通过UI线程的Handler把这些数据post到UI线程中,而ActivityThread会在UI线程中使用这些数据开始进行初始化操作,初始化的过程发生于ActivityThread的handleBindApplication方法中,那看下初始化都做了哪些工作。
初始化主要做了初始化PathClassLoader 、创建Application 、安装ContentProvider 、调用Application的onCreate方法这几件事情,那就来介绍下它们吧。
2.3.1 初始化PathClassLoader
大家都知道Java中的类被使用的话,需要去ClassLoader加载器中去查找该类。而每个App进程都对应了自己的PathClassLoader。因此App进程中各种类要被使用的第一前提就是初始化PathClassLoader ,初始化PathClassLoader就是使用ActivityManagerService传递过来的Apk文件路径 、共享库文件路径 、so库路径这些信息来初始化PathClassLoader对象,Apk文件路径包含了所有的类,共享库包含了App使用到的共享库信息,so库路径则包含了native代码。PathClassLoader初始化完毕后,就可以保证App进程中的各种类使用了,因此这一步是非常重要的。
初始化PathClassLoader的代码如下,有兴趣可以看下:
csharp
//LoadedApk类
public ClassLoader getClassLoader() {
synchronized (mLock) {
if (mClassLoader == null) {
//如果mClassLoader不存在,则会调用createOrUpdateClassLoaderLocked方法创建
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}
2.3.2 创建Application
有了上一步的基础后,就可以创建Application,如果在AndroidManifest文件中配置了对应的Application,这时候创建的就是配置的Application对象,否则创建的是Application对象,创建完毕Application对象后,为后面调用它的onCreate方法做准备。
下面是创建Application相关的代码,有兴趣可以看下:
ini
//LoadedApk.java类
private Application makeApplicationInner(boolean forceDefaultAppClass,
Instrumentation instrumentation, boolean allowDuplicateInstances) {
省略代码······
Application app = null;
final String myProcessName = Process.myProcessName();
String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
myProcessName);
//在AndroidManifest文件中如果没有配置Application,则appClass为null,这时候appClass为"android.app.Application";
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
final java.lang.ClassLoader cl = getClassLoader();
省略代码······
//调用mInstrumentation的newApplication方法创建Application对象
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
}
省略代码······
return app;
}
2.3.3 安装ContentProvider
如果在AndroidManifest文件中配置了ContentProvider,则在该步就会安装它们,安装ContentProvider的过程是一个相对耗时的过程,因为每一个ContentProvider安装成功后是需要把这个消息通知给ActivityManagerService的。因此如果AndroidManifest文件中配置的ContentProvider多,则会影响App启动的速度。
相关的代码如下,有兴趣自行取阅:
scss
//ActivityThread.java类
private void handleBindApplication(AppBindData data) {
省略代码······
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
省略代码······
}
2.3.4 调用Application的onCreate方法
这也是App进程启动的最后一步了,这时候就需要调用Application对象的onCreate方法,来把App进程启动完成的这个好消息告诉上层。
相关的代码如下,有兴趣自行取阅:
typescript
//ActivityThread.java类
private void handleBindApplication(AppBindData data) {
省略代码······
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
省略代码······
}
省略代码······
}
初始化阶段可不是当当做了上面几件事情,只是上面几件事情比较重要故把它们列出来。
2.4 小结
App端框架 把App进程启动分为准备 、告知 、初始化三个阶段,前一阶段都是在为后一阶段做准备。
- 准备阶段Looper在App进程的UI线程里面不断"循环"运行,这样让App进程"活"了起来。
- 告知阶段则会通知ActivityManagerService对应的App进程已经准备完毕了,并且会把ApplicationThread传递给ActivityManagerService,这样ActivityManagerService就可以把消息通过ApplicationThread传递给App进程了。
- 初始化 阶段则会使用ActivityManagerService传递过来的App相关的数据 (如包名、Apk文件路径、so库文件路径等信息),进行初始化PathClassLoader,初始化Application,并且调用Application的onCreate方法告知上层App进程启动完成。
我App端框架除了制定App进程的启动规则外,我还负责四大组件的运行,那就接着来看下吧。
3 Activity的启动
同样为了给大家展示Activity的启动过程,我同样也特意绘制了一幅图:
如上图,ATMS会通过binder通信把启动Activity的数据 发送给ApplicationThread,ApplicationThread同样把这一消息通过Handler post到UI线程中。最终会在ActivityThread的handleLaunchActivity方法中开始启动Activity,启动Activity相关的数据都放在ActivityClientRecord对象中,下面表格是它的主要属性:
属性 | 说明 |
---|---|
token:IBinder | 它是ActivityRecord与Activity建立一一对应关系的关键 介绍ActivityRecord的文章 |
activityInfo:ActivityInfo | 该属性包含了在AndroidManifest文件中配置的Activity信息 |
state:Bundle | 存放了Activity在onSaveInstance方法中保存的数据 |
intent:Intent | 该属性的getComponent方法可以获取到启动Activity的类信息 |
我App端框架把开始启动Activity 分为创建Context对象 、创建Activity对象 、为Activity对象设置基础信息 、调用Activity对象的onCreate方法这几个步骤,那就介绍下它们吧。
3.1 创建Context对象
大家都知道Context在Android中的地位是多么的重要,Context犹如一个万能类,从里面可以获取到很多信息,比如可以获取到各种Manager对象。每一个Activity都有自己的Context对象,因此需要先创建Context对象,而该对象指向ContextImpl类。
下面是相关代码,请自行取阅:
scss
//ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
省略代码······
ContextImpl appContext = createBaseContextForActivity(r);
省略代码······
}
3.2 创建Activity对象
要想创建Activity对象,第一步是需要从ActivityClientRecord对象的intent属性的getComponent方法中获取到子类信息;第二步就是使用Instrumentation对象的newActivity方法创建Activity对象,创建Activity对象用的是反射。
我觉得有必要先暂停下,大家可以思考下第二步为啥不直接创建Activity的子类对象,而是要使用Instrumentation对象呢?
Instrumentation它把创建四大组件及Application子类对象的这些操作都包含了,同时还包含了调用四大组件的各种回调方法,这样做的主要原因是,开发者可以在AndroidManifest文件中通过instrumentation标签配置自己的Instrumentation子类,这样在该子类中就可以覆写父类中的方法,进而可以做一些监听处理或者更改一些行为等。
创建的Activity对象是需要存储下来的,而它是被存储在ArrayMap<IBinder, ActivityClientRecord>这样的结构中,其中key为上面一直提到的token:IBinder,而value是ActivityClientRecord对象,它包含了刚刚创建的Activity对象。关于token介绍可以查看此文章|
下面是相关代码,请自行取阅:
scss
//ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
省略代码······
ComponentName component = r.intent.getComponent();
省略代码······
try {
//获取ClassLoader
java.lang.ClassLoader cl = appContext.getClassLoader();
//调用newActivity方法开始创建Activity子类对象
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
省略代码······
synchronized (mResourcesManager) {
//把Activity存储到mActivities
mActivities.put(r.token, r);
}
}
省略代码······
}
3.3 为Activity对象设置基础信息
Activity子类对象虽然创建了,但是还有很多信息它还没有比如上面创建的Context对象,那这一步就是要调用Activity对象的attach方法把这些信息交给它,下面表格列出了主要的一些信息:
信息 | 说明 |
---|---|
context:Context | Context对象 |
instr:Instrumentation | instr被用来拦截Activity的生命周期方法 |
application:Application | Application对象 |
token:IBinder | 它是ActivityRecord与Activity建立一一对应关系的关键 介绍ActivityRecord的文章 |
3.4 调用Activity对象的onCreate方法
Activity对象需要的基础信息设置好后,Activity也准备好了,这时候可以调用它的onCreate方法了,下面是相关代码,请自行取阅:
scss
//ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
省略代码······
//调用callActivityOnCreate方法,该方法会调用它的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state);
省略代码······
}
3.5 小结
App端框架会收到ATMS传递的创建Activity的消息,而后该消息会通过Handler发送到UI线程中,因此会在UI线程中开始启动Activity的过程。
4. 总结
本文带您认识了由ActivityThread、ApplicationThread、LoadedApk、Handler、Looper等类组成的底层框架,而我给这个底层框架起了一个名字App端框架。
App端框架负责App进程的启动,它把App进程的启动分为准备 、告知 和初始化三部分;App端框架还负责四大组件实例的初始化和回调方法的调用,而何时进行实例的初始化以及回调方法何时执行,App端框架是没有决定权的,而决定权是掌握在AMS和ATMS"手中"的,AMS/ATMS发送什么命令,App端框架就执行啥命令。