通过对嵌入式企鹅圈原创团队成员degao之前发表的《Android Small插件化框架源码分析》的学习,对Android使用的插件化技术有了初步的了解,但还是有很多需要认真学习的地方,特别是大部分知识都需要结合虚拟机和Androidframwork的原理才能慢慢理解。比如,文中作者提到了插件化框架要解决的三个核心问题:

  1)插件类的加载;

  2)插件资源的处理;

  3)Activity注册和生命周期问题;

  其中第3点作者是这样解释的,“大部分插件化框架解决办法都是采用在宿主工程里预先注册Activity占坑,然后通过占坑Activity将生命周期回调传回给插件Activity的方式。这里Small处理的比较有特色,通过替换 ActivityThread 里的mInstrumentation,在Instrumentation的newActivty实现里面实例化了插件Activity,通过较小改动就能完全解决生命周期回调的问题。”。

  这里面提到的ActivityThread、Instrumentation、newActivity是什么东西呢?该如何理解“预先注册Activity占坑”、“替换 ActivityThread 里的mInstrumentation”这些步骤呢?带着这些问题,我学习了Android framework中启动Activity的源代码,基本上搞懂了Small插件化框架是如何处理和控制Activity生命周期的了,在这里总结一下这个学习过程。

  我们App中Activity的生命周期由ActivityManagerService来管理,它决定着什么时候该调用onCreate、onResume这些生命周期的回调函数,把Activity放在哪个栈里,上下文之间的关系是怎样的等等。当我们把一个插件apk用classLoader加载进宿主应用后,插件apk里的Activity并不能被ActivityManagerService所管理,因为它只是作为一般的class被加载到宿主的进程空间中,而没有在AndroidManifest.xml中声明,因此通过startActivity启动插件里的activity时会抛出异常:

“android.content.ActivityNotFoundException: Unable to find explicit activity class… … … …

have you declared this activity in your AndroidManifest.xml?”

  显然,宿主应用自己来管理插件中activity的生命周期有很大难度,最好还是把这项工作交给ActivityManagerService。可以预先在AndroidManifest.xml中定义一些空的Activity,给它们预先起上特定的名字(ProxyActivty$1),当要启动插件里的activity(PluginActivity1)时,Small框架就先启动ProxyActivty$1,这样它的生命周期就会交由AMS去管理了,在真正构造和加载Activity的地方,再用PluginActivity1这个类的实例去替换ProxyActivty$1的实例。这样一来,AMS中管理的是ProxyActivty$1这个activity,但实际上在宿主应用进程中实例化的却是插件里的PluginActivity1,它里面的onCreate、onResume等函数会被依次调用,生命周期等问题就交由AMS去控制了,相当于欺骗了AMS和系统。

  这就是解决Activity注册和生命周期问题的方法。要理解这个过程,需要分三步走:

  1)App所在的客户端进程和AMS所在的system_server进程之间是如何交互的;

  2)搞清启动一个Activity的前前后后,特别是从客户端进程进入到AMS的入口,和从AMS返回到客户端进程的入口,从中找到activity是怎样被交给AMS的,activity是在哪儿实例化的;

  3)要关注上面这个过程中具体涉及到的类和方法,从中选择合适的Hook点,用Java反射的方法来进行动态替换,自己实现偷梁换柱的动作。

一、App和AMS通过Binder交互

1. AMS的初始化

  ActivityManagerService运行在system_server进程中,在SystemServer启动后完成初始化工作,然后加入到ServiceManager中。ServiceManager管理所有的Android系统服务,客户端应用如果要使用系统服务,调用getSystemService接口,ServiceManager就会返回给客户端应用对应的系统服务的IBinder对象。

在SystemServer的run函数中会调用startBootstrapServices先启动AMS、PMS这样关键的系统服务,它里面会初始化AMS。先调SystemServiceManager的startService方法,注意传入的参数不是ActivityManagerService.class,而是它的一个静态内部类Liftcycle。

  startService会调用同名的函数,用反射构造出传入class的实例,也就是ActivityManagerService.Liftcycle的实例并返回。

  然后再调Lifecycle的getService函数得到ActivityManagerService的实例,把它赋给SystemServer中的mActivityManagerService。

  在startBootstrapServices最后会调setSystemProcess把AMS加到ServiceManager中。

