美文网首页Android进阶之路Android开发Android开发
Tinker热修复加载补丁代码流程分析

Tinker热修复加载补丁代码流程分析

作者: enjoycc97 | 来源:发表于2019-08-09 01:15 被阅读2次

本文以Tinker自带demo项目分析,假设当前已经存在补丁代码文件,本文主要是描述如何加载此补丁代码的流程,考虑内容较复杂,本文只涉及代码补丁文件加载不涉及资源补丁加载

总体流程分析

当前经过差分算法,得出补丁代码文件,存在本地。文件的路径是类似/data/user/0/tinker.sample.android/tinker/patch-1504f6a9/dex/tinker_classN.apk这种。
在应用启动流程将此补丁通过反射添加到PathClassLoader中dexElements数组中即可

主流程分析

  1. 查看AndroidManifest,发现demo中启动的Application是.app.SampleApplication是
    继承TinkerApplication
  2. 查看attachBaseContext是加载补丁流程,见
    此方法通过loadTinker();
  3. loadTinker();核心方法是反射TinkerLoader.tryLoader方法,加载流程是跟进此方法
  4. 由于本文只看加载代码补丁,所以只看方法TinkerDexLoader.loadTinkerJars即可,此方法又调用 SystemClassLoaderAdder.installDexes(application, classLoader, classNFile, legalFiles, isProtectedApp);本文重点分析此方法

查看installDexes方法

SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles, isProtectedApp);
参数解释:
application, 应用
classLoader, 默认应用的类加载器
optimizeDir, odex目录
legalFiles, 补丁文件
isProtectedApp 是否加固

主要完成2个事情,1个是判断7.0以后需要自定义ClassLoader同时替换默认类加载器
第二个是往类加载器中dexElements填充补丁文件

ClassLoader classLoader = loader;
if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) {
classLoader = AndroidNClassLoader.inject(loader, application);
}
if (Build.VERSION.SDK_INT >= 23) {
V23.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 19) {
V19.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, files, dexOptDir);
} else {
V4.install(classLoader, files, dexOptDir);
}

传入系统默认loader类加载器,如果是7.0以上需要
自定义类加载器实现AndroidNClassLoader.inject(loader, application)
包含如下2个步骤

  1. createAndroidNClassLoader
AndroidNClassLoader androidNClassLoader = new AndroidNClassLoader("", originalClassLoader, application)

同时调用recreateDexPathLis来搬运成员变量
其实就是把原来类加载器内部成员变量比如pathList ,nativeLibraryDirectories通过反射给自定义类加载器AndroidNclassLoader设置过去

  1. reflectPackageInfoClassloader方法

也是反射来替换应用默认的类加载器
反射比较不直观,翻译成函数调用就是
context.mBase.mPackageInfo.mClassLoader=自定义类加载器
当然这些成员变量都是不可以直接调用的,这么写只是更加直观,实际代码是一堆invoke反射的

mPackageInfo是LoadedApk对象。是ContextImpl内部成员变量,可以看作一个应用的基础信息

将补丁填充到ClassLoader的dexElements中分析

上述代码,对不同sdk适配,以最新的适配,sdk=28为例子
分析如下:

 if (Build.VERSION.SDK_INT >= 23) {
                V23.install(classLoader, files, dexOptDir);
 Field pathListField = ShareReflectUtil.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
           
            ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                suppressedExceptions));
 makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
                    List.class);

这里通过调用pathClassLoader中的makePathElements
方法实现把指定dex文件填充到类加载的dexElements数组,对于pathClassLoader.
加载类是通过一个个从数组加载遍历的,如果补丁是第一个则比bug代码先加载类达到预期效果

查看具体的类PathClassLoader.内部有成员变量pathList,而pathList有一个成员变量就是dexElements

PathClassLoader继承BaseClassLoader查看加载类

protected Class<?> findClass(String name) throws ClassNotFoundException {
131        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
132        Class c = pathList.findClass(name, suppressedExceptions);
133       
141        return c;
142    }

是通过pathList.findClass,继续查看

  public Class<?> findClass(String name, List<Throwable> suppressed) {
485        for (Element element : dexElements) {
486            Class<?> clazz = element.findClass(name, definingContext, suppressed);
487            if (clazz != null) {
488                return clazz;
489            }
490        }       
495        return null;
496    }

其实对于每一个数组去遍历,找到就返回,那加载类的时候补丁需要在有bug的dex前面即可,当补丁先加载好也就是达到修复的目的

总结:TinkerApplication启动时候将插件dex填充到dexElement数组中。使补丁比有问题的类先加载来实现代码修复

相关文章

网友评论

    本文标题:Tinker热修复加载补丁代码流程分析

    本文链接:https://www.haomeiwen.com/subject/gspxjctx.html