Android 插件化开发——对startActivity方法进行hook(一)

本篇博客主要讲述一下对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字段进行hook


在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字段,然后执行我们自己的对象,

Android 插件化开发——对startActivity方法进行hook(一)_第1张图片
上图就是我们hook之后的startActivity的执行流程。
hook步骤:

  1. 获取Activity中的mInstrumentation字段的对象
  2. 将获取到的对象包装成自己的对象。
  3. 将包装好的对象反射复制给mInstrumentation字段。
    /**
     * 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中。


对Context的startActivity方法进行hook


要实现该功能,需要明白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步骤:

  1. 获取ActivityThread对象
  2. 获取ActivityThread中的mInstrumentation字段对象
  3. 将mInstrumentation包装成我们自己的对象
  4. 将ActivityThread中的mInstrumentation对象替换成我们自己的对象
    /**
     * 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()将不能被捕获,反之亦然。

那么有没有一个方法,能够同时支持跳转呢?


对ActivityManager的IActivityManagerSingleton字段进行hook


查看上述两种方法的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步骤:

  1. 通过反射获取IActivityManagerSingleton对象。
  2. 获取该对象中的字段mInstance值。
  3. 通过动态代理,将步骤2获取的对象包装成自己的代理对象。
  4. 将步骤1单例对象的mInstance字段进行替换,替换成我们步骤3生成的代理对象。
   /**
     * 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的三种方法,我们就学习完了,若文中有错误知识点,欢迎指正!

  1. Android 插件化开发——基础底层知识(Binder,AIDL)
  2. Android 插件化开发——基础底层知识(Context家族史,Activity启动流程)
  3. Android 插件化开发——基础底层知识(Service)
  4. Android 插件化开发——基础底层知识(反射)
  5. Android 插件化开发——基础底层知识(代理模式)

你可能感兴趣的:(android开发,Android插件化开发,Android插件化开发)