本文以Tinker自带demo项目分析,假设当前已经存在补丁代码文件,本文主要是描述如何加载此补丁代码的流程,考虑内容较复杂,本文只涉及代码补丁文件加载不涉及资源补丁加载
总体流程分析
当前经过差分算法,得出补丁代码文件,存在本地。文件的路径是类似/data/user/0/tinker.sample.android/tinker/patch-1504f6a9/dex/tinker_classN.apk这种。
在应用启动流程将此补丁通过反射添加到PathClassLoader中dexElements数组中即可
主流程分析
- 查看AndroidManifest,发现demo中启动的Application是.app.SampleApplication是
继承TinkerApplication - 查看attachBaseContext是加载补丁流程,见
此方法通过loadTinker(); - loadTinker();核心方法是反射TinkerLoader.tryLoader方法,加载流程是跟进此方法
- 由于本文只看加载代码补丁,所以只看方法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个步骤
- createAndroidNClassLoader
AndroidNClassLoader androidNClassLoader = new AndroidNClassLoader("", originalClassLoader, application)
同时调用recreateDexPathLis来搬运成员变量
其实就是把原来类加载器内部成员变量比如pathList ,nativeLibraryDirectories通过反射给自定义类加载器AndroidNclassLoader设置过去
- 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数组中。使补丁比有问题的类先加载来实现代码修复
网友评论