内容简介:在上一篇文章因此,现有的插件化框架都会有一套越过AndroidManifest.xml注册而启动Activity的机制。本文的目的就是分析这套机制。不考虑多进程的情况,Activity的启动是Binder双向通信的一个过程,它由我们的app进程和AMS所在的system_server进程共同完成。system_server是一个系统级的进程,其内部的AMS负责管理所有app的Activity状态。因此,Android是不允许我们对AMS进行修改的。那么,插件化技术只能在我们app进程里做文章了。
在上一篇文章 Android插件化之ClassLoader 中,我们已经可以成功加载apk,但是还没有办法启动插件中的Activity。我们知道,如果要启动一个Activity,那么这个Activity必须在AndroidManifest.xml中注册。因此,如果我们要启动插件中的Activity,那么这个Activity事先必须在宿主的AndroidManifest.xml中预注册。这样一来就会有两个问题:
- 插件中的Activity在宿主的AndroidManifest.xml中注册,开发不太友好。
- 只能启动插件中事先定义好的Activity,没有办法动态增加Activity。
因此,现有的插件化框架都会有一套越过AndroidManifest.xml注册而启动Activity的机制。本文的目的就是分析这套机制。
Activity的启动流程
不考虑多进程的情况,Activity的启动是Binder双向通信的一个过程,它由我们的app进程和AMS所在的system_server进程共同完成。system_server是一个系统级的进程,其内部的AMS负责管理所有app的Activity状态。因此,Android是不允许我们对AMS进行修改的。那么,插件化技术只能在我们app进程里做文章了。
下图我根据6.0的源码画出了Activity的启动流程图,我省略了我们不需要关心的AMS部分。
正如上图所示,我将Activity的启动流程分为了两部分。上部分描述了app从发起启动activity请求到AMS接受到该请求的过程。下部分描述了AMS处理完成后响应app进程启动Activity的过程。
请求阶段
上图无论是Activity的statActivity()还是startActivityForResult()最终都调用了Instrumentation.execStartActivity()。我们继续定位到Instrumentation.execStartActivity()。
execStartActivity()非常简单。它将传进来的参数进一步包装后传给ActivityManagerNative.getDefault()的startActivity()。ActivityManagerNative.getDefault()返回的其实是一个ActivityManagerProxy对象。ActivityManagerProxy是ActivityManagerService在app进程中的Binder代理对象。调用ActivityManagerProxy.startService()最后会调用ActivityManagerService.startService()。这样请求就到了ActivityManagerService。ActivityManagerNative.getDefault()如下:
到此,请求启动Activity的过程就分析完了。
响应阶段
前面我们提到过,在不考虑多进程的情况下,Activity的启动过程是一个Binder双向通信的过程。AMS要主动与app进程通信要依靠请求启动Activity阶段传过来的IBinder对象,这个IBinder对象就是上面介绍过的Instrumentation.execStartActivity()中的 whoThread对象,它实际上是一个ApplicationThreadProxy对象,用来和ApplicationThread通信。AMS通知app进程启动Activity是通过调用ApplicationThreadProxy.scheduleLaunchActivity()完成的。根据Binder通信,ApplicationThread.scheduleLaunchActivity()会被调用。我们就从ApplicationThread.scheduleLaunchActivity()开始分析。
scheduleLaunchActivity()将从AMS中传过来的参数封装成ActivityClientRecord对象,然后将消息发送给mH,mH是一个Handler对象。
H是ActivityThread的内部类,继承自Handler,它在收到LAUNCH_ACTIVITY的消息后,会调用ActivityThread.handlerLaunchActivity()。
handleLaunchActivity()主要调用了两个方法:performLaunchActivity()和handleResumeActivity()。performLaunchActivity()会完成Activity的创建,以及调用Activity的onCreate()、onStart()等方法。handleResumeActivity()会完成Activity.onResume()的调用。我们继续跟踪performLaunchActivity()。
上述代码在关键位置都加了注释。通过注释我们明白了Activity的创建以及onCreate()的调用都是在Instrumentation中完成的。这里需要注意一下Activtiy.attach()的调用时机,我们会在下文中用到。Instrumentation具体的代码如下:
到此AMS通知app进程启动Activity的流程就结束了。
偷天换日,Hook Instrumentation越过AndroidManifest检测
以上内容算是内功部分,接下来就到了学习具体招式的时候了。
要启动没有在AndroidManifest.xml中注册的Activity,其核心是就是偷天换日。怎么做呢?通过一个例子说明。
假如在插件中有一个未在AndroidManifest.xml注册的TargetActivity,我们想启动它,可以分为三步。
- 在AndroidManifest.xml中预先注册一个我们项目中没有的Activity,例如ProxyActivity。我们把这种行为称为插桩。
- 在请求启动Activity阶段,我们把TargetActivity替换成AndroidManifest中预先注册的ProxyActivity。
- 在AMS响应阶段,Activity实例产生之前,我们再做一个完全相反的动作。即把响应信息中要启动的ProxyActivity替换回TargetActivity。
第一步十分简单,没什么好说的。要实现第二步和第三步就需要用到Activity启动流程的知识了。
在Activity启动流程中,Instrumentation无论在请求阶段还是响应阶段都扮演着重要的角色。在请求阶段Instrumentation.execStartActivity()会被调用,而在响应阶段Instrumentation.newActivity()会被调用。因此如果我们可以Hook Instrumentation,那么我们就可以在execStartActivity()和newActivity()分别完成第二步和第三步中的功能。
再谈Instrumentation
ActivityThread中的Instrumentation
我们知道,每一个 Java 程序都有一个main()方法。Android App的main()方法就在ActivityThread中。
在main()方法中,ActivityThread会被初始化并最终把对象保存在静态的sCurrentActivityThread中。在一个app进程中只有一个ActivityThread实例sCurrentActivityThread。sCurrentActivityThread可以通过ActivityThread.currentActivityThread()拿到。
attach()中,mgr.attachApplication(mAppThread)这段代码又是一个Binder双向通信的过程,它主要为创建Application对象服务。整个通信过程和Activity启动过程类似,我就不再详细介绍了。在通信的最后,ActivtiyThread.handleBindApplication()被调用,而在方法内部,Instrumentation被初始化。
总结一下,一个App进程,只有一个ActivityThread对象,这个对象保存在sCurrentActivityThread中,可以通过ActivityThread.currentActivityThread()获取。ActivityThread的mInstrumentation会在Application创建之前初始化。
Activity中的Instrumentation
Activtiy中的Instrumentation是通过Activity.attach()传进来的。
Activity.attach()在介绍Activity启动流程时提到过。它会在ActivityThread.performLaunchActivity()中被调用。
这样ActivtyThread把自己内部的Instrumentation传递到了Activity中。
Hook Instrumentation
通过以上分析,我们知道,要Hook app的Instrumentation,只需要替换掉ActivityThread的Instrumentation即可。但是,Android SDK没有为我们提供任何关于ActivityThread的api。要访问Android SDK中不存在的类或方法,我们学习一下VirtualAPK是怎么做的。
在VirtualAPK中有个叫AndroidStub的module。它的结构如下:
VirtualAPK又重新声明了这些Android SDK没有提供的Framework层的类。这些类只有方法的声明,如ActivityThread中的内容如下:
这样我们就可以使用这些Android SDK没有提供的类或隐藏的方法了。需要注意的一点是,AndroidStub应该只参与编译过程,这很简单,用compileOnly依赖就可以了。
接下来,我们就可以通过反射替换ActivitThread的Instrumentation了。代码如下:
上面的VAInstrumentation是对系统Instrumentation的代理类。在VAInstrumentation的内部我们可以加入任何我们想要的逻辑。
在Instrumentation.execStartActivity()执行前将我们要启动的Activity替换成预注册的ProxyActivity。
在Instrumentation.newActivity()执行前将预注册的ProxyActivity替换回我们要启动的Activity。
结尾
在Android插件化开篇中我说过,每一篇文章的最后都会是一个Demo,这些Demo串联起来就是一个插件化框架,所以我在Github上建了一个项目 VirtualApkLike ,Demo都会以不同的分支放到这里。本文的Demo在 startActtivity 分支上。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 只需双击,老铁!debugserver快速启动插件
- JFinal-event 3.0.0 发布,插件启动速度提升十倍
- Tomcat 7 启动分析(一)启动脚本
- dotnet 启动 JIT 多核心编译提升启动性能
- Win32 程序在启动时激活前一个启动程序的窗口
- 死磕Android_App 启动过程(含 Activity 启动过程)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beautiful Code
Greg Wilson、Andy Oram / O'Reilly Media / 2007-7-6 / GBP 35.99
In this unique work, leading computer scientists discuss how they found unusual, carefully designed solutions to difficult problems. This book lets the reader look over the shoulder of major coding an......一起来看看 《Beautiful Code》 这本书的介绍吧!