Activity的管理者---Android四大组件系统系列

323 阅读27分钟

戳蓝字“牛晓伟”关注我哦!

用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。

本文摘要

本文采用自述的方式介绍了ActivityTaskManagerService是如何管理所有的Activity的,ActivityRecord的作用是啥,为啥要有Task的存在,Task的容器是啥,ATMS在Activity启动中都做了哪些工作。(文中代码基于Android13)

注:文中提到的ATMS是ActivityTaskManagerService的简称,AMS是ActivityManagerService的简称,PMS是PackageManagerService的简称。

四大组件管理系统系列文章:

开篇----Android四大组件系统系列

深度解读ActivityManagerService----Android四大组件系统系列

深入理解ActivityRecord和Task---Activity管理系列

App端框架之谜---Android四大组件系统系列

本文大纲

image

1. Activity的管理者

大家好,我是Activity的管理者,我的名字叫ActivityTaskManagerService,当然大家可以叫我的小名ATMS,原先的Android世界中是没有我的,管理Activity的工作是完全由ActivityManagerService (简称AMS)负责的,它还管理着Service、ContentProvider、BroadcastReceiver,如果按这样发展下去AMS中的代码会越来越膨胀,进而导致难以维护、高耦合等危害。为了解决这些问题AMS把Activity管理方面的工作都交给了我。

我也像AMS一样也是一个具名binder服务,即我也会把自己注册在ServiceManager服务中,而使用者使用我的话可以通过ActivityTaskManager来使用我的功能。我虽然全权负责Activity的管理工作,但是我还是归AMS管理。

我一直都在说Activity的管理,那我都负责哪些事情呢?

首先Android系统中所有App启动的Activity都归我管理,哪些Activity处于显示状态,哪些出于stop状态,哪些处于destory状态,Activity所属的App是哪些等等这些信息我都必须清楚;其次我提供了启动Activity的能力,凡是App想要启动Activity都必须调用我提供的能力来启动。当然我还提供了其他的一些能力比如Task相关的能力,但是这些能力并不是很重要就不在这赘述了。

关于我就介绍到此,大家一定对我是如何管理这么多的Activity感兴趣吧,那我就慢慢道来。

2. 如何管理所有的Activity

Activity的具体实例是位于每一个App进程,而我ATMS是位于systemserver进程。如果处于同一进程,那管理工作就特别简单了,我只需要拿到Activity的实例即可,但是现在不在同一进程,因此要想管理好所有的Activity,就需要定义一个类来记录启动的Activity。

2.1 Activity的记录者--ActivityRecord

ActivityRecord正如其名,它的作用就是记录启动的Activity,在本章不会详细介绍ActivityRecord,想查看关于它的详细介绍可以看此文。ActivityRecord中记录了关于Activity的很多信息,我拿出三个非常有用的信息介绍给大家,因为这三个信息在后面内容中会用到。

2.1.1 ActivityRecord主要信息

Activity信息

Activity信息存放在ActivityRecord的类型为ActivityInfo的info属性中,而ActivityInfo则存储了在AndroidManifest.xml文件中配置的Activity相关的信息,如下表格是ActivityInfo的主要属性:

属性说明
launchMode:intActivity的启动模式
name:String在AndroidManifest中使用android:name配置的Activity的类名信息
exported:String该Activity是否可以让别的App使用
enabled:boolean该Activity是否可以使用
applicationInfo:ApplicationInfo指向在AndroidManifest中配置的Application信息

上面只是列了一些Activity信息,还有很多的Activity信息没有列出。ActivityInfo中的信息是来自于PackageManagerService。

Activity状态信息

因为Activity是有各种生命周期方法的,比如onResume、onPause、onStop等,而Activity状态信息就是代表记录的Activity处于哪种状态,如下表格是ActivityRecord中Activity状态信息相关的属性:

