Android开发学习之路--插件化基础动态代理Hook

插件化中需要使用到hook技术,这里先了解下hook技术,以方便继续看代码,既然是代理,那就先来了解下代理设计模式吧。

1 代理

代理,或者称为 Proxy ,简单理解就是事情我不用去做,由其他人来替我完成。

1.1 无代理

通过例子来学习下,首先一个接口Human,有两个方法,一个是吃饭,一个上厕所

public interface Human {
    void eat(String something);
    void toilet();
}

再看下其实现类:

public class HumanImpl implements Human {
    @Override
    public void eat(String something) {
        Log.d("HumanImpl", "Eat "+something);
    }

    @Override
    public void toilet() {
        Log.d("HumanImpl", "toilet");
    }
}

测试下

public static void NoProxy() {
        Human human = new HumanImpl();
        human.eat("rice");
    }

最后运行结果:

HumanImpl: Eat rice

1.2 静态代理

我们在吃法和上厕所前都要洗手,那么实现一个静态代理类在Human实现类基础上加上洗手。

public class StaticEatProxy implements Human {
    @Override
    public void eat(String something) {
        HumanImpl human = new HumanImpl();
        wash();
        human.eat(something);
        wipeMouth();
    }

    @Override
    public void toilet() {
        HumanImpl human = new HumanImpl();
        wash();
        human.toilet();
        wash();
    }

    void wash() {
        Log.d("StaticEatProxy", "Wash Hands");
    }

    void wipeMouth() {
        Log.d("StaticEatProxy", "Wipe Mouth");
    }
}

测试例子:

 public static void StaticProxy() {
        Human human = new StaticEatProxy();
        human.eat("rice");
    }

运行结果:

StaticEatProxy: Wash Hands
HumanImpl: Eat rice
StaticEatProxy: Wipe Mouth

1.3 JDK动态代理

在吃饭和上厕所前都要洗手,那不是每个都要写一遍,如果还有其他的比如玩泥巴后洗手等等,那得写多少遍,这个时候可以用动态代理。来实现下:

public class DynamicEatProxy implements InvocationHandler {
    private Object object;

    public DynamicEatProxy(Object T) {
        object = T;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        wash();
        Object returnValue = method.invoke(object, args);//真正调用的方法,object是之前传进来的具体实例,args是接口所需要的方法参数
        return returnValue;
    }

    void wash() {
        Log.d("DynamicEatProxy", "Wash Hands");
    }
}

测试例子:

 public static void DynamicProxy() {
        HumanImpl human = new HumanImpl();
        DynamicEatProxy action = new DynamicEatProxy(human);
        ClassLoader loader = human.getClass().getClassLoader();
        Class[] interfaces = human.getClass().getInterfaces();
        Object proxy = Proxy.newProxyInstance(loader, interfaces, action);
        Human humanProxy = (Human)proxy;//将proxy代理转为Human接口类进行Human接口内方法的调用
        humanProxy.eat("rice");
        humanProxy.toilet();
    }

运行结果:

DynamicEatProxy: Wash Hands
HumanImpl: Eat rice
DynamicEatProxy: Wash Hands
HumanImpl: toilet

通过JDK动态代理,我们实现了需求。

2 Hook技术

  • Hook英文翻译为“钩子”,而钩子就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件,简单来说就是当我们开发过程中某些API不能满足我们的要求时,我们就得去修改某些api,使之能满足我们的要求。
  • Hook的基本的方法就是通过hook“接触”到需要修改的api函数入口点,改变它的地址指向新的自定义的函数。在Android开发中,我们同样能利用Hook的原理让系统某些方法运行时调用的是我们定义的方法,从而满足我们的要求。
  • 一般的Hook 的选择点是静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
  • Hook 过程是寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。选择合适的代理方式,如果是接口可以用动态代理,也可以自己写静态代理替换原始对象。

3 Hook掉startActivity

有了以上基础,接着我们可以开始Hook掉startActivity方法,这里参考了VitrualApk的源码。

3.1 startActivity源码

Context.startActivity只是接口,其实现类是ContextImpl,具体代码如下:

@Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

不难发现最终是通过调用ActivityThread类中的mInstrumentation成员的execStartActivity方法。

3.2 获取ActivityThread对象

