背景
当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。
这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?
答案是:可以的
方案
目前国内公布的方案有来自阿里巴巴AndFix与QQ空间的HotFix
Android插件化
插件化分热部署,动态加载资源,模块化开发.
热部署
热部署就是通常说的热修复.当发现紧急问题,紧急BUG时,需要自动升级用户的版本修复问题.
动态加载资源
资源一般是打包在APK时,有时候需要资源过大.不能全部打包进APK包里.例如动态换肤等功能.
模块化开发
DroidPlugin 是360手机助手在Android系统上实现了一种新的插件机制:它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。
典型案例:360开源的DroidPlugin
原理
在 Java 中,要加载一个类需要用到ClassLoader。
Android 中有三个 ClassLoader, 分别为URLClassLoader、PathClassLoader、DexClassLoader。
URLClassLoader
只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。
PathClassLoader
它只能加载已经安装的apk。因为 PathClassLoader 只会去读取 /data/dalvik-cache 目录下的 dex 文件。 例如我们安装一个包名为com.hujiang.xxx的 apk,那么当 apk 安装过程中,就会在/data/dalvik-cache目录下生产一个名为data@app@[email protected]的 ODEX 文件。 在使用 PathClassLoader 加载 apk 时,它就会去这个文件夹中找相应的 ODEX 文件,如果 apk 没有安装,自然会报ClassNotFoundException。
DexClassLoader
最理想的加载器。它的构造函数包含四个参数,分别为:
- dexPath,指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径. 如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得.
- dexOutputDir,由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径. 在Android系统中,一个应用程序一般对应一个Linux用户id,应用程序仅对属于自己的数据目录路径有写的权限,因此,该参数可以使用该程序的数据路径.
- libPath,指目标类中所使用的C/C++库存放的路径
- classload,是指该装载器的父装载器,一般为当前执行类的装载器
Android端代码
Android端主要需要时间的功能,就是改变dexpath路径.让类装载器优先取出来的jar包不是APK中的jar包 而是我们需要修复jar包.
热修复开源项目
项目 | 问题 |
---|---|
Nuwa | Gradle1.40 里Transform API无法打包 |
HotFix | 自动化不足,只能修复简单的类,且操作复杂 |
RocooFix | 正在开发,尚未完善 |
步骤
- 客户端依赖上述中任意一个项目
- 服务器开发对应的热修复接口
- 客户端增加对应的热修复代码
- 把需要修复的java文件编译成class文件
- 将class文件打入一个jar包中 jar cvf path.jar *
- 将jar包转换成dex的jar包 dx --dex --output=path_dex.jar path.jar
- 将最终jar发给服务器,配置到对应的地方
- 客户端下载好对应的文件.
- APP重启修复
总结
- 热修复目前来说比较适合修复紧急BUG,改动较小,一个或仅仅少数几个类的情况下,而且开发时间和成本比较高.
- 如遇到重大改动可使用强制升级方式
- 热修复还是会产生新的问题:增加包的大小,降低运行速度,产生新的兼容性问题
- 热修复等于避过应用市场的普通升级方式,谷歌会逐步修复该功能.