属性说明
mState:StateState是一个枚举类型,它的值有INITIALIZING、STARTED、RESUMED、PAUSING、PAUSED、STOPPING、STOPPED、FINISHING、DESTROYING、DESTROYED、RESTARTING_PROCESS
finishing:boolean代表记录的Activity是否finish
Intent信息

凡是启动一个Activity,都需要把启动的信息放入Intent中,如下例子:

//this代表当前Activity,xxxx.class代表目标Activity
Intent intent = new Intent(this,xxxx.class);
startActivity(intent);

//通过为Intent设置action的方式启动Activity
Intent intent = new Intent();
intent.setAction("xxx.xxx.xxxx");
startActivity(intent);

而ActivityRecord的类型为Intent的intent属性,则存放的就是启动Activity时候的Intent。

关于ActivityRecord的三个信息就介绍到此,大家可以思考个问题:App进程中的Activity和它的记录者ActivityRecord属于不同的进程,那它们之间是如何找到对方的呢?那我来解答下吧。

2.1.2 ActivityRecord和Activity之间如何找对方

深入理解ActivityRecord和Task一文中介绍过,在这在来介绍下,Activity和ActivityRecord是通过IBinder对象找到对方的。在解释原因之前,先来介绍下IBinder对象的特性。

IBinder对象特性

请先看下图:

image

图注: BinderBinderProxy类都是IBinder类的子类,A进程和B进程都是App类型进程,Binder-A对象和BinderProxy-B对象代表任意的Binder和BinderProxy对象。

结合上图来介绍下IBinder对象的特性:

  1. A进程中的一个Binder-A对象被传递给B进程时,该Binder对象在binder驱动层会被转换,因此B进程收到的是BinderProxy-B对象
  2. Binder-A对象从A进程传递到B进程传递多次,B进程获取的依然是Binder-B对象。即一个进程中的同一Binder对象多次传递相同的其他进程,则其他进程获取的BinderProxy对象是不变的。
  3. B进程的BinderProxy-B对象被传递给A进程,A进程得到的是Binder-A对象。即可以通过BinderProxy对象反解出原先的Binder对象
找对方

了解了IBinder对象的特性后,那我们来看下是如何通过IBinder对象找到对方的,在ActivityRecord中定义了一个Token类,为啥要介绍这个类呢?因为这个类就是“线索”,来看下它的定义如下:

//ActivityRecord类

//Token就是保证它们连接的类
private static class Token extends Binder {
        @NonNull WeakReference<ActivityRecord> mActivityRef;

        @Override
        public String toString() {
            return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
                    + mActivityRef.get() + "}";
        }
    }

Token类是不是非常简单啊,它只是继承了Binder了,并且它的mActivityRef属性指向ActivityRecord。为了说明Token类是如何起到关键作用的,先看下面这幅图:

image

图解

App进程中是在ActivityThread的mActivities属性中存储了所有启动的Activity实例,如下相关代码:

//ActivityThread类

final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

结合上面代码和上图,IBinder对象是作为mActivities的key值,而ActivityClientRecord对象是作为mActivities的value值,ActivityClientRecord对象中存储了Activity的实例以及从ATMS传递过来的Activity的各种信息。

systemserver进程中的每个ActivityRecord都有一个Token类型的token属性,没错这个Token类就是上面提到的Token类。

上图中展示了token属性与App进程中的作为key值的IBinder对象是有一个关系的,那这个关系是啥呢?还记得IBinder对象的特性吗?因为Token类是一个Binder子类,它会被传递到App进程中,App进程这时候收到的是类型为BinderProxy的对象,而该BinderProxy对象就是作为key值的IBinder对象,到这是不是有些明白了。

在Activity启动阶段,ActivityRecord的类型为Token的token属性、启动Activity的信息及其他信息会通过binder通信传递给App进程。App进程收到这些信息时,其中ActivityRecord的token属性已经被转换为BinderProxy对象,而App进程会把BinderProxy对象与ActivityClientRecord对象存储起来。

