一、前言
随着业务的迭代发展,消金的业务量也在快速增长。在这个过程中也扩展了一些新的业务,“嵌入式”SDK。也就是把已有的一些业务嵌入到第三方APP里面消金SDK累计接入6个依赖方,如果快狗打车、驾校一点通、58同城本地版未来会有更多接入方,故对代码质量和bug就有了一定的要求。
记忆尤深的两个事情:
1、项目初期产品的期望是只发布一个版本,即项目的开始便是结束。业务不会有迭代,对bug的要求是0。
2、线上出现了问题,具体问题我就不细节描述了。。
二、ClassLoader
1、ClassLoader
类加载器是个抽象类,是所有加载器的父类。主要有两个构造方法一个有参一个无参。传入parent基本上不允许为null。每个ClassLoader实例都有一个父类加载器的引用。
有两个重要的方法loadClass()、findClass()。loadClass加载类时首先判断是否已经加载过,加载过直接return。
否则就去调用传入parent的loadClass一层一层的去找如果还是找不到才会由自己加载。最后还是没有找到则会调用findClass抛出ClassNotFoundException异常。
这个过程就是就双亲委派模型。
2、双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
好处:
1、可以避免类的重复加载
2、避免了Java的核心API被篡改(如果自己编写了个Object类,用自定义的ClassLoader去加载那java的核心代码也无法保证)
这也就是Tinker发完补丁包后需要冷启动才能应用成功。
Tinker原理:
- 通过反射获取到DexPathList属性对象pathList
- 进行合并Element[],把path.dex合并的放到dexElements最前面去
- 根据双亲委派模型就会先加载修复后dex
3、DexClassLoader
主要用于加载我们修复bug后的dex
- dexPath:dex、jar文件的路径
- optimizedDirectory:缓存路径,存放优化后的 dex(Android 8.0后为null)
- librarySearchPath:native库的路径,一般都穿空
- parent:父类加载器
三、patch.dex
在我们正常的业务逻辑中一个Activity会有很多方法来实现各种各样的逻辑,避免不了个别方法会出现异常比如我们常见的NullPointerException。
Hunk会在重要的类中添加ChangeMethod接口,和方法最前面插入空方法,当methodB出现异常后我们根据收集到的崩溃信息来判断具体出现问题的代码。
然后对methodB插入的这个空方法来实现对有问题代码的修改。
判断methodB入的方法是有修改,如果有这直接retrun不会再调用有问题的代码。这样就达到我们修改bug的目的。
1、修复问题代码
比如我们要获取北京天气内容并显示在页面上,天气内容写死在代码里。这时候如果我们想改变天气让北京下雨怎么做那?
- 通过工具定位到问题所在类、代码行数
- 检查导致北京不下雨的原因是什么?
- 修复北京的天气为暴雨。
2、生成dex
.java-->.class→.dex
我们通过DexClassLoader加载修复后的dex来达到修复的bug的能力。首先生成dex
- 把修复后的代码run一下。在build/intermediates/classes/debug/包名 下会有对应的class文件
- 找到对应的class文件copy到和项目结构一样的包名下
- 通过Android SDK 的dx.bat把class打包成dex文件(dx --dex --output :\dex\patch.dex :\dex\)
- 把生成后dex文件下载到手机的固定位置,通过dexClassLoader
3、加载dex
找到生成后的dex的指定目录,通过classloader加载changeMethod的具体实现类,把加载到的具体实现类赋值给要修复bug的类中的静态变量。
再次调用这个方法时会先调用我们的hunk实现,这样就达成了我们要修复bug的能力。
(举例:小明想了解当天的天气状况,当小明拿出手机要查看的时候我告诉了他天气状态。这样小明就不需要拿手机查看天气了)
问题一:如何把changeMethod的具体实现类赋值给有Bug类的静态变量?
首先通过dexClassLoader加载有bug的HunkAcitivty,因为这个类里面有一个我们声明静态的ChangeMethod接口。然后通过反射拿到这个类里面的所有字段进行对比直到拿到ChangeMethod。
再加载我们打成dex里面ChangeMethod实现类,把具体实现类赋值给bug类里面ChangeMethod接口。
这样就完成赋值操作
问题二:怎样实现加载修复后的具体方法?
实现accessDispatch方法,也就是每个方法前面插入的代理方法。还是通过dexClassLoader加载指定类,再通过反射调用这个类里方法。
这样就完成一整套的流程。
4、效果
代码以开源到github:欢迎star https://github.com/blindmonk/Android
四、插桩(之前写过直接插桩超链接已加上)
1、打包流程
2、自定义Gradle插件
3、ASM字节码
如果SDK模块比较多类比较复杂,可以使用字节码插桩的形式为每个类每个方法进行插桩,这样就避免了手动去写代码。
不过这样也有一些弊端会使代码量增大,包的体积增大,也会有一些不必要的代码。
各有各的优势和劣势,取决于自己项目的实际情况选择最优的方案。
总之没有最好、最优的热修复框架只有最适合当前项目的。
五、未来优化方向
1、补丁上传?
2、补丁下发?
3、补丁加载?
3、补丁失效策略?
对补丁的管理也是比较繁琐的,补丁下发的版本平台管理(像tinker的大于1W下载量就会收费),避免补丁被加载时被篡改做加密解密处理
如果补丁本身就有异常需要针对补丁做失效策略,对补丁做标记让不让其加载
理论上只要成功加载补丁对补丁的应用成功率可以打达到99%的,当然需要也有必要对补丁的成功率做统计分析。