[玩转WX之二]如何让插件稳定运行?一步一步分析Tinker热加载关键流程

系列文章:
上篇 [玩转WX之一]一行命令打印安卓WX数据库密码并取出数据库文件到电脑查看

有兴趣的请关注,系列文章保持高实用性,不说废话,点明过程中的重点,最后直接给出结果。

基础须知:
1.热加载是可以让安卓App不重新安装但是又能改机运行逻辑的技术,腾讯系软件使用自家的热加载系统Tinker。
2.应用程序会从清单的appliction name开始进行运行, 一把程序加壳,多dex,热加载,都在这里做工作,这是应用程序自己的代码能被最早执行的地方。
3.热加载分成总体分资源,so, dex热加载,业务逻辑代码绝大部分在dex, 所以一般关注dex热加载比较多。
4.系统会先执行Application里面的生命周期函数attachBaseContext,比我们熟悉的onCreate更早。

如果代码是热加载的,很有可能会导致插件运行出错。如下所示:

wx热加载前的classloader
dalvik.system.PathClassLoader[DexPathList[[zip file "/system/framework/org.apache.http.legacy.boot.jar", zip file "/system/framework/com.google.android.maps.jar", zip file "/data/app/com.tencent.mm-cm3A_pxRWC2RjCfVnM6X4A==/base.apk"],nativeLibraryDirectories=[/data/app/com.tencent.mm-cm3A_pxRWC2RjCfVnM6X4A==/lib/arm64, /data/app/com.tencent.mm-cm3A_pxRWC2RjCfVnM6X4A==/base.apk!/lib/arm64-v8a, /system/lib64]]]

wx热加载后classloader
dalvik.system.DelegateLastClassLoader[DexPathList[[zip file "/data/user/0/com.tencent.mm/tinker/patch-d93c1e4d/dex/tinker_classN.apk"],nativeLibraryDirectories=[/data/user/0/com.tencent.mm/tinker/patch-d93c1e4d/lib/lib/arm64-v8a, /data/app/com.tencent.mm-cm3A_pxRWC2RjCfVnM6X4A==/lib/arm64, /data/app/com.tencent.mm-cm3A_pxRWC2RjCfVnM6X4A==/base.apk!/lib/arm64-v8a, /system/lib64]]]

为了兼容热加载,我们也要对类加载器进行修正。如何修正热加载器,我们要先对热加载流程做个分析,做到心中有数。

以下代码基于WX7.0.19

我们开始从0开始跟进分析,先查看应用程序入口

来看类  com.tencent.mm.app.Application

来看 com.tencent.mm.app.Application 父类 TinkerApplication

类初始化之后,就会由系统调用生命周期函数 attachBaseContext

loadTinker

反射调用真正的tryLoad

使用 tryLoadPatchFilesInternal 进行热加载

快速看下这个函数,发现比较重要的是loadTinkerJars
这个函数也对dex热加载文件进行验证以及读入

由loadTinkerJars进入installDexes

进入installDexes,根据不同的系统环境, 安装热更新

安卓热更新的关键是创建新的TinkerClassLoader,合并原有的classLoader

完成新的类加载器创建,并替换Application的类加载器

替换线程上下文和LoadedApk的classloader

替换classloader完毕,校验是否加载成功,如果成功了,就用tinker_xxx.dex等的类

经过上面分析,我们这样玩:
wx没替换classloader,我们就不替换xp的classloader;
wx替换了classloader,我们跟着替换xp的classloader。

class Hook : IXposedHookLoadPackage {    override fun handleLoadPackage(LoadPackageParam: XC_LoadPackage.LoadPackageParam) {        if (LoadPackageParam.packageName == "com.tencent.mm") {            Log.i("ACCESS", "%%% app_cl 1: ${AndroIDAppHelper.currentApplication()?.classLoader}")            // 这个时候,wx未修改classloader,保持xp的不变,            XposedBridge.hookAllMethods("com.tencent.tinker.loader.app.TinkerApplication".getClass(LoadPackageParam.classLoader), "onBaseContextAttached", object :XC_MethodHook() {                override fun afterHookedMethod(param: MethodHookParam) {                    LoadPackageParam.classLoader = XposedHelpers.callMethod(param.thisObject, "getClassLoader") as ClassLoader                    LoggerUtil.i("ACCESS", "%%% app_cl 2: ${LoadPackageParam.classLoader}")                    XposedBridge.hookAllMethods("com.tencent.mm.app.MMApplicationWrapper".getClass(LoadPackageParam.classLoader), "onCreate", object :XC_MethodHook() {                        override fun afterHookedMethod(param: MethodHookParam) {                            // 这个时候tinker有可能已经发生热加载了                            val app = XposedHelpers.getObjectField(param.thisObject, "app")                            val context = XposedHelpers.callMethod(app, "getApplicationContext") as Context                            Log.i("ACCESS", "%% context:${context} cl:${context.classLoader}")                            // 使用全局static对这两个关键变量进行保存。                            Global.context = context                            Global.classLoader = context.classLoader // 后面所有查找类加载类使用这个类加载器。不要再使用xp回调传过来的。或者使用TinkerClassLoader.findClass。                            // 下面开始调用注入逻辑                        }                    })                }            })        }    }}

THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发
管埋员的头像-小北的自留地

昵称

取消
昵称