经过如上步骤,当ATMS再有消息要通知对应的Activity时,会传递ActivityRecord中保存的Token对象给App进程,App进程根据BinderProxy对象拿到对应的Activity实例,就可以把消息通知该Activity实例了。而当App进程的Activity有消息通知ATMS时,会携带Activity对应的BinderProxy对象,而该对象到了ATMS端后就变为Token对象了,进而使用下面代码中的forTokenLocked方法就可以把Token对象转变为ActivityRecord对象了。

//ActivityRecord类

 //该方法从IBinder中找到ActivityRecord
 static @Nullable ActivityRecord forTokenLocked(IBinder token) {
        if (token == null) return null;
        final Token activityToken;
        try {
            activityToken = (Token) token;
        } catch (ClassCastException e) {
            Slog.w(TAG, "Bad activity token: " + token, e);
            return null;
        }
        final ActivityRecord r = activityToken.mActivityRef.get();
        return r == null || r.getRootTask() == null ? null : r;
    }

2.1.3 小结

我ATMS使用ActivityRecord来记录App进程中的Activity,因为ActivityRecord与App进程中的Activity是不在同一进程的,那得有一种方式能让它们互相找到对方,比如ATMS中的ActivityRecord要发送消息给App进程中的Activity,那如何确定目标Activity呢;再比如App进程中的Activity有消息要发送给ATMS中对应的ActivityRecord,那同样如何确定目标ActivityRecord呢。

答案就是ActivityRecord中定义的Token类,它是一个Binder子类,充分使用IBinder对象的特性就可以做到ActivityRecord与App进程中的Activity“互认”的目的。

深入理解ActivityRecord和Task中介绍过ActivityRecord不单单只有记录Activity的功能,它还是WindowManagerService侧的一个容器窗口。容器窗口是啥意思呢?容器窗口就是在Surfaceflinger服务侧有自己的一个容器layer,比如Activity配置的进入或者退出动画,也都是该容器layer在起作用。

2.2 ActivityRecord的容器--Task

既然每个App进程中的Activity的记录者已经确定是ActivityRecord了,那接下来需要确定的事情是选用哪种数据结构来存储所有的ActivityRecord呢?

大家可以跟随我的思路考虑个问题:现在界面上展示的是A界面,点击A界面中的按钮进入B界面,再次点击B界面中的按钮进入C界面。这时候界面的先后顺序是C、B、A。这时候按back按键C界面销毁展示出B界面,再次按back按键B界面销毁展示A界面。像以上这种情况符合哪个数据结构的特性呢?

当然是这个数据结构了,栈具有先进后出的特性,而上面界面的显示、销毁过程完全也是先进后出。因此ActivityRecord的容器是具有栈特性的Task这个类,Task也像ActivityRecord一样也是一个容器窗口。在深入理解ActivityRecord和Task中详细介绍过此类,在这就不过多的介绍了。我同样特意绘制了一幅图来展示Task、ActivityRecord、Activity之间的关系:

image

大家可以在思考个问题:是否可以用Task把所有的ActivityRecord存储下来?

答案是:不可以,那我就给大家举了个例子,请看下图:

image

如上图Task中从栈顶到栈底依次存储了App2的ActivityRecord1、launcher的ActivityRecord1、App1的ActivityRecord2、App1的ActivityRecord1,也就是Task把所有的ActivityRecord都存储下来了。因为现在App2的Activity是处于显示状态的,那如果用户这时候按home按键,这时候应该把launcher的Activity显示出来,而Task是一个先进后出的结果,因此如果这时候需要把launcher的Activity显示出来,那就需要把App2的Activity对应的ActivityRecord从Task移除来,也就是App2的Activity和它对应的ActivityRecord就死掉了。这种设计肯定是非常不合理的,不能说因为按了一个home键就把launcher以上的Activity全都杀死。

那该如何解决以上问题呢?答案是每个App都有自己独立的Task,也就是会存在多个Task的情况,不同的Task负责存储自己相关的ActivityRecord,比如当前打开了3个App,就会存在3个Task和一个launcher对应的Task,那我就介绍下多Task如何解决以上问题。

