本篇博客主要讲述一下对startActivity进行hook,对于Android开发来说,跳转一个新的Activity页面,最常见的无非两种了,
方法一:
Intent intent = new Intent(MainActivity.this, NewActivity.class);
startActivity(intent);
方法二:
Intent intent = new Intent(MainActivity.this, NewActivity.class);
getApplicationContent().startActivity(intent);
对于hook startActivity在此列出三种方法:
1: 我们知道我们的Activity页面继承自Activity.java,此时衍生出了hook的第一种方法。
2: getApplicationContext.startActivity实际上调用的是ContextImp的startActivity方法,由此衍生出了hook的第二种方法
3: 上面两种情况,都会执行一段代码就是:ActivityManager.getService().startActivity …, 由此衍生出了第三种方法,而且是一劳永逸的方法。
在Activity中有一个字段:mInstrumentation,我们调用的startActivity都会执行:
public class Activity extends ContextThemeWrapper {
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
}
}
此时我们可以替换掉mInstrumentation字段,然后执行我们自己的对象,
上图就是我们hook之后的startActivity的执行流程。
hook步骤:
/**
* hook Activity mInstrumentation
*/
private void hookActivityInstrumentation() {
//获取Activity中的mInstrumentation对象
Instrumentation mInstrumentation =
(Instrumentation) ReflexUtil.getFieldValue(Activity.class.getName(), this, "mInstrumentation");
//包装成自己的对象
Instrumentation proxy = new MyInstrumentation(mInstrumentation);
//将Activity中的对象替换成自己的对象
ReflexUtil.setFieldValue(Activity.class.getName(), this, "mInstrumentation", proxy);
}
对于MyInstrumentation,将它继承自Instrumentation:
/**
* author: liumengqiang
* Date : 2019/7/20
* Description :
*/
public class MyInstrumentation {
private Instrumentation mInstrumentation;
public MyInstrumentation(Instrumentation mInstrumentation) {
this.mInstrumentation = mInstrumentation;
}
public Instrumentation.ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.e("-----:", "页面跳转");
Class[] paramType = new Class[] {
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class
};
Object[] paramValue = new Object[] {
who, contextThread, token, target,
intent, requestCode, options
};
return (Instrumentation.ActivityResult) ReflexUtil.invokeInstanceMethod(mInstrumentation, "execStartActivity", paramType, paramValue);
}
}
注意:ReflexUtil工具类,在之前的博客中能够找到:
Android 插件化开发——基础底层知识(反射)
该方法有一个很大的缺点就是:只有在当前hook的Activity中生效,如果想要所有的Activity都生效,我们可以将hook代码放在BaseActivity中。
要实现该功能,需要明白getApplicationContext().startActivity()方法最终执行的是ContextImp中的startActivity方法:
@Override
public void startActivity(Intent intent, Bundle options) {
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
从上述代码可以看出,我们可以改变ActivityThread中的mInstrumentation字段,跟方法一类似,从而实现我们自己的功能。
注意:首先我们需要熟悉,在ActivityThread中通过currentActivityThread()方法,可以获取到ActivityThread对象。其次在ActivityThread中有一个字段是:mInstrumentation
hook步骤:
/**
* hook ContextWrapper
*/
private void hookContextWrapper() {
//1:获取到ActivityThread对象
Object currentActivityThread =
ReflexUtil.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
//2:获取当前ActivityThread内的mInstrumentation字段
Instrumentation mInstrumentation =
(Instrumentation) ReflexUtil.getFieldValue("android.app.ActivityThread", currentActivityThread, "mInstrumentation");
//3:创建新的Instrumentation对象
Instrumentation instance = new ContextWrapperProxy(mInstrumentation);
//4:替换ActivityThread中的mInstrumentation字段
ReflexUtil.setFieldValue("android.app.ActivityThread", currentActivityThread, "mInstrumentation", instance);
}
/**
* author: liumengqiang
* Date : 2019/7/20
* Description :
*/
public class ContextWrapperProxy extends Instrumentation{
private Instrumentation mInstrumentation;
public ContextWrapperProxy(Instrumentation mInstrumentation) {
this.mInstrumentation = mInstrumentation;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.e("-----:", "页面跳转");
Class[] paramType = new Class[] {
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class
};
Object[] paramValue = new Object[] {
who, contextThread, token, target,
intent, requestCode, options
};
return (ActivityResult) ReflexUtil.invokeInstanceMethod(mInstrumentation, "execStartActivity", paramType, paramValue);
}
}
上述两种方法都有共同的缺点就是:如果添加了方法1,那么getApplicationContext().startActivity()将不能被捕获,反之亦然。
那么有没有一个方法,能够同时支持跳转呢?
查看上述两种方法的startActivity方法,可以发现,都会执行一段代码:
ActivityManager.getService().startActivity(......)
查看源码可以发现,getService方法返回得是IActivityManager的实现类,也就是我们的AMS
/**
* @hide
*/
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton IActivityManagerSingleton =
new Singleton() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
通过上述代码可以知道,Singleton实际上是一个单例,而IActivityManager是一个接口,getService返回的是该接口实现类,那么我们就可以通过动态代理,将IActivityManager接口对象hook成我们自己的代理类,再替换Singleton中的mInstance字段。
注意:不了解动态代理的,传送门:Android 插件化开发——基础底层知识(代理模式)
hook步骤:
/**
* hook getService()
*/
private void hookGetService() {
//获取IActivityManagerSingleton对象
Object iActivityManagerSingleton =
ReflexUtil.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
//获取单例对象中的mInstance字段
Object mSingletonInstance =
ReflexUtil.getFieldValue("android.util.Singleton", iActivityManagerSingleton, "mInstance");
Class> classInterface = null;
try {
classInterface = Class.forName("android.app.IActivityManager");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//创建自己的代理对象
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{classInterface},
new SingletonProxy(mSingletonInstance));
//将单例对象中的字段替换
ReflexUtil.setFieldValue("android.util.Singleton", iActivityManagerSingleton, "mInstance", proxyInstance);
}
对于代理类,我们要实现InvocationHandler接口,
/**
* author: liumengqiang
* Date : 2019/7/20
* Description :
*/
public class SingletonProxy implements InvocationHandler {
private Object mBase;
public SingletonProxy(Object mBase) {
this.mBase = mBase;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// Log.e("--------:", "hook successful" + method.getName());
if("startActivity".equals(method.getName())) {
Log.e("--------:", "hook successful ");
}
return method.invoke(mBase, objects);
}
}
该方法弥补了前两种法的缺点,一劳永逸。
至此hook的startActivity的三种方法,我们就学习完了,若文中有错误知识点,欢迎指正!