胡琪

为今天工作,为明天投资,为未来孵化一些东西

从Instant Run看安卓Application的启动流程与替换

Instant Run和安卓Application启动流程有何关系

关于Application启动流程网上有很多文章分析,但绝大多数文章都是为了讲Application启动流程而讲的,因此绝大多数文章就是分析源码流程,但是大家都懂的,Application启动流程的这个源码是相当的的复杂,绝大多数人看了不一定能够很好的理解,因为源码细节太繁琐,只见树木不见森林,没有一个很好的目标感,而我觉得分析源码最好的方式就是带着某个问题去看,即我要分析这个源码是为了解决什么疑惑?这样才能更好的做到点到为止,能放能收,不过分注重细节,也不漏掉某些重要细节(比如关于Instant Run中类加载器部分的逻辑网上绝大部分文章就是一句简单的双亲委派带过,但是实际上Instan Run关于类加载器部分的精妙之处却是其设计思想,不清楚的同学可以看从ClassNotFoundException看java的类加载机制和Instant Run类加载部分的设计)但很遗憾网上的绝大多数文章就是把源码流程分析一遍。最近在看Instant Run相关的代码,发现Instant Run中的替换Application这个过程的细节代码需要了解Application的启动流程,因此觉得这是一个很好的去了解Applicaiton启动流程的契机,即了解Application的启动流程来解决看Instant Run中的替换Application这个过程的细节代码的疑惑。

为何需要替换Applicaiton

那么为何需要替换Application呢?或者说那些业务场景下需要替换Application呢?有些时候我们为了是实现一些特殊的需求,比如在自定义的ClassLoader中加载一个dex的所有代码(当然包括Application类),再比如一般的apk加固是会用自己的Applicaiotn去接管用户的apk的启动,再比如Instant Run,这些场景都有一个共同的特点就是希望自己的某些代码在用户的代码运行之前执行,而我们知道对于应用层而言,用户的Application(假设叫做OriginalApplication)是最早执行的代码,那这就产生了一个悖论,OriginalApplication是最早执行的,在其执行前没有任何入口可以提供给我们去执行我们的代码,因此只能用我们的Application(假设叫做ProxyApplication)替换掉用户的Application,然后在ProxyApplication中去接管执行OriginalApplication的业务逻辑。

如何做到替换Application,从而保证替换之后整个app使用的Application还是原来用户自定义的Application

前面说过Instan Run中存在替换用户自定义Application的过程,我们看下Instant Run是如何实现的,主要逻辑在MonkeyPatcher.java这个类的monkeyPatchApplication函数中。如何下是替换核心逻辑。