同一个Task不单单可以存放对应App的ActivityRecord,还可以存放其他App的ActivityRecord

2.3 Task的容器--TaskDisplayArea

既然是存在多个Task的情况,那我在来介绍下Task的容器--TaskDisplayArea,在深入理解ActivityRecord和Task中也介绍过它,同样在这就不详细介绍了。同样我特意绘制了一幅图来说明TaskDisplayArea、Task、ActivityRecord、Activity之间的关系:

image

如上图,TaskDisplayArea是用了一个数组来存储Task的,上图中分别存储了三个Task,从index=0开始这些Task分别对应App1进程、launcher进程、App2进程。非分屏情况下位于数组尾部的Task中的栈顶的ActivityRecord对应的Activity是处于显示状态,如上图中的App进程的Activity4就处于显示状态。

如果这时候用户按home按键,则会把launcher进程对应的Task和App进程对应的Task交换位置,这时候launcher进程对应的Task处于数组尾部,因此这时候它包含的ActivityRecord对应的Activity也处于显示状态,而App进程对应的Task中的栈顶ActivityRecord对应的Activity则进入stop状态。

TaskDisplayArea也是一个容器窗口,而它也有包含它的容器,这些内容都属于WindowManagerService的内容,在介绍WindowManagerService时会详细的介绍到它们。WindowManagerService的所有窗口的顶级容器窗口是RootWindowContainer,在我ATMS中有关于它的属性,如下:

//ActivityTaskManagerService

RootWindowContainer mRootWindowContainer;

2.4 小结

我ATMS为了管理好所有的Activity,因为这些Activity是位于各自的App进程,而我是位于systemserver进程,为了管理所有App进程的所有启动的Activity。我设计了ActivityRecord这个类,ActivityRecord与App进程中的Activity是一一对应关系,是通过Token这个类来找到对方的,ActivityRecord不仅仅用于记录Activity它还是WindowManagerService侧的一个容器窗口

为了存储ActivityRecord,我设计了Task这个类,它以的结构来存储ActivityRecord,每个App都有自己的Task,因此会存在多个Task,Task也是一个容器窗口Task可以包含同一App进程的ActivityRecord也可以包含别的App进程中的ActivityRecord

因为会存在多个Task,因此我设计了TaskDisplayArea这个类,它同样也是一个容器窗口,它以数组的结构来存储所有的Task。非分屏情况下位于数组尾部的Task栈顶的ActivityRecord对应Activity处于显示状态

本节介绍了Activity的记录者ActivityRecord、Task、TaskDisplayArea相关的类及其作用,那我再来介绍下Activity的“出生”吧。

3.1 Activity的“出生”

Activity的“出生”其实指的就是Activity的启动,不论是启动App内的Activity还是启动其他App内的Activity,都需要经过我ATMS的一系列的处理流程,只有这系列流程全部通过后,我才会发送启动Activity的信息给相应的App进程。App进程收到该信息后,才真正开始该Activity的启动流程。

当一个App进程还没有启动,这时候启动它的一个Activity (比如从桌面打开一个App),此种Activity的启动是最复杂的启动流程,为啥呢?因为启动Activity的时候还需要涉及到App进程的启动。同样我也绘制了一幅此种Activity的启动流程图:

image

图解

启动Activity本身就是ATMS和App进程互相配合的一个过程,因此上图分别从systemserver进程和App进程的流程展示了一个Activity的启动过程。关于App进程的流程可以参考App端框架之谜 这篇文章,在此就不过多的赘述了。那我就结合上图来介绍下我ATMS在Activity启动时都做了哪些事情。

如上图,App进程没有启动的情况下,启动Activity的过程分为查询Activity信息检查记录Activity信息启动App进程App进程准备好发送初始化数据发送Activity启动信息这几步,当然这几步中会需要zygote进程进程管理模块帮忙,那就依次介绍下这几步吧。