2. AMS在服务端的代理ActivityManagerNative

  Android Binder机制在客户端和服务端各有一个代理Proxy和Stub,它们之间通过transact和onTransact进行进程间通信,AMS在服务端的代理是ActivityManagerNative,它和客户端的代理ActivityManagerProxy进行Binder通信,调用AMS提供的各种功能。AMS对外提供的接口是IActivityManager,它里面包含了AMS对外提供服务的全部函数,有startActivity、startService、registerReceiver等等,ActivityManagerNative实现了IActivityManager,但它只是一个抽象类,只用于和客户端的Binder通信,startActivity等的具体实现在它的子类ActivityManagerService中完成。

3.AMS在客户端的代理ActivityManagerProxy

  应用App使用AMS提供的功能,比如startActivity,是通过AMS在客户端的代理ActivityManagerProxy发起的。这里ActivityManagerNative的getDefault方法返回就是ActivityManagerProxy的实例。

  getDefault返回的是ActivityManagerNative中的一个单例gDefault,它通过ServiceManager获取到AMS的IBinder,然后再通过asInterface得到ActivityManagerProxy。

  ActivityManagerNative的asInterface方法在客户端和服务端都会被调用。在客户端,因为IBinder在native的实现BpInterface没有重载queryLocalInterface,所以返回它的缺省实现,缺省为null,走new ActivityManagerProxy的分支。可见,ActivityManagerProxy的asBinder返回的就是从ServiceManager中取到的AMS的IBinder对象。

4. ActivityThread在客户端的代理ApplicationThreadNative

  与服务端相似,App在客户端进程中完成实例化Activity、调用onCreate等生命周期函数的功能,也不能被AMS直接调用,而是通过自己在客户端的代理ApplicationThreadNative来处理。虽然代理的名字有Thread字样,但它并不表示App所在的进程,ActivityThread才是描述客户端进程的类。也就是说当新创建一个应用进程时,系统就会为我们新构造一个ActivityThread对象。IApplicationThread是个接口,里面有scheduleLaunchActivity、scheduleCreateService等需要在客户端进程中调用的方法。ApplicationThreadNative是个Binder,负责与它在服务端的代理ApplicationThreadProxy通信,同时也实现了IApplicationThread接口,但具体的实现放在了它的子类ApplicationThread中。

  ApplicationThread对象被客户端应用进程ActivityThread所持有,ActivityThread与AMS的交互实际上遵循了设计模式中的代理模式:

  üIApplicationThread是抽象对象角色,提供了要使用的业务逻辑的抽象接口。

  üActivityThread是目标对象角色,AMS不能直接与它交互、直接使用它的功能,都是通过它的代理类来进行。

  üApplicationThread是代理对象角色,是ActivityThread的代理类,实现了具体的业务逻辑。与一般的代理模式不同,它不是直接持有ActivityThead的一个引用,而是把处理的请求发到ActivityThread内部的一个Handler上。

5.ActivityThread在服务端的代理ApplicationThreadProxy

  与获取ActivityManagerNative在客户端的代理ActivityManagerProxy的过程类似,ApplicationThreadNative在服务端,也就是AMS所在的system_server进程中的代理,也是通过调用一个叫asInterface的函数来获得的。

  可见返回的ApplicationThreadProxy对象是IApplicationThread这个接口的类型,在ActivityManagerNative.java的onTransact中会用到这个客户端的代理,比如在App中调用startActivity时,ActivityManagerNative在服务端的onTransact函数里会调用AMS的startActivity,这时就通过ApplicationThreadNative.asInterface得到ApplicationThreadProxy,把它作为startActivity的参数传给AMS。ApplicationThreadProxy在这儿的用处是作为app的代理,代表这客户端的ActivityThread,因为AMS要处理多个客户端进程的请求,所以通过这个代理可以得到客户端的pid、uid、ProcessRecord等信息,还会通过它发起对客户端的Binder调用。

