参考资料:微信Android热补丁实践演进之路
简单来说,在编译时通过新旧两个Dex生成差异patch.dex。在运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中。为了补丁包尽量的小,微信自研了DexDiff算法,它深度利用Dex的格式来减少差异的大小。它的粒度是Dex格式的每一项,可以充分利用原本Dex的信息,而BsDiff的粒度是文件,AndFix/Qzone的粒度为class。
—来自<微信Android热补丁实践演进之路>
项目的Github地址:
Tinker_imitator
梳理一下过程:
我们通过gradle打包app的时候记录下生成的dex,当线上版本出现问题以后.使用gradle插件打出相对应的dex,并且与之前生成的dex做差分生成patch.dex,将patch.dex push到手机上,在手机端上将patch和旧的dex合成成新的dex.,并且将这个dex放置在classloder的最前面。
主要技术点
- 差分算法
- 为了使patch包足够的小就应该保证新旧dex包内的内容足够的相近
第一个难点:
差分算法我们使用google开源的Bsdiff。
通过编译成so文件.实现了在手机端合成算法。
第二个难点:
我们会gradle
打包的时候。记录每个dex的class列表。在打patch的时候会根据这个列表。
并且检查这个列表中的文件是不是有改动. 将改动所在的dex.打出新的dex. 使用差分算法生成patch。
怎么记录class列表?
我们都知道gradle是通过dx命令生成dex。
我们查看dx源码发现源码位于:
/dalvik/dx/src/com/android/dx/command/dexer/Main
dx源码中有一个verbose参数,会在处理所有的文件(eg class )的时候会有一个输出"processing xxx .class...
,但是比较可惜的是现有的代码只能获取都处理class信息,不能获取分dex的信息。这里的做法是,自己编译dx.jar。
在dx源码中加入下面代码。
dx然后在gradle中指定我们编译的dx.jar。
gradle怎么使用dx的呢?
源码地址com.android.builder.core.DexProcessBuilder.build
我们看出这里指定了dx地址。我们直接hook了AndroidBuilder类,
然后直接生成自定义的DexProcessBuilder将dx地址指向了我们自己的编译的dx。
同时hook了一个BaseProcessOutputHandler对dx的输出信息进行分析。
这样下来就能获取dex的class列表。
比较有意思的是在不开启Instant Run的情况下会把所有的java代码合成一个combined的jar,dx其实是操作是这个combined的jar。Instant Run情况就不一样了。
手机端sdk
sdk就比较简单了。将过来的patch和手机上旧的dex使用bsdiff进行合成新的dex并且放置在classloder的最前面。
比较有意思的是在当打完patch后app进入后台以后会kill当前进程。并发起一个NoneService服。然后app会被重新启动,而activiy栈也会被保存起来。重新构建起来。
尾巴
最近阿宅开了个QQ实践群(568863373),欢迎大家进来玩耍,也可以关注我们的公众号:魔都三帅
Paste_Image.png
网友评论