相关阅读
插件化知识梳理(1) - Small 框架之如何引入应用插件
插件化知识梳理(2) - Small 框架之如何引入公共库插件
插件化知识梳理(3) - Small 框架之宿主分身
插件化知识梳理(4) - Small 框架之如何实现插件更新
插件化知识梳理(5) - Small 框架之如何不将插件打包到宿主中
插件化知识梳理(6) - Small 源码分析之 Hook 原理
插件化知识梳理(7) - 类的动态加载入门
插件化知识梳理(8) - 类的动态加载源码分析
插件化知识梳理(9) - 资源的动态加载示例及源码分析
插件化知识梳理(10) - Service 插件化实现及原理
一、前言
至此,花了四天时间、五篇文章,学习了如何使用Small
框架来实现插件化。但是,对于我来说,一开始的目标就不是满足于仅仅知道如何用,而是希望通过这一框架作为平台,学习插件化中所用到的知识。
对于许多插件化的开源框架而言,一个比较核心的部分就是Hook
的实现,所谓Hook
,简单地来说就是在应用侧启动A.Activity
,但是在AMS
看来却是启动的B.Activity
,之后AMS
通知应用侧后,我们再重新替换成A.Activity
。
在阅读这篇文章之前,大家可以先看一下之前的这篇文章 Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现 ,Small
其实就是通过替换这一双向通信过程中的关键类,对调用方法中传递的参数进行替换,来实现Hook
机制。
二、源码分析
Hook
的过程是Small
预初始化的第一步,就是我们前面在自定义的Application
构造方法中所进行的操作:
public class SmallApp extends Application {
public SmallApp() {
Small.preSetUp(this);
}
}
在Small
的preSetUp(Application context)
函数中,做了下面的两件事:
-
实例化三个
BundleLauncher
的实现类,添加到Bundle
类中的静态变量sBundleLaunchers
中,这三个类的继承关系为:
依次调用这个三个实现类的
onCreate()
方法。
public static void preSetUp(Application context) {
//1.添加关键的 BundleLauncher。
registerLauncher(new ActivityLauncher());
registerLauncher(new ApkBundleLauncher());
registerLauncher(new WebBundleLauncher());
//2.调用 BundleLauncher 的 onCreate() 方法。
Bundle.onCreateLaunchers(context);
}
对于之前添加进入的三个实现类,只有ApkBundleLauncher()
实现了onCreate()
方法,其它两个都是空实现。
protected static void onCreateLaunchers(Application app) {
//调用之前添加进入的 BundleLauncher 的 onCreate() 方法。
for (BundleLauncher launcher : sBundleLaunchers) {
launcher.onCreate(app);
}
}
我们看一下ApkBundleLauncher
的内部实现,这里就是Hook
的实现代码:
@Override
public void onCreate(Application app) {
super.onCreate(app);
Object/*ActivityThread*/ thread;
List<ProviderInfo> providers;
Instrumentation base;
ApkBundleLauncher.InstrumentationWrapper wrapper;
Field f;
// Get activity thread
thread = ReflectAccelerator.getActivityThread(app);
// Replace instrumentation
try {
f = thread.getClass().getDeclaredField("mInstrumentation");
f.setAccessible(true);
base = (Instrumentation) f.get(thread);
wrapper = new ApkBundleLauncher.InstrumentationWrapper(base);
f.set(thread, wrapper);
} catch (Exception e) {
throw new RuntimeException("Failed to replace instrumentation for thread: " + thread);
}
// Inject message handler
ensureInjectMessageHandler(thread);
// Get providers
try {
f = thread.getClass().getDeclaredField("mBoundApplication");
f.setAccessible(true);
Object/*AppBindData*/ data = f.get(thread);
f = data.getClass().getDeclaredField("providers");
f.setAccessible(true);
providers = (List<ProviderInfo>) f.get(data);
} catch (Exception e) {
throw new RuntimeException("Failed to get providers from thread: " + thread);
}
sActivityThread = thread;
sProviders = providers;
sHostInstrumentation = base;
sBundleInstrumentation = wrapper;
}
(1) 获得当前应用进程的 ActivityThread 实例
首先,我们通过反射获得当前应用进程的ActivityThread
实例
thread = ReflectAccelerator.getActivityThread(app)
具体的逻辑为:
public static Object getActivityThread(Context context) {
try {
//1.首先尝试通过 ActivityThread 内部的静态变量获取。
Class activityThread = Class.forName("android.app.ActivityThread");
// ActivityThread.currentActivityThread()
Method m = activityThread.getMethod("currentActivityThread", new Class[0]);
m.setAccessible(true);
Object thread = m.invoke(null, new Object[0]);
if (thread != null) return thread;
//2.静态变量获取失败,那么再通过 Application 的 mLoadedApk 中的 mActivityThread 获取。
Field mLoadedApk = context.getClass().getField("mLoadedApk");
mLoadedApk.setAccessible(true);
Object apk = mLoadedApk.get(context);
Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
mActivityThreadField.setAccessible(true);
return mActivityThreadField.get(apk);
} catch (Throwable ignore) {
throw new RuntimeException("Failed to get mActivityThread from context: " + context);
}
}
这里面的逻辑为:
- 通过
ActivityThread
中的静态方法currentActivityThread
来获取:
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
sCurrentActivityThread
是在ActivityThread#attach(boolean)
方法中被赋值的,而attach
方法则是在入口函数main
中调用的:
public static void main(String[] args) {
//创建应用进程的 ActivityThread 实例。
ActivityThread thread = new ActivityThread();
thread.attach(false);
}
- 如果上面的方法获取失败,那么我们再尝试获取
Application
中的LoadedApk#mActivityThread
。
(2) 替换 ActivityThread 中的 mInstrumentation
通过(1)
拿到ActivityThread
实例之后,接下来就是替换其中mInstrumentation
成员变量为Small
自己的实现类ApkBundleLauncher.InstrumentationWrapper
,并将原始的mInstrumentation
传入作为其成员变量。
正如 Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现 中所介绍的,当我们调用startActivity
之后,那么会调用到它内部的mInstrumentation
的execStartActivity
方法,经过替换之后,就会调用ApkBundleLauncher.InstrumentationWrapper
的对应方法,下面截图中的mBase
就是原始的mInstrumentation
:
ReflectAccelerator
又通过反射调用了mBase
的对应方法:由此可见,
hook
的目的就在于替代者的方法被调用,到调用原始对象的对应方法之间所进行的操作,也就是下面红色框中的这两句:首先看一下wrap(Intent intent)
方法,它的作用为:当我们在应用侧启动一个插件Activity
时,需要将它替换成为AndroidManifest.xml
预先注册好的占坑Activity
。
private void wrapIntent(Intent intent) {
ComponentName component = intent.getComponent();
String realClazz;
//判断是否显示地设置了目标组件的类名。
if (component == null) {
//如果没有显示设置 Component,那么通过 resolveActivity 来解析出目标组件。
component = intent.resolveActivity(Small.getContext().getPackageManager());
if (component != null) {
return;
}
//获得目标组件全路径名。
realClazz = resolveActivity(intent);
if (realClazz == null) {
return;
}
} else {
//如果设置了类名,那么直接取出。
realClazz = component.getClassName();
if (realClazz.startsWith(STUB_ACTIVITY_PREFIX)) {
realClazz = unwrapIntent(intent);
}
}
if (sLoadedActivities == null) return;
//根据类名,确定它是否是插件当中的 Activity
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) return;
//将真实的 Activity 保存在 Category 中,并加上 > 标识符。
intent.addCategory(REDIRECT_FLAG + realClazz);
//选取占坑的 Activity
String stubClazz = dequeueStubActivity(ai, realClazz);
//重新设置 intent,用占坑的 Activity 来替代目标 Activity
intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
其中,dequeueStubActivity
就是取出占坑的Activity
,它是预先在AndroidManifest.xml
中注册的一些占坑Activity
,同时,我们也会把真实的目标Activity
放在Category
字段当中。
接下来,再看一下ensureInjectMessageHandler(Object thread)
函数,代码的逻辑很简单,就是替换AcitivtyThread
的mH
中的mCallback
变量为sActivityThreadHandlerCallback
,它的类型为ActivityThreadHandlerCallback
,是我们自定的一个内部类。
private static void ensureInjectMessageHandler(Object thread) {
try {
Field f = thread.getClass().getDeclaredField("mH");
f.setAccessible(true);
Handler ah = (Handler) f.get(thread);
f = Handler.class.getDeclaredField("mCallback");
f.setAccessible(true);
boolean needsInject = false;
if (sActivityThreadHandlerCallback == null) {
needsInject = true;
} else {
Object callback = f.get(ah);
if (callback != sActivityThreadHandlerCallback) {
needsInject = true;
}
}
if (needsInject) {
// Inject message handler
sActivityThreadHandlerCallback = new ActivityThreadHandlerCallback();
f.set(ah, sActivityThreadHandlerCallback);
}
} catch (Exception e) {
throw new RuntimeException("Failed to replace message handler for thread: " + thread);
}
}
在 Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现 我们分析过,Activity
的生命周期是由AMS
使用运行在系统进程的代理对象ApplicationThreadProxy
,通过Binder
通信发送消息,在应用进程中的ActivityThread#ApplicationThread
在onTransact()
收到消息后,再通过mH
(一个自定的Handler
,类型为H
),发送消息到主线程,H
在handleMessage
中处理消息,回调Activity
对应的生命周期方法。
而ensureInjectMessageHandler
所做就是让H
的handleMessage
方法被调用之前,进行一些额外的操作,例如在占坑的Activity
启动完成之后,将它在应用测的记录替换成为Activity
,而这一过程是通过替换Handler
当中的mCallback
对象,因为在调用handleMessage
之前,会先去调用mCallback
的handleMessage
,并且在其不返回true
的情况下,会继续调用Handler
本身的handleMessage
方法:
对于Small
来说,它会对以下四种类型的消息进行拦截:
我们以redirectActivity
为例,看一下将占坑的Activity
重新替换为真实的Activity
的过程。
private void redirectActivity(Message msg) {
Object/*ActivityClientRecord*/ r = msg.obj;
//通过反射获得启动该 Activity 的 intent。
Intent intent = ReflectAccelerator.getIntent(r);
//就是通过前面放在 Category 中的字段,来取得真实的 Activity 名字。
String targetClass = unwrapIntent(intent);
boolean hasSetUp = Small.hasSetUp();
if (targetClass == null) {
if (hasSetUp) return; // nothing to do
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
return;
}
Small.setUpOnDemand();
return;
}
if (!hasSetUp) {
//确保初始化了。
Small.setUp();
}
//重新替换为真实的 Activity
ActivityInfo targetInfo = sLoadedActivities.get(targetClass);
ReflectAccelerator.setActivityInfo(r, targetInfo);
}
由于handleMessage
的返回值为false
,按照前面的分析,mH
的handleMessage
方法也会得到执行。
以上就是整个Hook
的过程,简单的总结下来就是在应用进程与AMS
进程的通信过程的某个节点,通过替换类的方式,插入一些逻辑,以绕过系统的检查:
- 从应用进程到
AMS
所在进程的通信,是通过替换应用进程中的ActivityThread
的mInstrumentation
为自定义的ApkBundleLauncher.InstrumentationWrapper
,在其中将Intent
当中真实的Activity
替换成为占坑的Activity
,然后再调用原始的mInstrumentation
通知AMS
。 - 从
AMS
所在进行到应用进程的通信,是通过替换应用进程中的H
中的mCallback
,在其中将占坑Activity
替换成为真实的Activity
,再执行原本的操作。
(3) ActivityThread 内部的 mBoundApplication 变量
这一步没有进行Hook
操作,而是先获得ActivityThread
内部的mBoundApplication
实例,然后获得该实例内部的providers
变量,它的类型为List<ProviderInfo>
。
(4) 备份
最后一步,就是备份一些关键变量,用于之后的操作:
//ActivityThread 实例
sActivityThread = thread;
//List<ProviderInfo> 实例
sProviders = providers;
//原始的 Instrumentation 实例
sHostInstrumentation = base;
//执行 Hook 操作的 Instrumentation 实例
sBundleInstrumentation = wrapper;
三、实例分析
以上就是源码分析部分,下面,我们通过一个启动插件Activity
的过程,来验证一下前面的分析:
3.1 从应用进程到 AMS 进程
通过下面的方法启动一个插件Activity
:
public void startStubActivity(View view) {
Small.openUri("upgrade", this);
}
按照前面的分析,此时应当会调用经过Hook
之后的Instrumentation
实例的execStartActivity
方法,可以看到在wrapIntent
方法调用之前,我们的目标Activity
仍然是真实的UpgradeActivity
:
让断点继续往下走,经过
wrapIntent
之后,Intent
的目标对象替换成为了占坑的Activity
:3.2 从 AMS 进程到应用进程
而当AMS
需要通知应用进程时,它第一次回调的是占坑的Activity
,也就是如下所示:
通过反射,我们修改
ActivityClientRecord
中的内容,让其还原成为真实的Activity
:四、小结
以上就是Small
预初始化所做的一些事情,也就是其Hook
实现的原理,很多第三方的插件化都是基于该原理来实现启动不在AndroidManifest.xml
中注册的组件的,开始的时候,理解起来可能会有点困难,关键是要弄清楚应用程序和AMS
进程的交互原理,欢迎阅读 Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现 。
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/