可以看到sCurrentActivityThread是一个静态变量,主线程又只有一个,只要获取了这个ActivityThread,修改其mInstrumentation对象为代理的对象,那么就可以hook了。

private static volatile ActivityThread sCurrentActivityThread;

接着我们通过反射来获取:

try {
   Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
    Object activityThread = null;
    try {
        activityThread = ReflectUtil.getField(activityThreadClazz, null, "sCurrentActivityThread");
    } catch (Exception e) {
        // ignored
    }

3.3 获取mInstrumentation对象

获取到sCurrentActivityThread后就可以通过getInstrumentation方法获取mInstrumentation,同样通过反射:

try {
   sInstrumentation = (Instrumentation) ReflectUtil.invoke(
           sActivityThread.getClass(), sActivityThread, "getInstrumentation");
} catch (Exception e) {
   e.printStackTrace();
}

3.4 创建代理的VAInstrumentation

接着我们创建自己的VAInstrumentation来替换mInstrumentation对象,由于mInstrumentation是类不是接口,不能用动态代理,这里写一个静态的代理类来覆盖原方法。

public class VAInstrumentation extends Instrumentation {
    public static final String TAG = "VAInstrumentation";

    private Instrumentation mBase;

    public VAInstrumentation(Instrumentation base) {
        this.mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        /**
         * 启动Activity的时候会调用到这里
         */
        Log.d(TAG, "====== This is hook startActivity by eastmoon! =======");

        ActivityResult result = realExecStartActivity(who, contextThread, token, target,
                intent, requestCode, options);

        return result;
    }

    private ActivityResult realExecStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ActivityResult result = null;
        try {
            Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
                    int.class, Bundle.class};
            result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
                    "execStartActivity", parameterTypes,
                    who, contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            if (e.getCause() instanceof ActivityNotFoundException) {
                throw (ActivityNotFoundException) e.getCause();
            }
            e.printStackTrace();
        }

        return result;
    }
}

在VAInstrumentation中复写了execStartActivity,加了一行log信息,然后继续调用系统真实的realExecStartActivity方法。

3.5 设置instrumentation为我们代理的对象

最后我们把创建的代理替换下:

/**
* 创建代理VAInstrumentation
*/
final VAInstrumentation instrumentation = new VAInstrumentation(baseInstrumentation);
/**
* 获取当前activityThread对象
*/
Object activityThread = ReflectUtil.getActivityThread(base);
/**
* 设置instrumentation为我们代理的对象
*/
ReflectUtil.setInstrumentation(activityThread, instrumentation);

然后我们再调用startActivity启动另一个Activity看下log信息:

02-24 14:13:39.162 7209-7209/com.jared.virtualappdemo D/ViewRootImpl: ViewPostImeInputStage ACTION_DOWN
02-24 14:13:39.382 7209-7209/com.jared.virtualappdemo D/VAInstrumentation: ====== This is hook startActivity by eastmoon! =======
02-24 14:13:39.382 7209-7209/com.jared.virtualappdemo I/Timeline: Timeline: Activity_launch_request id:com.jared.virtualappdemo time:1900491538
02-24 14:13:39.443 7209-7209/com.jared.virtualappdemo D/Activity: performCreate Call secproduct feature valuefalse
02-24 14:13:39.453 7209-7209/com.jared.virtualappdemo D/Activity: performCreate Call debug elastic valuetrue
02-24 14:13:39.553 7209-7209/com.jared.virtualappdemo I/Timeline: Timeline: Activity_idle id: android.os.BinderProxy@15d76d41 time:1900491707
02-24 14:13:39.953 7209-7209/com.jared.virtualappdemo V/ActivityThread: updateVisibility : ActivityRecord{3a54776a token=android.os.BinderProxy@21031155 {com.jared.virtualappdemo/com.jared.virtualappdemo.MainActivity}} show : false

调用了我们自己的代理类了,已经被hook了,之后就可以做很多其他事情了,比如Activity必须要在AndroidManifest中注册,而插件化的时候是不可能预先知道插件中的Activity的,可以通过hook,占坑等来实现,这个后续再来实现下,基本上插件化的hook机制相信都能明白了。
github源码:https://github.com/eastmoon1117/StudyTestCase/tree/master/VirtualAppDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值