Android 热修复
本文链接:https://blog.csdn.net/feather_wch/article/details/132052856
文章目录
- Android 热修复
- 方案对比
- AndFix@Deprecate
- Robust-即时生效
- Tinker-非及时生效
- ClassLoader
- 实战一
- 手动打补丁包
- 热修复二
方案对比
AndFix@Deprecate
1、AndFix为什么可以实时生效?
在native层动态替换掉Java层的方法,通过native层hook java层代码。
2、如何拿到补丁包的有注解Method?
- 补丁包包含Test.class
- 类加载Test.class
- 反射Method
- 拿到注解(标记了要替换谁),找到目标类
3、如何完成两个对象的替换?bug method => fix method
Java层伪代码:
BugMethod.clazz = FixMethod.clazz
Native层代码:
replace(env, jobject bug, jobject fix)
{// 把bug method的所有属性,都替换为,补丁method的所有属性
}
4、后续拿到的都是修复后的Method.class类对象
Robust-即时生效
美团方案:抖音还在用,纯Java实现
- 对每个函数插入一段代码,字节码插桩技术。
- 编译阶段,在class字节码写代码
- 插入开关: 如果没有补丁包,就返回原先逻辑。有补丁包,交给补丁。
class State{public static ChangeQuickRedirect changeQuickRedirect;public long getIndex{if(changeQuickRedirect != null){return PatchProxy.accessDispatch(xxxx);}return 100;}
}
- 拿到补丁包内的类对State.changeQuickRedirect进行赋值
Class<StatePatch> clz = StatePatch.class; // 拿到补丁包的类
State.changeQuickRedirect = clz.newInstance(); // 赋值,影响开关的条件判断
相关类:PatchesInfoImpl.java、StatePatch.java
Tinker-非及时生效
1、关键词:DexDiff、增量更新
2、工具:
bsdiff 将两者区别信息,放到patch上
bsdiff 1.txt 2.txt patch
bspatch:合成 1.txt + patch = 2.txt
3、Tinker是差分包 + Bug Dex = 修复后Dex
4、热修复思路 => 将补丁Dex放到数组前面
5、什么时候热修复?越早越好,防止类更早加载,就失效了
6、补丁Dex什么时候删除?不能删。
7、Application有Bug怎么办?不能有Bug
ClassLoader
1、实现类:BootClassLoader、PathClassLoader、DexClassLoader、InMemoryClassLoader
2、下面类的ClassLoader是什么?
MainActivity.class.getClassLoader() // Path 我们
AppCompatActivity.class.getClassLoader() // Path 第三方
Application.class.getClassLoader() // Boot 系统
getClassLoader() // Path 应用
3、PathClassLoader原理
- parent => BootClassLoader
public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {}public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {}
}
- loadClass做了什么?双亲委派,先交给BootClassLoader去加载。
- findClass源码
// BaseDexClassLoader.javaDexPathList pathList; // 内部有一个dexElements数组,存储着该ClassLoader的所有dex/resources的路径public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexFiles);// DexPathList}protected Class<?> findClass(String name) throws ClassNotFoundException {Class c = pathList.findClass(name, suppressedExceptions); // DexPathList中查找if (c == null) {ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);throw cnfe;}return c;}
// DexPathList.javapublic Class<?> findClass(String name, List<Throwable> suppressed) {for (Element element : dexElements) {// Element内部的DexFile中查找Class<?> clazz = element.findClass(name, definingContext, suppressed);if (clazz != null) {return clazz;}}return null;}
// Element,DexPathList的静态内部类private Element[] dexElements;static class Element {private final File path;private final DexFile dexFile;// DexFilepublic Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed) {return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;}}
实战一
手动打补丁包
1、手动补丁包流程
- bug修复
- 编译项目,生成类的class文件(make project)
- 生成补丁包
dx --dex --output=patch.jar com/zte/tv5gshow/test/Bug.class
2、补丁包放入到DexFile数组
- 获取PathClassLoader对象
- 反射到DexPathList对象
- 反射到Element数组(oldElement)
- 把补丁包编程Element数组,patchElement:反射执行makePathElement()
- 合并oldElement + patchElement = newElement (Array.newInstance)
- 反射吧oldElement数组设置为newElement数组
makePathElement()需要List File集合
File file = new File("/sdcard/patch.jar")
list.add(file);
// 作为List的参数传入
热修复二
1、PathClassLoader是哪里创建的?
ActivityThread中创建了PathClassLoader并且传入了Apk的dex路径
LoadedApk->ApplicaitonLoaders.getDefault().getClassLoader()
-> ClassLoaderFactory.createClassLoader
-> new PathClassLoader(dexPath, librarySearchPath, parent)
2、so修复 => DexPathList内部属性nativeLibraryPathElements
3、资源修复 => 和换肤一样