之前一直听说有 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() 方法.最终我们可以里面干”坏事”了。
CustomInstrumentation 类继承了 Instrumentation,并且重写了 newActivity()
在 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 的反射机制 可以然我们逃脱限制,无所不能。