3.1 查询Activity信息

大家都清楚,启动一个Activity是需要把启动的信息放入Intent中,如下例子:

//this代表当前Activity,xxxx.class代表目标Activity
Intent intent = new Intent(this,xxxx.class);
startActivity(intent);

//通过为Intent设置action的方式启动Activity
Intent intent = new Intent();
intent.setAction("xxx.xxx.xxxx");
startActivity(intent);

查询Activity信息的作用就是根据Intent中的信息从PackageManagerService查询到对应的Activity信息 (PackageManagerService简称PMS),而这个Activity信息就指的是在AndroidManifest文件中声明的Activity信息,Activity信息是存放在ActivityInfo对象的属性中,下面列举ActivityInfo的几个关键属性:

属性说明
launchMode:int通过android:launchMode配置的信息,它的值有standard,singleTop,singleTask,singleInstance
permission:String通过android:permission配置的启动该Activity的权限
name:String通过android:name配置的Activity对应的类名信息
enabled:boolean该Activity是否可用
exported:boolean该Activity是否可以被别的App启动
processName:String通过android:process设置的进程名字
applicationInfo:ApplicationInfo该App对应的Application信息

请记住查询到的ActivityInfo信息可是非常重要的,它可是后面所有流程的基础,ActivityInfo可是会被传递到App进程的;如果从PMS中没有获取到ActivityInfo信息,则整个启动过程会停止,并且会把错误信息返回给启动者。

第一步结束后,我在给大家介绍下第二步都做了哪些事情。

3.2 检查

检查所做的事情不像安装Apk那么复杂,主要是检查启动者是否有启动该Activity的权限。检查主要分为以下主要步骤:

  1. 检查启动者是否有启动任意Activity的权限 (START_ANY_ACTIVITY),如果有则直接返回true (像systemserver进程是有这种特权的);否则进行下一步的检查
  2. 检查被启动的Activity的exported值,该值为true则代表该Activity可以被其他App启动,否则不可以被启动 (该值是在AndroidManifest文件中使用android:exported配置的)。如果该值为false则整个启动过程结束,并且把错误信息传递给启动者;否则进入下一步的检查
  3. 检查被启动的Activity是否配置了相应权限 (该权限是在AndroidManifest文件中使用android:permission配置的)。如果启动者配置了该权限,则可以启动;否则因为启动者没有权限,也同样停止整个启动过程,并且把错误信息传递给启动者。

以上就是检查的主要的过程,当然检查这一步还做了一些别的检查就在这步赘述了。

为了让大家更明了,我举了如下例子:

//该Activity的exported设置为false,则代表其他App不可以启动该Activity,
<activity android:name=".SecondActivity" android:exported="false" >
</activity>

//该Activity设置了permission,则代表启动者是需要相应权限的,没有该权限则启动者无法启动该Activity
<activity android:name=".SecondActivity" android:permission="xxxx.xxx" >
</activity>

下面是检查相关的代码,有兴趣可以看下:

//ActivityStarter
private int executeRequest(Request request) {
  省略代码······
  boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
                request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
                resultRootTask);
  abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                callingPid, resolvedType, aInfo.applicationInfo);
  abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
                callingPackage);
  省略代码······

}

3.3 记录Activity信息

检查环节也通过了,那接下来就进入记录Activity信息这一步,还记得记录Activity用到了ActivityRecord和Task吗?没错记录Activity信息这一步就是ActivityRecord和Task该发挥它们作用的时候了。

创建ActivityRecord

大家先看下创建ActivityRecord相关的代码吧,如下:

