最近在研究安卓插件技术,系列文章用来记录一下自己的学习经历以及遇到的坑。
文章通过反射动态替换的方式,修改activity的startActivity()方法的具体实现。
这里需要先了解一下java的反射机制:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任
意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
具体可以参考:http://blog.csdn.net/liujiahan629629/article/details/18013523这篇文章,作者写的很清楚的。
首先我们来看看activity的startActivity()方法的真面目。
通过调用activity的startActivity()方法最后都是调用了带三个参数的startActivityForResult()方法。在方法体内部,最终实际调用的还是Activity成员变量mInstrumentation的execStartActivty()方法。到了这里我们可以修改startActivityForResult方法或者mInstrumentation的execStartActivty方法,但是我发现startActivityForResult方法是一个公开的方法,而mInstrumentation是一个私有成员变量(同时考虑到还可以通过context启动activity)。为了不影响外界主动调用startActivityForResult产生不一致的结果,决定修改mInstrumentation这个成员变量。
首先,我们需要拿到这个私有成员变量进行替换。。
通过反射,我们或许可以这么拿:
Class> activityClass = null;
activityClass = Class.forName("android.app.Activity");
// 拿到原始的 mInstrumentation变量
Field mInstrumentationField = activityClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(activityClass);
但是实际测试过程中,我发现不行。抛了一个异常:
java.lang.IllegalArgumentException: Expected receiver of type android.app.Activity, but got java.lang.Class
然后回头看我们的代码:发现
mInstrumentationField.get()
这个方法传递的参数是一个Class类型的,这里应该传递一个Activity类型的参数,因为mInstrumentation是Activity的一个成员变量。
于是乎,下面遇到了一个问题,就是怎么拿到Activity的实例,我们知道Activity对外是没有提供获取实例的静态方法,也没有提供显示的创建实例的方法。
通过Class.newInstance()方法会重新创建一个实例对象,显然是不行的。
通过反射获取构造器调用newInstance()也相当于重新创建了一个对象,也是不行的。
秉着万物皆对象的想法,我试着获取默认无参的构造方法:
activityClass.getDeclaredMethod("Activity"),发现抛了NoSuchMethodFound异常。
于是乎,我决定把需要hook的activity作为参数穿给hook对象。(毕竟不是本文要说的重点啊~~)
改写后的代码是这样的:
public static void replaceIsr(Activity activity) {
try {
Class> activityClass = Class.forName("android.app.Activity");
// 拿到原始的 mInstrumentation字段
Field mInstrumentationField = activityClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(activity);
// 创建代理对象
Instrumentation hookInstrumentation = new HookInstrumentation(mInstrumentation);
// 更换instrumentation对象
mInstrumentationField.set(activity, hookInstrumentation);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
这里来看。我们貌似可以更换Instrumentation对象了。接下来,我们看看我们这个假的Instrumentation怎么写。
public class HookInstrumentation extends Instrumentation {
// Activity中原始的对象, 保存起来
Instrumentation mBase;
public HookInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.d("wh", "HookInstrumentation:execStartActivity");
// 调用原生的方法正常启动activity,由于这个方法是隐藏的,且有多个重载形式,选择一个最终调用的方法进行反射获取
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("do not support!!! pls adapt it");
}
}
}
这里我们直接继承自Instrumentation来创建一个我们自己的类,因为我们需要改造启动activity的方法,所以需要改造execActivity()方法。为了不影响系统正常的activity启动,我们需要维持一个系统的Instrumentation对象来启动activity。具体见注释。这里就不在赘述了。我只是在系统调用这个hook的对象的execStartActivity()方法时,打了一段日志。不影响正常的activity正常启动。
写完这个假的Instrumentatinon,接下来就是需要替换Activity的成员变量mInstrumentation了。。观察Activity给mInstrumentation赋值的地方:
是个常量方法,无法被重写。再往前面看看。然后发现了这个:
不知道大家有没有注意到第一行的注释,在构造器调用之后,onCreate()调用之前进行设置这些参数,那么我们可以在Activity已经设置完参数后,进行成员变量的替换;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HookHelper.replaceIsr(this);
this.startActivity(new Intent(this, JumpActivity.class));
}
到此,一次对Activity的startActivity()方法的hook操作就完成了。文章是对weishu的博文学习后的小结,博文内容非常完整,流程清晰。
具体参考:http://weishu.me/2016/01/28/understand-plugin-framework-overview/