DroidPlugin代码分析(一) 背景知识

前段时间360github上公开了DroidPlugin的代码,工作中也正好要用到类似的技术,于是打算花点时间研究一下。

在开始之前,首先需要了解一个概念:Java动态代理。这是实现hook的一个关键技术,在代码里被大量运用。那么什么是Java动态代理呢?下面以一个小例子进行说明。

首先我们定义一个IFruit接口,里面只有一个方法,用来打印水果的名字:

public interface IFruit {
    /**
     * 打印水果的名字
     */
    void printName();
}

接下来写两个实现类,一个叫Apple,一个叫Banana

public class Apple implements IFruit {
    private final static String TAG = Apple.class.getSimpleName();
    private static String mName = Apple.class.getSimpleName();
    @Override
    public void printName() {
        Log.d(TAG, "I am " + mName);
    }
}

public class Banana implements IFruit {
    private final static String TAG = Banana.class.getSimpleName();
    private static String mName = Banana.class.getSimpleName();
    @Override
    public void printName() {
        Log.d(TAG, "I am " + mName);
    }
}

写完了,这时候突然需求变了,要求在这句打印的上面和下面再各打印一行”##########”。

我们该怎么改,是不是直接去修改AppleBanana类的实现呢?如果有100种水果是不是要改100次?另外,现在接口里只有一个方法,如果里面有50个方法,要求每个方法打印的前后都要加上一行”##########”,是不是每个水果都要改50遍?这简直是个噩梦。。。

幸好有Java动态代理可以完美解决这个问题。其实我们只要“劫持”或者叫“hook”住接口里每个方法的调用,在调用前和调用后各加上一行打印就可以了。

那么怎么“劫持”呢?首先写一个IFruitHook类,实现InvocationHandler接口。需要实现这个接口的invoke()方法:

public class IFruitHook implements InvocationHandler {
    private final static String TAG = IFruitHook.class.getSimpleName();
    private Object mHookedObj;

    public IFruitHook(Object hookedObj) {
        mHookedObj = hookedObj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.d(TAG, "##########");
        Object result = method.invoke(mHookedObj, args);
        Log.d(TAG, "##########");

        return result;
    }
}

可以看到,我们在接口每个方法调用的前后各加了一行打印。

接下来我们写一个方法获取代理对象,返回的接口还是IFruit,但是调用接口方法的时候就会调进上面那段代码了:

    private IFruit getProxy(IFruit fruit) {
        return (IFruit) Proxy.newProxyInstance(
                fruit.getClass().getClassLoader(),
                fruit.getClass().getInterfaces(),
                new IFruitHook(fruit));
    }

最后我们来简单测试一下:

IFruit apple = new Apple();
getProxy(apple).printName();
IFruit banana = new Banana();
getProxy(banana).printName();

可以看到,打印结果的上面和下面各增加一行“##########”的打印。

回过头来再看看getProxy()这个方法做了什么?其实它通过Proxy.newProxyInstance()方法动态加载了一个叫做$Proxy0的类(该类extends Proxy implements IFruit),创建了一个该类的对象,并且注册了我们的IFruitHook。在调用这个对象的printName()方法时,它会把它自己、方法对象、参数都传入到IFruitHookinvoke()方法中:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

这样我们就可以在这个方法里进行各种处理了,甚至你可以不调用原始对象方法,全部替换成你自己的实现。

 

Java另一项强大的功能是反射,你可以在运行时动态修改类的实现。比如我们可以通过下面的方法把苹果的名字改成榴莲~~ 再次运行的时候你就会发现打印出来的是”I am Durian”:

    private void changeFruitName() {
        try {
            Class cls = Class.forName("com.xinxin.dynamicproxy.Apple");
            Field gQueryInterface = cls.getDeclaredField("mName");
            gQueryInterface.setAccessible(true);
            gQueryInterface.set(null, "Durian");
        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

Java动态代理结合反射可以发挥更强大的作用,因为它可以篡改你传入的参数以及返回值!这个其实就是Droid Plugin“劫持”系统API的主要手段。举个例子:

我们启动activity的时候,最终会通过ActivityManagerNative.getDefault()获取一个全局的IActivityManager代理对象,然后通过binder远程调用到AMS那边。Droid Plugin会先通过反射替换掉这个全局对象,然后通过动态代理技术修改所有系统API实现。这样虽然外部的应用程序看起来还是调用的系统API,但是实际上内部已经被偷梁换柱了。

背景知识学习完毕,下一篇开始正式分析Droid Plugin代码~

你可能感兴趣的:(插件化)