//ActivityStarter
private int executeRequest(Request request) {
  省略代码······
  final ActivityRecord r = new ActivityRecord.Builder(mService)
                .setCaller(callerApp)
                .setLaunchedFromPid(callingPid)
                .setLaunchedFromUid(callingUid)
                .setLaunchedFromPackage(callingPackage)
                .setLaunchedFromFeature(callingFeatureId)
                .setIntent(intent)
                .setResolvedType(resolvedType)
                .setActivityInfo(aInfo)
                .setConfiguration(mService.getGlobalConfiguration())
                .setResultTo(resultRecord)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setComponentSpecified(request.componentSpecified)
                .setRootVoiceInteraction(voiceSession != null)
                .setActivityOptions(checkedOptions)
                .setSourceRecord(sourceRecord)
                .build();
  省略代码······
}

如上代码,ActivityRecord就犹如一个账本,会把启动者的pid、uid,packagename这些信息保存下来,这样通过它就能知道是谁启动了它。并且还会把从PMS查询到的Activity信息ActivityInfo信息保存下来,还有启动该Activity的Intent信息也给保存下来。

创建的ActivityRecord可是非常重要的,因为后面的流程都会使用到它。

创建Task

其实创建Task这一步并不是每一个启动Activity都需要做的事情,只有针对launchMode为singleTask、singleInstance或者启动Activity时Intent中携带了FLAG_ACTIVITY_NEW_TASK数据等情况才会创建Task。

而我现在介绍的启动Activity的前提是:App进程没有被启动,比如从桌面启动Activity。这种情况下在启动Activity时Intent中会携带FLAG_ACTIVITY_NEW_TASK数据,因此会创建Task。上面介绍过创建Task的作用是为了作为ActivityRecord的容器。

创建的Task会把上面的创建的ActivityRecord作为它的孩子保存起来,而该Task也会作为TaskDisplayArea的孩子,并且该Task是位于TaskDisplayArea数组的最后位置 (对于非分屏情况最后位置的Task是处于显示状态)。

小结

启动的Activity的信息ActivityInfo、Intent及调用者等信息就被保存在ActivityRecord中,而ActivityRecord位于新创建的Task的栈顶,而新创建的Task位于TaskDisplayArea数组的最后位置。这样启动Activity的信息也被保存起来了,而作为WindowManagerService侧的容器窗口也被保存在TaskDisplayArea中了,WindowManagerService侧的不论容器窗口还是非容器窗口,它们在Surfaceflinger侧都对应自己的layer。以上这些事情都是在为Activity启动及Activity中View显示做铺垫。

3.4 启动App进程

因为启动Activity的前提是App进程没有被启动,因此如果要想把启动Activity的信息通知给App进程,那最重要的事情是App进程必须启动了,否则只有我ATMS在“自high”没有任何意义。本身启动Activity的事情就是我ATMS和App进程互相配合的一个过程。

启动App进程这个事情确实是超出了我ATMS的能力范围了,那我可得把进程管理模块大佬请出来,让他给大家介绍下相关知识。

大家好,我是进程管理模块关于我的介绍可以看此文,那我就直入主题启动App进程主要分为启动前启动中启动后这三个阶段,同样我也特意绘制了一幅图展示这三阶段的一个关系:

启动进程

那就结合这幅图来介绍下启动App进程的过程。

启动前

既然我是负责进程管理的,像ATMS一样为了管理好Activity,会使用ActivityRecord来记录下启动的Activity,而我同样为了管理好每个App进程,也使用了ProcessRecord这个类来记录每个App进程。那我就先来介绍下ProcessRecord的主要属性吧。

属性说明
processName:String进程的名字
uid:int安装Apk后生成的唯一id
userId:intAndroid设备的用户id,默认userid是0
info:ApplicationInfoAndroidManifest中的Application信息
mPid:intApp进程的进程id,这个可是相当重要的
processName:String通过android:process设置的进程名字
mThread:IApplicationThread通过它可以把消息通过binder通信发送给App进程
mPersistent:boolean为true则代表该进程会“长生不老”
mState:ProcessStateRecord进程状态信息,如进程oom_adj值、进程的proc_state等值

关于ProcessRecord的属性就简单介绍到这,因为它的属性实在是太多了,后面会有专题继续介绍它们。

