最近在研究安卓插件技术,系列文章用来记录一下自己的学习经历以及遇到的坑。
文章通过反射动态替换的方式,修改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<android.app.Activity>
然后回头看我们的代码:发现
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/