6.小结

  Android的命名可以帮助我们区分这些类。ActivityThread代表了应用所在的进程,xxxNative是在本进程内的Binder代理类,xxxProxy是在对方进程的Binder代理类,熟悉Binder机制的话可以想到Binder在C++层的实现也是这样命名BnXXX和BpXXX的。

二、startActivity的过程

1.从App进入AMS的过程

  调用startActivity有两种情况,一是Context(实际上是它的具体实现ContextImpl)调startActivity,一是Activity调startActivity。最终它们都会调用Instrumentation的execStartActivity。图15是ContextImpl调用的代码,mMainThread就是ActivityThread,Instrumentation是它里面的一个成员。图16是Activity调用的代码,startActivity先调用了startActivityForResult,在startActivityForResult中再调用了Instrumentation的execStartActivity。注意,在Context中调用startActivity时,因为上下文中没有Activity的栈,所以要加上FLAG_ACTIVITY_NEW_TASK这个flag。Instrumentation是工具、插桩的意思,顾名思义Activity只是在执行时经过Instrumentation,以便它起到监控和测试的功能。

  execStartActivity中通过调用ActivityManagerNative.getDefault()获得AMS在客户端的代理对象ActivityManagerProxy,它是ActivityManagerNative类中的一个静态单例对象。然后调用它的startActivity方法,实际上是通过binder的transact发出命令,由AMS客户端的代理ActivityManagerNative的onTransact来处理,

  App请求AMS的其他函数调用过程与此相似,无一例外的都是经过ActivityManagerNative里面的单例对象gDefault,它是从客户端进程发起调用进入AMS的中转站,这为我们提供了一个hook点,按照一开始所设想的那样,原来startActivity启动的是插件中的PluginActivity1,在这里可以hook gDefault,这样所有客户端向AMS发起的调用,比如startService之类的都可以被我们截获,然后就可以修改其中的参数,替换成含有AndroidManifest.xml中预先占坑的ProxyActivity$1的Intent了。对宿主应用来说,它启动的是PluginActivity1,对AMS来说,它管理的activity是ProxyActivity$1。

2.在AMS中处理的过程

  AMS中处理startActivity的过程比较复杂,主要涉及了ActivityManagerService、ActivityStackSupervisor、ActivityStack、PackageManagerService、WindowManagerService等几个类。

  在ActivityManagerService中,startActivity先是调用了startActivityAsUser,然后在startActivityAsUser中调用了ActivityStackSupervisor的startActivityMayWait。注意,第一个参数caller就是我们前面说过的app在服务端的代理ApplicationThreadProxy,它是一个Binder对象,实现了IApplicationThread。转到ActivityStackSupervisor中后,又在它和ActivityStack之间来回调用了许多次,主要是进行栈和Task的管理、解析Activity的信息,准备窗口的切换之类的工作,最后回到了ActivityStackSupervisor中,调用realStartActivityLocked函数。

  在realStartActivityLocked函数中,app是ProcessRecord类型,app.thread是IApplicationThread类型,也就是从客户端的代理ApplicationThreadProxy,在这儿调用了它的scheduleLaunchActivity方法,接下来就会回到app的进程空间里。

我们无法在AMS中去hook类和方法,因为它在system_server进程中,我们没办法跨越进程的鸿沟,当然也没有权限注入到system_server进程中。而且,AMS中的处理流程非常复杂,都是栈的管理之类的逻辑,很难找到hook点,来把占坑的Activity替换回插件里的activity,所以我们还是得回到app进程中。

