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

戳蓝字"牛晓伟"关注我哦!

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

本文摘要

本篇本来是应该继续写Activity管理系列之Activity启动 相关的文章的,但是发现在整理整个大纲结构之前,发现Activity启动设计的知识面非常广,比如会涉及到进程管理、包管理、zygote进程、App端框架等。并且通过一篇文章把Activity启动的全貌都展示出来本身就是需要很大的篇幅。 遂决定先把App端框架 这个相对基础的内容介绍给大家,并且App端框架在后面的Service、BroadcastReceiver、ContentProvider内容中都会涉及到。

关于App端框架 这个名字,其实Android官网是没有这样的一个概念或名字的,这个概念完全是我自己创造的,我个人认为这个框架是一直都存在的,只不过没人给它一个名字而已。大家可以想想,四大组件的初始化及回调方法的执行都是被动的行为,而到底是"谁"在做这个事情呢?很明显就是底层框架 在做这些事情,底层框架对于开发者来说是透明的。因此我给这个底层框架 起了个普通的名字App端框架

本文采用自述的方式介绍了App端框架是啥,它是如何帮助App进程启动的,以及它是如何帮助Activity启动的。(文中代码基于Android13)

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

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

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

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

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

本文大纲

1. App端框架为何物

亲爱的朋友们,大家好啊,我是App端框架,在进入今天的主题之前,我先让大家思考个问题:Android提供了Application和四大组件,并且也指定了一套规则:就是在对应的回调方法里面做相应的事情。比如在Application的onCreate方法里面可以监听App进程的启动;比如在Activity的onCreate方法里面需要设置Content View。而Application和四大组件的实例何时被初始化,以及它们的回调方法何时被执行,这些事情开发者统统都不需要关心。那这些事情是谁来做的呢?

那当然是我App端框架 了,在App进程启动后,我会初始化Application对象,并且调用它的onCreate等回调方法。而四大组件的初始化及相应回调方法的调用也是由我来负责 。而至于何时来执行这些操作 ,其实我也做不了主啊,我还得听从ActivityManagerServiceActivityTaskManagerService发出相应的命令,它们会通过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进程启动分为准备告知初始化三个阶段,前一阶段都是在为后一阶段做准备。

  1. 准备阶段Looper在App进程的UI线程里面不断"循环"运行,这样让App进程"活"了起来。
  2. 告知阶段则会通知ActivityManagerService对应的App进程已经准备完毕了,并且会把ApplicationThread传递给ActivityManagerService,这样ActivityManagerService就可以把消息通过ApplicationThread传递给App进程了。
  3. 初始化 阶段则会使用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端框架就执行啥命令。

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

相关推荐
元争栈道12 分钟前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库1 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道2 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
居居飒2 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He5 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗6 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_397562317 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio
峥嵘life7 小时前
Android Studio版本升级那些事
android·ide·android studio
新手上路狂踩坑7 小时前
Android Studio的笔记--BusyBox相关
android·linux·笔记·android studio·busybox
TroubleMaker10 小时前
OkHttp源码学习之retryOnConnectionFailure属性
android·java·okhttp