可以看到这个函数绝大部分都是注释,而且从注释中可以看到替换Application主要包括3部分:

  1. 将ActivityThread类的mInitialApplication成员替换为真实的Application,所谓真实Application即用户原本定义的Application
  2. 将ActivityThread类的mAllApplications域(为ArrayList<Application>对象)所有的值替换为真实的Application(Replace all instance of the stub application in ActivityThread#mAllApplications with the real one)
  3. 枚举所有的LoadedApk或者PackageInfo对象(这2个对象的作用基本一致,只不过在不同版本的sdk中因为历史原因起名不同而已),将这些对象中的mApplication域设置为真实的Application。同样也要把Application的mLoadedApk设置为该LoadedApk对象。(Enumerate all LoadedApk (or PackageInfo) fields in ActivityThread#mPackages and ActivityThread#mResourcePackages and do two things:1 Replace the Application instance in its mApplication field with the real one.2 Set Application#mLoadedApk to the found LoadedApk instance)

可以看到替换Application对象涉及到2个重要类,ActivityThread和LoadedApk(或者PackageInfo),替换Application就是把ActivityThread对象中已经赋过值的mInitialApplication,mAllApplications和LoadedApk对象中的mApplication替换为用户原本的Application。那么为何替换Application需要对这2个类进行相关操作呢?这就需要从安卓Application的启动流程来找答案了。

替换Application过程中涉及到的Applicaiton启动流程细节

我们都知道当我们在桌面上点击app图标后,安卓的Zygote进程会fork一个新进程作为app的进程空间,然后调用经过一系列的复杂流程后进入到ActivityThread的main函数,因为安卓是用java语言开发的,在java程序中程序的入口即使main函数,安卓也不例外,安卓的入口main函数是ActivityThread的main函数。我们来看下其代码。以下源码流程分析都是基于安卓6.0.0_r1。

为了更好的抓住主要逻辑,省略了很多细节代码,可以看到main函数主要做了2件事

  1. 创建主消息循环,执行loop循环(因为这个不在分析app启动流程的重点中,所以不进行过多介绍)
  2. 创建ActivityThread对象,调用该对象的attach方法。

接下来我们跟进到attach函数中看下

同样的省略若干不重要细节代码,可以看到该函数分2中情况进行处理,系统app和非系统app,因为我们研究的是用户app,所以系统app这块逻辑我们忽略。可以看到在处理用户app时会创建一个ActivityManagerService对象,然后调用该对象的attachApplication方法

传入进去的是mAppThread是ApplicationThread对象,该对象是一个binder对象,用来在ActivityManagerService(以下简称AMS)和ActivityThread这2者之间进行跨进程通信。到这逻辑就跳转到了AMS中了,AMS的attachApplication方法主要逻辑是调用了自己的attachApplicationLocked方法,因此我们直接看下attachApplicationLocked方法。

可以看到该函数主要逻辑是调用了传进来的ApplicationThread的bindApplication方法。这是一个binder跨进程调用,流程逻辑重新回到ApplicationThread的bindApplication方法。跟进到ApplicationThread的bindApplication方法,看下逻辑:

可以看到该方法的主要逻辑就是把一系列参数打包为data,然后通过H这个Handler以BIND_APPLICATION消息发送出去(这里的H和ApplicationThread一样是ActivityThread的内部类),因此接下来我们看下H时如何处理BIND_APPLICATION这个消息的。

可以看到调用了ActivityTread的handleBindApplication这个方法,该方法时Application启动流程最核心的方法,跟进该方法:

可以看到该方法主要做了以下几件事:

  1. 创建app运行时的上下文环境ContextImpl
  2. 调用LoadedApk的makeApplication方法创建Application对象,同时将该对象赋值给ActivityThread的mInitialApplication成员变量,同时需要注意的是LoadedApk的makeApplication方法会把创建的Application对象赋值给自己内部定义的mApplication成员变量
  3. 如果存在ContentProvider,先加载ContentProvider
  4. 通过Instrumentationd的callApplicationOnCreate函数间接调用当前Application的onCreate函数,至此Application启动流程就跑起来了。

其中LoadedApk的makeApplication方法逻辑如下:

可以看到将创建的Application对象添加进了ActivityThread的mAllApplications中,同时也将其赋值给了自己的成员变量mApplication。

现在回过头来看Instan Run替换Application的过程是不是就非常清晰明朗了,而且从这个过程中看到安卓中的四大组件以及Application之所以不能够像java那样直接new一个就可以用是因为涉及到一系列复杂的流程初始化,会在framework层来回牵扯,互相赋值,直接new的四大组件和Application对象没有这些赋值过程,因此不具备生命周期,上下文环境等一些安卓上特有的属性,另外也可以看到ContenProvider的执行是在Applicaton的onCreate之前。

总结

先总结下Application的启动流程的主要逻辑

  1. ActivityThread#main->ActivityThread#attach->ActivityManagerService#attachApplication,此时流程逻辑跳转到了AMS
  2. AMS#attachApplication->ActivityManagerService#attachApplicationLocked->
    ApplicationThread#bindApplication,此时AMS通过传进来的ApplicationThread进行binder通信调用ApplicationThread的bindApplication,此时逻辑重新回到ActivityThread
  3. 在bindApplication中通过H这个Handler发送BIND_APPLICATION消息,该消息被ActivityThread#handleBindApplication处理
  4. handleBindApplication是Application的核启动流程的核心方法,在该方法中会直接或间接创建LoadedApk对象,创建app上下文环境ContextImpl,创建Application

再总结下替换Application的主要逻辑

  1. 将ActivityThread类的mInitialApplication成员替换为真实的Application
  2. 将ActivityThread类的mAllApplications这个ArrayList<Application>对象的所有的子元素替换为真实的Application
  3. 枚举所有的LoadedApk或者PackageInfo对象,将这些对象中的mApplication域设置为真实的Application。同样也要把Application的mLoadedApk设置为该LoadedApk对象

 

 

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注