Xposed模块开发,免重启改进方案

Xposed模块开发,免重启技巧

工欲善其事,必先利其器.我们在开发Xposed模块的时候,每一次修改Hook方法,都需要重启一次设备,很是麻烦,还浪费了很多宝贵的时间,有没有不重启的办法呢?当然有的,不然我在这里bb什么?
Xposed模块开发,免重启改进方案_第1张图片

原理图

Xposed模块开发,免重启改进方案_第2张图片

具体实现

Android设备安装一个app后,系统会在/data/app/目录下保存一份原始的apk安装包,当我们覆盖安装这个app时,系统同样会删除旧的apk文件,而保留新的apk文件。同样的,我们的Xposed模块(即使没有界面)安装后,也会在/data/app/目录下保存或者更新这个apk。这里就是利用系统备份的这个apk做了些文章

寻找apk文件

不过在Android api_21之前和Android api_21及之后,系统在/data/app/目录下保存原始apk文件方式是不一样的。
假设当前程序的包名为com.dx.test,则在api_21之前,保存方式为/data/app/com.dx.test-1.apk或者/data/app/com.dx.test-2.apk,而在api_21及之后保存方式为/data/app/com.dx.test-1/base.apk或者/data/app/com.dx.test-2/base.apk.那么xxx.xxx.xxx-1xxx.xxx.xxx-2有什么区别呢?如果该app属于新安装则保存为xxx.xxx.xxx-1,如果为覆盖安装则保存为xxx.xxx.xxx-2。这样的话要找到这个文件不是很简单么?下面马上给出代码。

//通过包名寻找apk文件
private File findApkFile(String pkg){
    //具体实现略
}

Xposed模块开发,免重启改进方案_第3张图片
额。。。你TM在逗我么?大哥,我错了,不要打我。
Xposed模块开发,免重启改进方案_第4张图片
完整的代码我已经上传到github了,下面放出链接!

调用apk中的方法

apk文件倒是找到了,我们怎样才能实例化这个类,进而调用其中的方法呢?
Android虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中,我们我们为什么不可以自己去加载一个类呢?这里我们自定义一个Classloader,并使用这个Classloader去加载这个apk文件,然后使用反射去实例化对象,并调用具体的方法。

/**调用真正的包含hook逻辑的方法
  * @param pkg   当前app的packageName
  * @param handleHookClass  指定由哪一个类处理相关的hook逻辑
  * @param handleHookMethodName 处理相关的hook逻辑的方法名
  * @param loadPackageParam 传入XC_LoadPackage.LoadPackageParam参数
  * @throws Exception
  */
private void invokeHandleHookMethod(String pkg, String handleHookClass, String handleHookMethodName, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        //利用上面实现的代码寻找apk文件
        File apkFile = findApkFile(pkg);
        //自定义Classloader
        PathClassLoader pathClassLoader = new PathClassLoader(apkFile.getAbsolutePath(), ClassLoader.getSystemClassLoader());
        try {
            //使用反射的方式去调用具体的Hook逻辑
            Class cls = Class.forName(handleHookClass, true, pathClassLoader);
            Object instance = cls.newInstance();
            Method method = cls.getDeclaredMethod(handleHookMethodName, XC_LoadPackage.LoadPackageParam.class);
            method.invoke(instance, loadPackageParam);
        } catch (Exception e) {
            throw e;
        }
    }

完成

这里,我们假设有一个HookLogic类,其中的doHook方法中实现了具体的Hook逻辑,我们只需要在模块的入口处调用上面的invokeHandleHookMethod方法,正真的Hook逻辑就会被调用了(注意xposed_init配置文件要指向HookLoader).

//注意assets下面的xposed_init配置文件要指向这个类
public class HookLoader implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        invokeHandleHookMethod(pkg, "xxx.xxx.HookLogic", "doHook", loadPackageParam);
    }
    此处省略若干行代码....
}

然后我们就可以愉快的玩耍了,我们只需要在HookLogic类的doHook方法中像以前一样实现具体的hook逻辑.

public class HookLogic{
    @Override
    public void doHook(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
        if (loadPackageParam.packageName.equals("xxx.xxx.xxx")){
            ...此处省略具体的hook逻辑
        }
    }
}

当然,第一次安装还是需要重启设备的,重启后我们在HookLogic的doHook方法中的任何修改,都不需要再重启设备,只需要杀死宿主程序让其重新运行即可.

总结

  • Android设备安装一个app后,系统会在/data/app/目录下保存一份原始的apk安装包,当我们覆盖安装这个app时,系统同样会删除旧的apk文件,而保留新的apk文件。同样的,我们的Xposed模块(即使没有界面)安装后,也会在/data/app/目录下保存或者更新这个apk.
  • 我们为什么不事先将Hook的逻辑写在一个apk中,然后自定义一个Classloader去加载这个apk,然后去执行其中的Hook逻辑呢?这样每次修改Hook逻辑打包重新安装后,就可以直接去加载这个apk,而无需重启设备.
  • 这里就是这样实现的,不过我们可以将”加载apk”的逻辑和”Hook逻辑”写在一个app中,我们通过读取这个包含”hook逻辑”的apk文件,然后new一个PathClassLoader,该PathClassLoader用于加载包含”hook处理逻辑”的类,最后使用反射的方式进入到程序入口。
  • 由于这里是动态加载的”hook逻辑”,所以不需要每次都重启设备,仅仅在第一次安装需要重启。
  • 虽然不用每次都重启设备了,不过由于Xposed实现机制的原因(handleLoadPackage方法的被调用时机的问题),需要杀死宿主程序后,并重新启动宿主程序才能生效。

优缺点:

  1. 采用传统方式开发Xposed模块时,每次修改Hook逻辑时,都需要重启设备;
    值得注意的是,由于这些hook代码提前加载进了Android虚拟几种,因此这种方式效率更高。

  2. 采用改进方式开发Xposed模块时,由于每次调用的Hook逻辑都是从apk文件中动态加载出来的,所以效率略慢,但优点是只需要在第一次安装时重启设备,大大提高了开发效率;
    如果你对实际运行效率特别在意,可以在开发的时候使用改进方式,而在具体发布使用的时候修改配置文件,效率和传统方式是一样的.

完整代码: https://github.com/shuihuadx/XposedHook.git

你可能感兴趣的:(Xposed)