插件化(二)

宿主启动插件的Activity
Activity 是需要在清单文件中注册的,显然,插件的 Activity 没有在宿主的清单文件中注册,那我们如何来启动它
呢?
这里我们就需要使用 Hook 技术,来绕开系统的检测。可能有些同学不知道 Hook 是什么,所以我们先简单的介绍
下 Hook 技术。
Hook
正常情况下对象A调用对象B,对象B处理后将数据返回给对象A,如下图:

而加入Hook后的流程就变成了下图形式:

Hook可以是一个方法或者一个对象,它就像一个钩子一样挂在对象B上面,当对象A调用对象B之前,Hook可以做
一些处理,起到“欺上瞒下”的作用。而对象B就是我们常说的Hook点。为了保证Hook的稳定性,Hook点一般选择
容易找到并且不易变化的对象,如静态变量和单例。
那么思路就来了,首先我们在宿主里面创建一个 ProxyActivity 继承自 Activity,并且在清单中注册。当启动插件
Activity 的时候,在系统检测前,找到一个Hook点,然后通过 Hook 将插件 Activity 替换成 ProxyActivity,等到检
测完了后,再找一个Hook点,使用 Hook 将它们换回来,这样就实现了插件 Activity 的启动。思路是不是非常的
简单。
如何查找 Hook 点呢?这就需要我们了解 Activity 的启动流程了。

Activity的启动流程
首先我们来看一张 Activity 启动流程的简单示意图,如下:

通过这张图我们可以确定 Hook 点的大致位置。
1. 在进入 AMS 之前,找到一个 Hook 点,用来将插件 Activity 替换为 ProxyActivity。
2. 从 AMS 出来后,再找一个 Hook 点,用来将 ProxyActivity 替换为插件 Activity。
在看源码之前,我们再想一个问题,看源码是要找什么东西作为 Hook 点呢?

我们在项目中一般通过  startActivity(new Intent(this,PluginActivity.class)); 启动 PluginActivity,如果我
想换成启动 ProxyActivity,调用方法  startActivity(new Intent(this,ProxyActivity.class)); 这样就可以了。
是不是已经知道答案了!!!没错,我们只要找到能够修改 Intent 的地方,就可以作为 Hook 点,从这儿也可以
看出 Hook 点并不是唯一的。
好的,下面我们进入源码

// android/app/Activity.java
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
startActivityForResult(intent, -1, options);
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
}
// android/app/Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// 这儿就是我们的 Hook 点,替换传入 startActivity 方法中的 intent 参数
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
}

既然 Hook 点找到了,那我们怎么修改呢?
答案是动态代理,所以我们要生成一个代理对象,显然,我们要代理的是  ActivityManager.getService() 返回的
对象。
那下面我们就来看下它返回的是什么?
首先我们看下  ActivityManager.getService() 返回的是一个什么类的对象

// android/app/ActivityManager.java
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}

可以看到,它返回的是 IActivityManager 类的对象。下面我们就生成代理对象,并且当执行的方法是 startActivity
的时候,替换它的参数 intent。代码如下:

Object mInstanceProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 当执行的方法是 startActivity 时作处理
if ("startActivity".equals(method.getName())) {
int index = 0;
// 获取 Intent 参数在 args 数组中的index值
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 得到原始的 Intent 对象 -- 唐僧(插件)的Intent
Intent intent = (Intent) args[index];
// 生成代理proxyIntent -- 孙悟空(代理)的Intent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.enjoy.pluginactivity",
ProxyActivity.class.getName());
// 保存原始的Intent对象
proxyIntent.putExtra(TARGET_INTENT, intent);
// 使用proxyIntent替换数组中的Intent
args[index] = proxyIntent;
}
return method.invoke(mInstance, args);
}
});

接着我们再使用反射将系统中的 IActivityManager 对象替换为我们的代理对象 mInstanceProxy。那如何替换了?
我们接着看源码。

// android/app/ActivityManager.java
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

通过上面的代码,我们知道 IActivityManager 是调用的 Singleton 里面的 get 方法,所以下面我们再看下
Singleton 是怎么样的。

// android/util/Singleton
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}

可以看出,IActivityManagerSingleton.get() 返回的实际上就是 mInstance 对象。所以接下来我们要替换的就是这
个对象。代码如下:

// 获取 Singleton<T> 类的对象
Class<?> clazz = Class.forName("android.app.ActivityManager");
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// 获取 mInstance 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
// 使用代理对象替换原有的 mInstance 对象
mInstanceField.set(singleton, mInstanceProxy);

到这儿我们的第一步就实现了,接着我们来实现第二步,在出来的时候,将它们换回去。
还记得前面那张图吗?在出来的时候,会调用 Handler 的 handleMessage,所以下面我们看下 Handler 的源码。

public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

当 mCallback != null 时,首先会执行 mCallback.handleMessage(msg),再执行 handleMessage(msg),所以我
们可以将 mCallback 作为 Hook 点,创建它。ok,现在问题就只剩一个了,就是找到含有 intent 的对象,没办
法,只能接着看源码。

// android/app/ActivityThread.java
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
}
}
static final class ActivityClientRecord {
Intent intent;
}

可以看到,在 ActivityClientRecord 类中,刚好就有个 intent,而且这个类的对象,我们也可以获取到,就是
msg.obj。接下来就简单了,实现代码如下,如果有兴趣的同学也可从 handleLaunchActivity 方法一路跟下去,看
看 ActivityClientRecord 的 intent 到底在哪使用的,这儿我们就不赘叙了。

// 获取 ActivityThread 类的 对象
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
// 获取 Handler 对象
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
// 设置 Callback 的值
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100:
try {
// 获取 proxyIntent
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
// 目标 intent 替换 proxyIntent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
proxyIntent.setComponent(intent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return false;
}
});

总结
插件化涉及的技术其实是非常多的,比如应用程序启动流程、四大组件启动流程、AMS原理、PMS原理、
ClassLoader原理、Binder机制,动态代理等等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值