Hook Instrumentation实现要比Hook IActivityManager实现要简洁一些,示例代码会和Hook IActivityManager实现有重复,重复的部分这里不再赘述。
Hook Instrumentation实现同样也需要用到占坑Activity,与Hook IActivityManager实现不同的是,用占坑Activity替换插件Activity以及还原插件Activity的地方不同。
frameworks/base/core/java/android/app/Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
//启动Activity
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
...
} else {
...
}
}
startActivityForResult方法中调用了Instrumentation的execStartActivity方法来激活Activity的生命周期。
如图3所示,ActivityThread启动Activity的过程中会调用ActivityThread的performLaunchActivity方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//创建要启动Activity的上下文环境
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
//用类加载器来创建Activity的实例
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);//1
...
} catch (Exception e) {
...
}
...
return activity;
}
注释1处调用了mInstrumentation的newActivity方法,其内部会用类加载器来创建Activity的实例。看到这里我们可以得到方案,就是在Instrumentation的execStartActivity方法中用占坑SubActivity来通过AMS的验证,在Instrumentation的newActivity方法中还原TargetActivity,这两部操作都和Instrumentation有关,因此我们可以用自定义的Instrumentation来替换掉mInstrumentation。首先我们自定义一个Instrumentation,在execStartActivity方法中将启动的TargetActivity替换为SubActivity,如下所示。
InstrumentationProxy.java
/**
* @author ex-chenmengjia001
* @date 2019/1/23
*/
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(HookerHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());
intent.setClassName(who, "com.example.cmj.pluginization.StubActivity");
}
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class
, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String intentName = intent.getStringExtra(HookerHelper.TARGET_INTENsT_NAME);
if (!TextUtils.isEmpty(intentName)) {
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
}
首先查找要启动的Activity是否已经在AndroidManifest.xml中注册了,如果没有就在注释1处将要启动的Activity(TargetActivity)的ClassName保存起来用于后面还原TargetActivity,接着在注释2处替换要启动的Activity为StubActivity,最后通过反射调用execStartActivity方法,这样就可以用StubActivity通过AMS的验证。在InstrumentationProxy 的newActivity方法还原TargetActivity.
newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。
编写hookInstrumentation方法,用InstrumentationProxy替换mInstrumentation:
HookHelper.java
public static void hookInstrumentation(Context context)throws Exception{
Class<?> contextImpClass = Class.forName("android.app.ContextImpl");
Field mMainThread = FieldUtil.getField(contextImpClass, "mMainThread");
Object activityTHread = mMainThread.get(context);
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field mInstrumentation = FieldUtil.getField(activityThreadClass, "mInstrumentation");
FieldUtil.setField(activityThreadClass,activityTHread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentation.get(activityTHread),context.getPackageManager()));
}
获取ContextImpl类的ActivityThread类型的mMainThread字段,注释2出获取当前上下文环境的ActivityThread对象。 获取ActivityThread类中的mInstrumentation字段,最后用InstrumentationProxy来替换mInstrumentation。 在MyApplication的attachBaseContext方法中调用HookHelper的hookInstrumentation方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。
需要在MyApplication中调用HookerHelper.hookInstrumentation(base);public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
HookerHelper.hookInstrumentation(base);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @author ex-chenmengjia001
* @date 2019/1/22
*/
public class FieldUtil {
public static Object getField(Class clazz, Object target, String name) throws Exception {
Field declaredField = clazz.getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField.get(target);
}
public static Field getField(Class clazz, String name) throws Exception {
Field declaredField = clazz.getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField;
}
public static void setField(Class clazz, Object target, String name, Object value) throws Exception {
Field declaredField = clazz.getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(target, value);
}
}