默认已知:Android Apk的打包过程。
Tinker热修复分三部分:
class文件修复、资源文件修复和so文件修复。
一、class文件修复(代码修复)原理
1. ClassLoader源码解读,局部的继承关系:
ClassLoader->BaseDexClassLoader->DexClassLoader->DexClassLoaderProvider
2. Android Dex文件的加载过程:
我们会使用DexClassLoader去加载dex文件,DexClassLoader会将这个任务委派给DexPathList中的makeDexElements方法,在makeDexElements中调用了native层的 c++方法去真正的加载dex文件,然后返回DexFile的对象,通过这个对象构建一个Element的对象,然后将这个Element添加到dexElements的数组中。
3. class文件的查找过程:
DexClassLoader通过findClass去查找一个类,同样它也是委派给DexPathList的findClass去查找,在DexPathList的findClass中会去遍历我们上面创建的dexElements数组,然后在每个dex中去查找相应的类,找到之后就返回,不再向后查找。
4. 补丁包的合并过程:
Patch拿下来之后,会开启一个Service,把Patch包和Bug Dex合并,并置于tinker的文件加载路径中。
5. 补丁包的加载流程:
读取到有合并后的补丁dex,通过反射得到DexPathList中的dexElements数组,并将新的dex放在数组的最前面,完成修复。
二、 资源文件修复
1. Resource类对象的构建过程:
所有的资源文件是通过Resource类获取到的,创建一个Resource对象需要三个参数AssetManager、DisplayMetrics和Configuration,后两个参数都是全局单例的,直接通过getResources().getDisplayMetrics(),
getResources().getConfiguration()就可以,而一个AssetManager对象的关键就在于addAssetPath方法以指定resource目录的路径,直接newInstance后通过反射拿到addAssetPath,invoke传参一个resDir目录即可得到对应的AssetManager对象。
2. 补丁加载过程:
先将Patch中的Resource和原有Resource合并,而后保存到本地->
通过当前的Activity线程去获取到所有已加载的apk(loadedApk)->
将所有loadedApk的resDir目录都替换成新的Resource目录->
创建新的resDir目录对应的AssetManager对象->
替换原有全部的Resource对象里的AssetManager,即可完成修复。
三、 动态链接库修复
Android里面关于so的加载的两种方式:
1. System.loadLibrary
这种方式传入的是so的名字,会直接从系统的目录去加载so文件,系统的路径包括/data/data/${package_name}/lib、/system/lib、/vender/lib等这类路径。
2. System.load
这种方式传入的是so的绝对路径,直接从这个路径加载so文件。
Tinker的so文件热更新的原理就是通过方式二,直接加载新的so实现的。
1. 补丁生成:
生成补丁时比较新旧so文件使用BSdiff算法生成补丁包。
2. 补丁合成:
下发补丁成功后根据BSpatch算法将补丁包和旧的library合成新的library并保存在tinker下面的目录下:/data/data/${package_name}/tinker/lib。
3. 补丁加载:
通过System.load加载该目录下面的so文件。
注意:Tinker中so的热更新对用户并不是无感的,需要用户自发的去加载自己需要的库文件。
下面是tinker的wiki里关于这方面的描述:
Tinker并没有直接将补丁的lib路径添加到DexPathList中,理论上这样可以做到程序完全没有感知的对Library文件作补丁。这里主要是因为在多abi的情况下,某些机器获取的并不准确。
所以想要加载最新的库,需要自己使用TinkerInstaller.load*Library去加载库文件,它会自动尝试先去Tinker中的库文件加载,加载不成功会调用System.loadLibrary调用系统的库文件。如TinkerInstaller.loadArmLibrary()或TinkerInstaller.loadArmV7Library()