atlas-docs/update/principle.md
#技术原理
普通Apk的更新的过程为构建->安装->生效,与之相对应,动态部署也可以分为三个过程:
下面对每个过程展开讲解下,以便更好理解整个动态部署的机制
动态部署构建的产物为tpatch为后缀的一个压缩文件--文件名为
例如手淘6.2.0部署到6.2.1的动态部署的tpatch文件为: [email protected]
patch文件解压后一级目录如下图:
注意点:主Apk的差量与bundle的差量目前采取不同的策略,主Apk永远是与基线版本做差量;而bundle是更上一个版本做差量:比如从6.1动态部署到6.2再动态部署到6.3,则6.3中主apk的差量是6.3主apk的集成内容和基线(6.1)主apk中的内容diff出差量;6.3中bundle的差量则是与上一个版本进行diff,如果是6.2的版本去请求动态部署的tpatch,则tpatch中的bundle的差量是6.3与6.2的bundle进行diff所得,如果是6.1版本去请求6.3的动态部署的tpatch,则bundle的差量是6.3与6.1的bundle进行diff所得
与tpatch文件相对于的,接口会返回该次动态部署的修改内容,数据结构如下:
数据中可以看出bundle动态部署过程bundle的依赖关系允许发生变更,同时动态部署也有一定的限制,这个会在后面单独进行说明
merge过程在下载到tpatch且校验通过后在线程中进行,由之前Atlas容器安装目录介绍可知,假设当前bundle的版本在文件系统上为Version.1,则merge成功后新的bundle会安装在Version.2目录下,并在后续新的动态部署中版本号逐步往后追加,同时鉴于空间占用的考虑,目前的策略是会保留上一个版本(鉴于回滚的考虑);
与bundle以包名为文件夹存放类似,主Apk更新存放的文件夹以com.taobao.maindex为名字,里面的版本管理与bundle一致,不同点是com.taobao.maindex只在发生过动态部署以后才会有,Version.1意味着已经发生一次动态部署;bundle通常情况下动态部署从Version.2开始(未安装过的bundle除外)
动态部署成功后,会在下次进程重启后去resolve新的bundle,resolve的策略是按版本号从高到低回溯,选择最高可用的版本使用。
下面展开介绍下在新bundle(假设主Apk当做是一个特殊的bundle)安装成功前merge的过程中bundle和主Apk在merge过程中的差异点:
bundle的merge主要以下几个过程(如下图)
merge完以后接下去安装就是容器的逻辑了,如果没有安装过则在bundle目录下生成Version.1目录进行安装,如果安装过则生成新的版本目录
主Apk的Merge在dalvik和art上使用的机制有所不同,dalvik设备上没有任何merge过程,直接把libcom_taobao_maindex.so以bundle的形式安装到com.taobao.maindex目录下 Art设备上我们会根据source的apk(主apk的merge永远是基于基线版本)把classes.dex 提取出来以多dex的方式追加到libcom_taobao_maindex.so中,如果本身是多dex机制的,那么会将多个子dex全部追加进去,patch里面的classes.dex保持不变,source里面的dex的序列号往后偏移一位(classes.dex->classes2.dex,classes2.dex->classes3.dex),如下图所示:
Merge安装成功后,会将bundleinfo的信息更新到baselineinfo的目录中,并在下次启动的最早时间替换掉原有的baselineinfo信息;里面的内容记录了更新的bundle的name,version以及部署成功以后的版本号等信息。
由之前的容器的运行机制可以得知,bundle动态部署后对容器来说没有任何变化,还是按需的load bundle,只是在需要去load前版本选择上会使用最高的可用版本
了解主Apk的加载策略之前,先分别介绍下主Apk动态部署得以成功的技术基础再阐述生效的方式:
这里粗略介绍下普通apk(也就是主Apk)class,so库加载入口--PathClassLoader 前面容器的技术原理里面介绍了PathClassLoader的父子关系,PathClassLoader自身负责主Apk的类和c库的查找路口;其parent BootClassloader负责framework sdk的内容的查找。
PathClassLoader本身也是一个class,继承了BaseDexClassLoader(同DexClassLoader),里面查找过程在DexPathList里面实现(如下图)
DexPathList最终通过DexFile去loadClass,DexPathList可以理解为持有者DexFile以及nativeLibrary目录,再查找的时候遍历这些对象,直到找到需要的类或者c库,那么动态部署的方式就是把新修改的内容添加到这些对象的最前面,从而使得查找的过程中新修改的内容能够提前找到从而替换原有的(如下图)
这里有两个注意点提及一下以加深理解:
主Apk为了支持在Resource能够动态部署实际上同PREVERIFY的机制类似,在基线包构建的时候就已经做了处理:基于Atlas打出来的apk的资源列表中有如下内容:
我们取一个动态部署的tpatch包,能够看到如下内容:
这些是基于我们atlas-aapt的修改,dalvik上我们是基于res的overlay机制去修改资源,这个机制并不支持新增资源,在overlay的包里面如果读到了一个资源,dalvik系统会去校验该资源ID在base中值,如果不存在则抛错,所以动态部署为了利用这一特性同时支持新增资源的需求,在打基线包的时候就在每个不同的type里面预留了128个资源ID供后续动态部署使用;同时打动态部署的patch时会以之前的ID分配的内容作为输入,保证已有的资源分配到的ID保持不变,同时如果资源没有发生变更,则剔除该资源,所以aapt dump patch的时候我们看到的INVALID TYPE 的资源段没有发生变更的资源,如果有发生了变更,且之前有这个资源,就会在原有的资源ID分配过去,同时新的资源文件打入patch包或者新的资源文本写入arsc,如果是新增的资源,则使用预留的资源段进行分配。整个替换过程如下图所示:
在打包对资源做了一系列处理以后,运行期资源的加载只是在启动加载资源时根据系统读取资源的先后书序把patch包插入到AssetsManager的合适的位置中,assets由于没有资源ID,不需要预留,插入AssetsManager的方式类似res的处理,这里不再展开