大家好,我是拭心,这篇文章是一个好友 Divin 的投稿,介绍 SDK 热更新的一种实现思路,希望对你有所启发。
一、背景
App热更新
目前市面上成熟的商业热更新方案不少,有腾讯Bugly的Tinker封装,有阿里云的Sophix,也有游戏垂直行业的卓盟乐变。这些成熟方案,都有一个适用范围,即对App、对游戏整包进行热更新。前两者是和包名绑定在一起的,所以只适用于App热更新;而卓盟乐变则专注于游戏行业,可支持多渠道包热更新。其实最好的还是Sophix,可惜没有开源,虽有公开原理,但是公开资料里也透露了探索与开发周期长达9个月。
在社区,比较流行的热更新有Tinker
、QZone
、AndFix(HotFix)
、Sophix
、Robust
、Dexposed
、Nuwa
、Amigo
,同商业热更新方案一样,也是适用于App整包热更新。在这些方案里,影响力最大的是微信的Tinker
方案,13048个Star,拥有完善的文档,整个框架注重高可用性,最重要的是官方持续维护,在2018年12月,merge
7次。相比之下,其他有在Github上开源的框架,star
数都是7000以下,上次更新时间都在1年前,甚至2年前。
SDK热更新
SDK热更新,这是一个极少被关注的问题,Google、百度上相关的文章一篇都没有。我们首先进行思考,SDK热更新
同App热更新
有什么不同?,SDK热更新
要做什么?
SDK热更新同App热更新有什么不同?
•App热更新,输入的是一个基准包和一个新版包,输出的是差分包(或补丁),将这个差分包(或补丁)下载到客户端,客户端加载后生效。•SDK热更新,输入的是一个基准SDK和一个新版SDK,输出的也是差分包(或补丁),不同的是,SDK会被集成到不同的游戏包中,这个游戏包也会被分成各式各样的渠道包,我们要将这个差分包(或补丁)下载到所有游戏、所有渠道包,并加载生效。
SDK热更新要做什么?
1. 对SDK的代码、资源进行标识,我们要进行热更新的对象,就是这些代码、资源。
比如,我们可以进行这样标识:所有在com.divin.
包名之下的java类,所有assets/divin/
文件夹之下的Assets文件,所有以divin_
开头的Res文件,所有/res/values/
文件,所有以divin_
开头的so文件。
2. 在热更新的整个流程,对上述代码、资源进行特别操作。
包括build(计算差分)、patch(合并差分)、load(加载差分)。
十分感谢微信Tinker
的开源,对外开放了完整的热更新过程,站在伟人肩上,下面的SDK热更新,都是基于Tinker
开源库进行的修改。
热更新重点
1. dex热更新,即Java代码热更新。
阿里系(AndFix,Hotfix)走的底层替换方案,好处在于实时生效,腾讯系(Tinker)走的是类加载方案,好处在于高兼容性。阿里百川系(Sophix)就有点机智了,两种方案都有使用,还进行了一定的升级,优先走底层替换方案,底层替换方案走不下去了就走类加载方案。
AndFix(HotFix)的底层替换方案已过时,Sophix的无视底层具体结构的底层替换方案较新。感兴趣的同学可以深入了解下,追寻极致的代码热替换[1]。
Tinker的类加载方案,需要重启应用后让Classloader去加载新类。因为Android上无法对一个类进行卸载,不重启,则无法加载新类。
2. 资源文件热更新。
这里也是有两个流派,一个流派是参考Instant Run通过addAssetPath加载新的资源包到AssetsManager,然后再替换Resource中的AssetsManager;一个流派是构造新的R文件资源地址以0x66开头的资源包,再通过addAssetPath加载新的资源包到AssetsManager,因为新的R文件资源地址以0x66开头,新的Java代码里,也引用0x66开头的资源,这样就可以新旧资源不干扰且都能生效。
Tinker属于第一个流派[2],Sophix属于第二个流派[3]。
非常遗憾的是,在我们基于Tinker实现SDK资源更新(即指定资源更新)时,只知道第一个流派,并不知道第二个流派(那篇文章没细读,印象不深)。所以后文中所提到的SDK资源更新(指定资源更新),其实是自己摸索出来的,可以理解成流派二的拼多多版,实现了资源新增、更改,但暂未支持R文件直接引用。
3. so文件热更新。
说到这里,是真感谢这世界上有数组这玩意。so文件的热更新,也是把补丁so库的路径插入到nativeLibraryDirectories数组的最前面。
二、Tinker
开源
Tinker已开源,Tencent/tinker[4],同时有详细的使用Wiki,Tinker使用Wiki[5]。
热更新过程
Tinker的整个热更新过程,可以理解成四个步骤。