3.重新回到app进程

  前面说过在ApplicationThreadNative中会处理AMS对ActivityThread的binder调用,它的onTransact函数会调用scheduleLaunchActivity,其具体实现在ApplicationThread中。ApplicationThread是ActivityThread里的一个内部类,它的scheduleLaunchActivity的实现就是发一个LAUNCH_ACTIVITY类型的message到ActivityThread中的一个handler上。

  这个名为H的handler中用handleLaunchActivity函数来处理AMS发起的scheduleLaunchActivity调用。handleLaunchActivity里又调用了performLaunchActivity。

  performLaunchActivity中又用到了Instrumentation类,调它的newActivity函数构造出activity对象。newActivity函数很简单,直接用classLoader加载了Activity类,然后用反射调它的构造函数newInstance出activity实例。

  然后再回到performLaunchActivity中,在通过netActivity得到activity的实例后,接下来就该调用它的生命周期函数onCreate了,照旧还是通过Instrumentation来完成,调用它的callActivityOnCreate函数。

  在callActivityOnCreate里会调用Activity的performCreate函数,它里面调用的就是我们熟悉的onCreate了。

  到此为止,我们简单地跟踪了startActivity的整个流程,根据前面的设想,这里的Activity还是占坑的ProxyActivity$1,要是能把它替换回插件的PluginActivity1就好了。从前面的流程可以看出,Instrumentation这个类是startActivity整个过程中的必经之路,无论是从app到AMS,还是从AMS回到app都会经过它,要是能hook它就好了,因为它是ActivityThread里的一个成员mInstrumentation,所以我们在客户端进程中可以通过反射拿到ActivityThread对象,也可以拿到mInstrumentation。

三、利用反射完成动态代理

  前面我们梳理了startActivity的整个调用流程,发现在app和AMS之间来回调用的过程中有两个可以hook的点,下面我们来具体分析一下:

  1)从app进程进入到AMS中,都是通过AMS在客户端进程的代理ActivityManagerNative类里的静态单例对象gDefault来发起binder调用的,它是ActivityManagerProxy类型。我们可以在这个地方换掉startActivity传入的参数,把占坑的ProxyActivity$1传给AMS。但是从AMS返回到app里,调用scheduleLaunchActivity等一系列动作却不会经过它,而是经过客户端的代理ApplicationThreadNative发起对客户端的binder调用,但如果hook ApplicationThreadNative也不能解决问题,因为最后真正构造出activity实例的步骤是在Instrumentation的newActivity函数中,ApplicationThreadNative对象离着它还很远。

  2)参考第一部分我们分析的流程,从app进入AMS和从AMS返回到app,在客户端进程中都要经过Instrumentation这个类。从app进入AMS时,调用它的execStartActivity函数,在execStartActivity中再调ActivityManagerNative.getDefault().startActivity,发起binder调用。从AMS返回app时,调用它的newActivity,用发射构造出activity的实例,所以Instrumentation才是我们需要hook的类,可以在进入AMS时把插件的PluginActivity1换成占坑的ProxyActivity$1,在回到app构造真正的activity时,new出插件的PluginActivity1。这样一来,对AMS来说它管理的是ProxyActivity$1,对宿主应用来说,插件PluginActivity1在它的进程里已经被实例化和运行了

现在我们来看Small框架中的代码是处理的。

  ActivityThread中有一个单例对象sCurrentActivityThread,它是在attche时被赋值的。前面说过Instrumentation在ActivityThread中的实例是其成员mInstrumentation,所以我们先通过ActivityThread的静态方法currentActivityThread得到它的实例sCurrentActivityThread,然后找到它的成员mInstrumentation,把它的引用对象换成自己实现的InstrumentationWrapper。InstrumentationWrapper也是Instrumentation类型,它里面实现了execStartActivity和newActivity这两个方法,用来替换掉原来的函数调用。

  其中,execStartActivity函数在不同android版本中的参数有所不同,wrapIntent函数完成了替换Intent的动作,把PluginActivity1的Intent替换成了ProxyActivity$1的Intent。最后在ReflectAccelerator中完成原来函数的调用。

  newActivity中的unwrapIntent是wrapIntent的反向工作,得到的className是插件中的PluginActivity1的类名,最后再调用原来的newActivity完成activity的构造。

  这样最终完成了用PluginActivity1替换AndroidManifest.xml中的ProxyActivity$1的偷天换日的工作,插件里的PluginActivity1既可以被正常的加载运行也不用担心它的生命周期之类的管理了。

      嵌入式企鹅圈原创团队由阿里、魅族、nvidia、龙芯、炬力、拓尔思等资深工程师组成。百分百原创,每周两篇,分享嵌入式、Linux、物联网、GPU、Android、自动驾驶等技术。欢迎扫码关注微信公众号:嵌入式企鹅圈,实时推送原创文章!

