概述
Activity的插件化一直以来都是插件化技术的重点。Activity的插件化有很多技术方案,但是主流实现还是使用Hook技术。所有本文主要讲解通过Hook技术的实现Activity插件化的方案。在了解Activity插件化之前首先要清楚Activity的启动流程,不清楚的可以看我之前写的文章。
Activity插件化方案
Hook技术主要有以下两种实现方案,本文也是围绕着这两种方案进行介绍:
- Hook IActivityManager
- Hook Instrumentation
占坑Activity
众所周知,Activity是需要在AndroidManifest.xml文件中进行注册的,而插件中的Activity是不能在宿主的AndroidManifest.xml文件中进行注册的,所以不管采用哪一种Hook方案都需要提前在宿主中创建一个Activity并且在AndroidManifest.xml中进行注册。通过启动这个占坑Activity我们就能完成对插件Activity的替换。如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.pluginactivity">
<application
...
<activity android:name=".StubActivity"/>
</application>
</manifest>
Hook IActivityManager方案
1.通过AMS校验
Activity启动的时候会由AMS对Activity的合法性进行校验。插件Activity因为没有在AndroidManifest.xml文件中进行注册,不能直接通过startActivity方法启动插件Activity,所以这个时候就需要利用占坑Activity来欺骗AMS。
在Activity的启动的时候,会调用IActivityManager的startActivity方法。因为Hook点IActivityManager是一个接口,所有我们可以通过动态代理的方式接管IActivityManager的startActivity方法。首先定义一个IActivityManager的代理类IActivityManagerProxy:
public class IActivityManagerProxy implements InvocationHandler {
private Object mActivityManager;
private static final String TAG = "IActivityManagerProxy";
public IActivityManagerProxy(Object activityManager) {
this.mActivityManager = activityManager;
}
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {//1
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
intent = (Intent) args[index];
Intent subIntent = new Intent();//2
String packageName = "com.example.pluginactivity";
subIntent.setClassName(packageName,packageName+".StubActivity");//3
subIntent.putExtra(HookHelper.TARGET_INTENT, intent);//4
args[index] = subIntent;
}
return method.invoke(mActivityManager, args);
}
}
在注释1处接管startActivity方法,并且获取到args中的第一个Intent对象,它是插件TargetActivity的Intent对象。注释2处新建一个subIntent,然后在注释3处使用新建的subIntent来启动StubActivity,这个Activity就是之前占坑的Activity。注释4处是将插件TargetActivity的Intent保存到subIntent中。通过一系列操作之后,启动的目标就会变成StubActivity,这样我们就可以通过AMS的校验。原来的插件TargetActivity的信息被保存在StubActivity的Intent对象中,后续我们会根据这个对象取出插件TargetActivity。
然后我门就可以通过Hook的方式替换掉系统的IActivityManager:
public class HookHelper {
public static final String TARGET_INTENT = "target_intent";
public static void hookAMS() throws Exception {
Object defaultSingleton=null;
if (Build.VERSION.SDK_INT >= 26) {//1
Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
//获取activityManager中的IActivityManagerSingleton字段
defaultSingleton= FieldUtil.getField(activityManageClazz ,null,"IActivityManagerSingleton");
} else {
Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
//获取ActivityManagerNative中的gDefault字段
defaultSingleton= FieldUtil.getField(activityManagerNativeClazz,null,"gDefault");
}
Class<?> singletonClazz = Class.forName("android.util.Singleton");
Field mInstanceField= FieldUtil.getField(singletonClazz ,"mInstance");
//获取iActivityManager
Object iActivityManager = mInstanceField.get(defaultSingleton);//2
Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));
mInstanceField.set(defaultSingleton, proxy);//3
}
}
Android 8.0和8.0以下的源码是不一样的,所有需要在注释1处作区分。在注释2处拿到系统的IActivityManager对象,然后在注释3处用前面创建的代理类IActivityManagerProxy替换IActivityManager。最后再自定义的Application中的attachBaseContext方法中调用HookHelper.hookAMS()。
这时候当我们想要启动插件TargetActivity的时候会发现启动的并不是TargetActivity,而是StubActivity。说明我们已经成功利用StubActivity通过了AMS的校验。下面我们就可以进行下一步操作,还原插件Activity。
2.还原插件Activity
熟悉Activity启动流程的同学应该知道,启动Activity的操作是由H类中的handleMessage方法处理的,H类继承自Handler。所以如果我们能够拦截H类的handleMessage方法,就可以还原插件Activity。具体实现我们可以把Handler的成员mCallback作为Hook点,通过创建一个Handler.Callback类型的对象来替换掉H类的成员mCallback,这样我们就能拦截H类中的handleMessage方法。不清楚为什么的可以看看Handler的源码。
首先创建一个继承自Handler.Callback类型的类:
public class HCallback implements Handler.Callback{
public static final int LAUNCH_ACTIVITY = 100;
Handler mHandler;
public HCallback(Handler handler) {
mHandler = handler;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
Object r = msg.obj;
try {
//1.得到消息中的Intent(启动SubActivity的Intent)
Intent intent = (Intent) FieldUtil.getField(r.getClass(), r, "intent");
//2.得到此前保存起来的Intent(启动TargetActivity的Intent)
Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);
//3.将启动SubActivity的Intent替换为启动TargetActivity的Intent
intent.setComponent(target.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
//4
mHandler.handleMessage(msg);
return true;
}
}
在注释1处得到之前占坑Activity的Intent对象,注释2处从占坑Activity的Intent中取出插件TargetActivity的Intent对象。注释3是将占坑Activity的Intent替换为插件TargetActivity的Intent,然后在注释4处交由H类分发。
接着就是Hook H类的成员mCallback:
public static void hookHandler() throws Exception {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Object currentActivityThread= FieldUtil.getField(activityThreadClass ,null,"sCurrentActivityThread");
Field mHField = FieldUtil.getField(activityThread,"mH");
Handler mH = (Handler) mHField.get(currentActivityThread);
FieldUtil.setField(Handler.class,mH,"mCallback",new HCallback(mH));
}
获取到ActivityThread中的成员mH,这个成员就是H类的对象。然后在用自定义的Callback替换掉mH的成员mCallback。同样的我们需要在自定义Application中的attachBaseContext方法中调用HookHelper.hookHandler方法。
当我们再次从宿主中启动插件Activity时,会发现这次启动的正是插件TargetActivity。
Hook Instrumentation方案
Hook Instrumentation方案同样也需要利用占坑Activity通过AMS的校验和还原插件Activity,只是具体的实现会不一样。
1.通过AMS校验
首先自定义一个Instrumentation,然后在execStartActivity方法中将启动的插件TargetActivity替换为StubActivity:
public class InstrumentationProxy extends Instrumentation {
private Instrumentation mInstrumentation;
private PackageManager mPackageManager;
public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
mInstrumentation = instrumentation;
mPackageManager = packageManager;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (infos == null || infos.size() == 0) {
intent.putExtra(HookHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());//1
intent.setClassName(who, "com.example.pluginactivity.StubActivity");//2
}
try {
//3
Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,
target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
注释1处是将插件TargetActivity的ClassName保存起来,注释2是将插件TargetActivity替换为StubActivity,注释3是反射调用execStartActivity方法。这样我们就能利用占坑Activity通过AMS的验证
2.还原插件Activity
还原插件Activity比较简单,我们可以通过自定义Instrumentation的newActivity方法中还原插件Activity:
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
//1
String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);
if (!TextUtils.isEmpty(intentName)) {
//2
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
注释1处获取插件TargetActivity的ClassName,如果获取到了就会这注释2处将插件TargetActivity的ClassName传入父类的newActivity方法中,否则就说明需要启动的Activity不是插件Activity。
紧接着就是用自定义的Instrumentation替换系统的Instrumentation:
public static void hookInstrumentation(Context context) throws Exception {
Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
Field mMainThreadField =FieldUtil.getField(contextImplClass,"mMainThread");
Object activityThread = mMainThreadField.get(context);
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field mInstrumentationField=FieldUtil.getField(activityThreadClass,"mInstrumentation");
FieldUtil.setField(activityThreadClass,activityThread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentationField.get(activityThread),
context.getPackageManager()));
}
最后在自定义Application中的attachBaseContext方法中调用HookHelper.hookInstrumentation()方法。运行程序之后发现,启动的正是插件Activity。
总结
Activity插件化是整个插件化技术的重点,本文针对Activity插件化的两种Hook方案作了讲解,想要完全理解Activity的插件化,需要对Activity的启动流程有所了解。