而启动前的首要工作就是创建ProcessRecord实例,ProcessRecord会把App进程相关的信息都记录下来,请记住我有一个lru的缓存队列,我通过该缓存队列来管理所有的进程,如下代码:

//ProcessList

//lru缓存队列
private final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();

而刚刚创建的ProcessRecord对象可是还不能加入到该缓存队列中,只有App进程“告知”AMS它准备好后,才可以把它加入 (关于“告知”下面会介绍) 。

接着还会为下一步准备entryPoint、uid、seInfo等相关的数据,这些数据在启动App进程时候可是要传递给zygote进程的。其中entryPoint的值为android.app.ActivityThread,也就是我会告诉zygote进程fork出的App进程,它的入口类是哪个类。

该步创建的ProcessRecord对象非常的有用,它可是后面步骤的基础,那就进入下一步流程。

启动中

大家都知道fork App进程的工作是由zygote进程完成的,而要想请求zygote进程fork一个App进程,那就只能由我进程管理模块通过socket通信,把fork App进程的请求发送给zygote进程,跟随请求一起发出去的还有在启动前准备的各种数据。剩下的事情就都交给zygote进程了,而我能做到的事情就是“慢慢的等待”。

当zygote进程fork App进程成功或失败后,都会把最终结果发送给我,而这个结果里面最重要的就是pid,如果pid大于0则代表fork App进程成功;否则代表失败。既然拿到了fork App进程的结果,那看下拿到结果后都做了哪些事情。

启动后

启动后其实就是依据上一步的结果来做一些事情,下面列出了fork App进程成功后要做的几件重要事情:

  1. 启动前创建了ProcessRecord对象,它的pid值可还是0呢,因此需要把zygote进程传递过来pid设置给ProcessRecord对象的pid属性
  2. 把App进程fork成功的消息发送给AMS以及其他的服务比如BatteryStatsService
  3. 进行日志记录把App进程fork成功的pid、时间点等信息记录下来
  4. 启动前创建的ProcessRecord对象还没有放在进程管理模块的lru缓存队列中,它需要先被放入AMS的mPidsSelfLocked属性中,该属性是PidMap类型的,以pid为key值,value值为ProcessRecord对象。如下是相关代码:
//ActivityManagerService

final PidMap mPidsSelfLocked = new PidMap();

还有一件最重要的事情就是启动监听App进程“告知”机制,是不是看了很懵逼吧?先来解释下“告知”是啥意思。