Android Small插件化框架解读——Activity注册和生命周期的更多相关文章

  1. Android Small插件化框架源码分析

    Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...

  2. 自己动手写Android插件化框架,让老板对你刮目相看

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由达文西发表于云+社区专栏 最近在工作中接触到了Android插件内的开发,发现自己这种技术还缺乏最基本的了解,以至于在一些基本问题上浪 ...

  3. [置顶] 滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理

    上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是Vir ...

  4. Android插件化框架

    1.   dynamic-load-apk/DL动态加载框架 是基于代理的方式实现插件框架,对 App 的表层做了处理,通过在 Manifest 中注册代理组件,当启动插件组件时,首先启动一个代理组件 ...

  5. Android 全面插件化 RePlugin 流程与源码解析

    转自 Android 全面插件化 RePlugin 流程与源码解析 RePlugin,360开源的全面插件化框架,按照官网说的,其目的是“尽可能多的让模块变成插件”,并在很稳定的前提下,尽可能像开发普 ...

  6. MVC 插件化框架支持原生MVC的Area和路由特性

    .NET MVC 插件化框架支持原生MVC的Area和路由特性 前面开放的源码只是简单的Plugin的实现,支持了插件的热插拔,最近晚上偶然想到,原生的MVC提供Areas和RouteAtrribut ...

  7. 开源的JavaScript插件化框架MinimaJS

    本文介绍我开发的一个JavaScript编写的插件化框架——MinimaJS,完全开源,源码下载地址:https://github.com/lorry2018/minimajs.该框架参考OSGi规范 ...

  8. Activity完整的生命周期

    首语:群里看到一位网友说:你能说出Activity的完整生命周期吗?看到这句话,我也在反思自己,我也是个fresh,所以想找个时间仔细的扒一扒Activity生命周期. 首先拿一张简单而又复杂的生命周 ...

  9. Android系列之Fragment(二)----Fragment的生命周期和返回栈

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

随机推荐

  1. Objective-C Mojo和Django 对接

    最近在做资料类app需要一个好的资源管理工具,excel在这方面非常好,不过能第一非常low,第二数据量一大查询就是个问题. 因此,我使用django做了个资源管理小工具.好处还是很明显的 (1)可以 ...

  2. chrome浏览器插件启动本地应用程序

    chrome浏览器插件启动本地应用程序 2014-04-20 00:04:30|  分类: 浏览器插件|举报|字号 订阅     下载LOFTER我的照片书  |     chrome的插件开发这里就 ...

  3. 友盟消息推送和更新XML配置

    <receiver android:name="com.umeng.message.NotificationProxyBroadcastReceiver" android:e ...

  4. Cocos2d-x 网络资源

    blog: http://www.cnblogs.com/mmidd/tag/Cocos2d-x/ http://blog.csdn.net/u012945598

  5. 关于js中this的疑问

    学习bootstrap.js源码中被js里边的this绕的有点晕 /* ================================================================ ...

  6. PSObject

    PSBASE the raw view of the object PSADAPTED the fully adapted view of the object PSEXTENDED just the ...

  7. 201521123064 《Java程序设计》第2周学习总结

    1. 本章学习总结 1.学会使用码云管理代码,包括将本地的代码上传至码云,和将码云上的项目保存至本地. 2.将码云上项目保存至本地的过程中,若eclipse窗口中已有同名项目,则导入的过程中可能会出错 ...

  8. Batch update returned unexpected row count from update [0] 异常处理

    在one-to-many时遇到此异常,本以为是配置出错.在使用s标签开启debug模式,并在struts2主配置文件中添加异常映射,再次提交表单后得到以下异常详情. org.springframewo ...

  9. Android SDK下载失败的解决方法

    Android SDK下载失败的解决方法 图1 在下载过程中,Android SDK Manager Log中出现下面出错信息: Preparing toinstall archives Downlo ...

  10. (区间dp + 记忆化搜索)Treats for the Cows (POJ 3186)

    http://poj.org/problem?id=3186   Description FJ has purchased N (1 <= N <= 2000) yummy treats ...