之前一直听说有 Hook 这个技术,但是一直不知道有什么作用,今天通过 Hook Instrumentation 小试牛刀过把瘾。
在 Android 中 有个 Instrumentation 这个类,ActivityThread 拥有这个类的实例,并且通过它实现 Activity 很多操作,如 newActivity() ,startActivity(),callActivityOnCreate() 等。比如我们可以 Hook newActivity 这个方法,然后当某个状态还没有到达的时候,去 new 一个 Loading Activity,然后让系统去启动并显示它。
那么如何 Hook Instrumentation 来实现自己想要的行为呢?
通过 ActivityThread 的源码发现,在 main 方法有
ActivityThread thread = new ActivityThread();
thread.attach(false);
这两行代码实例化了一个 ActivityThread 对象 并且调用的 attach 方法,把当前的对象赋值给了下面的这个变量 sCurrentActivityThread
private static volatile ActivityThread sCurrentActivityThread;
这个变量是 static 的,所以具体的步骤:
- 通过反射拿到 ActivityThread 的 sCurrentActivityThread 变量的值,然后再通该实例变量拿到mInstrumentation 变量。
- 再通过反射 将 mInstrumentation 变量设置为我们自定义的 CustomInstrumentation 对象。
这样当系统通过 ActivityThread 调用 它的的成员变量 mInstrumentation 调用 newActivity 等方法的时候,实际是调用我们 CustomInstrumentation 的 newActivity(),但前提是 我们的 CustomInstrumentation 继承 Instrumentation,并且重写 newActivity() 方法.最终我们可以里面干”坏事”了。
public class CustomInstrumentation extends Instrumentation{
private Instrumentation base;
public CustomInstrumentation(Instrumentation base) {
this.base = base;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Log.e("TAG", "invoked CustomInstrumentation#newActivity, " + "class name =" + className + ", intent = " + intent);
return super.newActivity(cl, className, intent);
}
}
CustomInstrumentation 类继承了 Instrumentation,并且重写了 newActivity()
public class Hooker {
private static final String TAG = "Hooker";
public static void hookInstrumentation() throws Exception {
Class> activityThread = Class.forName("android.app.ActivityThread");
Method sCurrentActivityThread = activityThread.getDeclaredMethod("currentActivityThread");
sCurrentActivityThread.setAccessible(true);
//获取ActivityThread 对象
Object activityThreadObject = sCurrentActivityThread.invoke(activityThread);
//获取 Instrumentation 对象
Field mInstrumentation = activityThread.getDeclaredField("mInstrumentation");
mInstrumentation.setAccessible(true);
Instrumentation instrumentation = (Instrumentation) mInstrumentation.get(activityThreadObject);
CustomInstrumentation customInstrumentation = new CustomInstrumentation(instrumentation);
//将我们的 customInstrumentation 设置进去
mInstrumentation.set(activityThreadObject, customInstrumentation);
}
}
在 MyApplication 代理里调用 hookInstrumentation
public class MyApplication extends Application{
private static MyApplication INSTANCE ;
public static MyApplication getInstance() {
return INSTANCE;
}
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
try {
Hooker.hookInstrumentation();
} catch (Exception e) {
e.printStackTrace();
}
}
}
启动一个 Activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
startActivity(new Intent(this, Main2Activity.class));
}
}
最后打印出
05-05 09:26:37.959 32760-32760/com.meyhuan.hookinstrumention E/TAG: invoked CustomInstrumentation#newActivity, class name =com.meyhuan.hookinstrumention.Main2Activity, intent = Intent { cmp=com.meyhuan.hookinstrumention/.Main2Activity }
将 CustomInstrumentation 的 newActivity 改为 return super.newActivity(cl, Main2Activity.class.getName(), intent);
public class CustomInstrumentation extends Instrumentation{
...
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Log.e("TAG", "invoked CustomInstrumentation#newActivity, " + "class name =" + className + ", intent = " + intent);
return super.newActivity(cl, Main2Activity.class.getName(), intent);
}
}
然后启动应用出现如下画面
代码下载
虽然最后的例子没有很大的使用价值,但是可以提供一些解决问题的思路,比如当我们在 Application#onCreate() 启动不同的线程,初始化一些 SDK,但是当用户点击进入到某个需要用到这个 SDK 的 Activity 的时候,就可以通过在 CustomInstrumentation#newActivity() 去检测 SDK 是否初始化好了,如果没有就可以显示一个 Loading 的 Activity。Java 的反射机制 可以然我们逃脱限制,无所不能。