因为App进程被zygote进程fork成功后,会在App进程内执行ActivityThread的main方法,这时候App进程会做一些准备工作 (具体可以参考 此文 ,当准备工作完成后是需要通过binder通信调用AMS的attachApplication方法把App进程准备好的消息“告知”AMS的,这也就是“告知”的含义。“告知”除了告诉AMS App进程准备好之外,还有一件事情就是把IApplicationThread binder服务传递给AMS,这样AMS有任何消息就可以它传递给App进程了,它可是AMS发送消息给App进程的“桥梁”。

监听App进程“告知”机制就是App进程需要再10s内调用AMS的attachApplication方法,如果10s内没有调用,则会进行相应处理,比如把创建的ProcessRecord等信息销毁掉。(10s这个值取自在AMS的PROC_START_TIMEOUT属性)

下面是相应代码,有兴趣可以看下:

//ProcessList

private void handleProcessStart(final ProcessRecord app, final String entryPoint,
            final int[] gids, final int runtimeFlags, int zygotePolicyFlags,
            final int mountExternal, final String requiredAbi, final String instructionSet,
            final String invokeWith, final long startSeq) {
        //在一个线程中启动进程工作
        final Runnable startRunnable = () -> {
            try {
                //请求fork app进程
                final Process.ProcessStartResult startResult = startProcess(app.getHostingRecord(),
                        entryPoint, app, app.getStartUid(), gids, runtimeFlags, zygotePolicyFlags,
                        mountExternal, app.getSeInfo(), requiredAbi, instructionSet, invokeWith,
                        app.getStartTime());

                synchronized (mService) {
                    //fork app进程成功后,下面方法执行启动后的操作
                    handleProcessStartedLocked(app, startResult, startSeq);
                }
            } 
            省略代码······
        };
        省略代码·····
    }
小结

谢谢进程管理模块帮大家介绍启动App进程的知识,那我ATMS就斗胆小结下: 进程管理模块启动App进程成功后,就等着App进程调用AMS的attachApplication方法把App进程准备好的消息“告知”AMS了。同样ATMS启动该Activity也处于暂停阶段,因为只有知道App进程已经准备好了,才能把启动Activity的消息发送给它。如果在10s内没有收到App准备好的消息,则ATMS启动该Activity也会停止,并且会清理在启动App进程时创建的ProcessRecord等信息。

App进程启动的文章可以看 此文

3.5 App进程准备好

上天不负有心人,App进程终于调用了AMS的attachApplication方法“告知”AMS它已经准备好了,App进程可是把它的ApplicationThread传递过来了,这样AMS就可以一有消息就可以通过它来告诉App进程了。

AMS收到App进程准备好的信息后,第一件所做的事情就是找到对应的ProcessRecord对象,还记得在启动App进程中创建了一个ProcessRecord对象来保存App进程的信息吗,它可是被保存在AMS的mPidsSelfLocked属性中,因为binder通信是可以拿到对方的pid的,因此通过pid从mPidsSelfLocked属性中拿到ProcessRecord对象。只有找到了ProcessRecord对象以后,后面的流程才可以执行;否则停止执行后面的流程。那咱们进入下一个流程。

3.6 发送初始化数据

App进程只是把它准备好的消息通过AMS的attachApplication方法告知了AMS,这时候的App进程还是一个“空壳”,它需要各种数据来进行初始化,下面罗列了一些关键数据:

数据说明
processName:String进程的名字
appInfo:ApplicationInfoApp相关的重要数据都在这个对象中,比如Apk的文件路径、so库文件路径、包名等
debugMode:intdebug模式
persistent:boolean是否是“长生不老”进程
config:Configuration配置信息

当然上面只是列出了一些关键数据,而这些数据是由AMS是通过IApplicationThread传递给App进程的。App进程拿到这些数据进行初始化操作,比如使用ApplicationInfo中的Apk文件路径、so库文件路径等初始化PathClassLoader类加载器,它可是最关键、最基础的一步,只有PathClassLoader被初始化了,App进程中的各种类才可以被使用。关于初始化更多细节可以看 App端框架之谜---Android四大组件系统系列 这篇文章。

发送初始化数据给App进程后,还有一件事情非常重要就是把App进程传递的IApplicationThread对象保存在ProcessRecord对象中,它可是会被经常使用的。

3.7 发送Activity启动数据

在AMS的attachApplication方法中还会去检查当前App进程是否有还没有启动的Activity,如果有的话就会去继续启动该Activity,这时候的启动就是把Activity的启动数据发送给App进程,而App进程在收到相应数据后,开始Activity的实例化,执行它的onCreate生命周期方法等操作。关于更详细的细节,可以看App端框架之谜---Android四大组件系统系列 这篇文章。

或许你会担心发送初始化数据发送Activity启动数据对于App进程来说收到消息顺序混乱的问题,这个大可放心,因为这两种消息对于App进程来说都会在UI线程中执行它们,并且发送初始化数据要早于后者,因此不会有混乱问题。

3.8 小结

在App进程没有启动的情况下,启动一个Activity,是需要ATMS和App进程互相配合才能完成的工作,而AMTS侧会做以上7步的处理,来保证Activity的顺利启动,凡是这7步中有一步出现错误都会导致Activity的启动失败。

4.总结

本节介绍了ActivityTaskManagerService及它是如何管理Activity,并且它在启动Activity的过程中都做了哪些工作。

欢迎关注我的公众号牛晓伟(搜索或者点击牛晓伟链接)