阿里电子书《深刻探索Android热修复技术原理》整理的笔记java
1.热修复技术介绍
- 代码修复两大主要方案
- 底层替换方案:限制较多,但时效性好,当即见效
- 类加载方案时效性差,须要从新冷启动才能见效,但限制少
- 代码修复底层替换方案
- 底层替换方案是在已经加载了的类中直接替换掉原有方法.
- 不能对原有类进行方法和字段的增减,由于这样将破坏原有类的结构.
- 方法增减将致使这个类及整个Dex方法数的变化,伴随着方法索引的变化,这样访问方法时就没法正常的索引到正确的方法;
- 字段增长或减小,全部字段的索引都会发生变化;
- 传统底层替换方案,都是直接依赖修改虚拟机方法实体中的具体字段.依据的是Android开源版本.若是厂商修改了虚拟机方法实体,替换机制就可能出问题;
- 代码修复类加载方案
- 类加载方案的原理是在app从新启动后让Classloader去加载新的类.
- 在app运行到一半的时候,全部须要发生变动的类都已经被加载过,Android没法对一个类进行卸载.若是不重启,原来的类还在虚拟机中,就没法加载新类.
- 只有在下次重启时候,在尚未走到业务逻辑前抢先加载补丁中的新类,后续访问才是新的类.
- dex比较的最佳粒度,应该是在类的粒度
- Sophix采用的也是全量合成dex的技术.能够看作是dex文件级别的类插装方案.Sophix对旧包与补丁包中classes.dex的顺序进行了打破与重组,使得系统能够天然地识别到这个顺序,以实现类覆盖的目的
- 资源修复
- 市面上不少资源热修复方案都采用了Instant Run的实现
- Instant Run中资源修复步骤:
- 构造一个新的AssetManager,经过反射调用addAssetPath,把这个完整的新资源包加到AssetManager中.这样就获得一个含有全部新资源的AssetManager.
- 找到全部以前引用到AssetManager的地方,经过反射,将引用出替换为AssetManager.
- Sophix资源热修复没有采用Instant Run的技术,而是构造了一个package id 为 0x66 的资源包,这个包里只包含改变了的资源项,而后直接在原有AssetManager中addAssetPath这个包便可.无需变动AssetManager对象的引用.
- Sophix构造的补丁包的 package id 为0x66,不与已经加载的 0x7f冲突,因此直接加入到已有的AssetManager中能够直接使用.
- Sophix资源补丁包中,只包含新增资源,以及原有内容发生了改变的资源.
- SO库修复:本质上是对native方法的修复和替换
Sophix采用的是相似类修复反射注入方式,把补丁so库的路径插入到nativeLibraryDirectories数组的最前面, 这样加载so库的时候就是补丁so库而不是原来的so库
android
2.代码热修复技术
- 底层热替换原理
- Android的java运行环境,在4.4如下用的是dalvik虚拟机,4.4以上是art虚拟机.
- 在各类Android热修复方案中,Andfix即时生效.Andfix采用的方法是,在已经加载了的类中直接在native层替换掉全部方法,是在原来的类的基础上进行修改的.
- 以art,Android6.0为例,每个Java方法在art中都对应着一个ArtMethod对象,ArtMethod记录了这个Java方法的全部信息,包括所属类,访问权限,代码执行地址等.
- Andfix会将一个旧Java方法对应的ArtMethod实例中的全部字段值替换为新方法的值,这样全部执行到旧方法的地方,都会取得新方法的执行入口,所属class,方法索引,所属dex.像调用旧方法同样执行了新方法的逻辑.
- 底层热替换兼容性根源
- 市面上几乎全部的native替换替换,都是写死了ArtMethod结构体
- 写死的ArtMethod结构和Android开源版本中彻底一致,但各个厂家能够对ArtMethod进行修改,那么在修改过的设备上,市面上的native替换方案(将方案中写死了的ArtMethod关联的新方法的属性赋值到设备中的ArtMethod实例)就会出现问题,由于两个ArtMethod中相同字段的索引不一样
- 突破底层热替换兼容问题
- native层面替换,实质是替换ArtMethod实例的全部字段.
- 只要把ArtMethod做为总体进行替换,便可解决兼容问题.
- ArtMethod实例之间,是紧密线形排列的,因此一个ArtMethod的大小,就是其相邻的两个方法对应的ArtMethod实例的起始地址的差值.
- 包括Sophix在内的底层替换方案,都只能支持方法的替换,不支持补丁类中增减方法和字段
- 补丁类中增减方法,会致使这个类及整个dex方法数的变化,方法数的变化伴随方法索引的变化,这样在调用方法时没法正常的所引导正确的方法.
- 补丁类中增减字段,也会致使全部字段的索引起生变化.
- 你说不知的Java
- 内部类在编译期会被编译为根外部类同样的顶级类;
- 非静态内部类持有外部类的引用,静态内部类不持有外部类的引用.因此android性能优化中建议自定义Handler的实现尽可能使用静态内部类,防止外部类Activity类不能被回收致使内存泄漏. 自定义Handler使用静态内部类避免内存泄漏
- 内部类和外部类之间,访问彼此的private属性及方法,编译期间:
- 外部类访问内部类的私有成员及方法,编译期间自动为内部类生成access&**方法
- 内部类访问外部类的private属性及方法,编译期间也会生成access&**方法提供给内部类
- 同一个类及其内部类,若是老代码没有访问对方的私有属性/方法,新代码有访问对方的私有属性/方法,若是不能避免生成access&的生成,就会致使方法数的变化,致使热修复失败.避免生成access&方法须要:
- 外部类全部的属性及方法改成public或protected;
- 内部类全部的属性及方法改成public或protected;
- 在编译期间,根据匿名内部类在外部类中出现的前后顺序,匿名内部类的名称依次累加:外部类名称&数字
- 外部类名称是OutClass,其中对应的内部类在编译期间的名称依次是:OutClass&1,OutClass&2,-----
- 为了实现热修复,外部类应该极力避免新增及减小匿名内部类;
- 除非是新增匿名内部类到外部类的尾部,不会影响以前添加过的匿名内部类的名称,否则会致使热修复失效;
- Java原始类型:double、float、byte、short、int、long、char、boolean
- 若是一个常量的类型是Java原始类型,或String,为了优化性能,应该用static final修饰;
- static final 引用类型,没有任何优化效果.
- 由于 static final 修饰的原始类型及String常量,是在所属类的初始化时赋值,直接在内存中读取;
- 而 static final 引用类型常量,初始化是在clinit方法中,本质上是经过sget-object指令去获取值,从虚拟机运行性能上无任何优化;
- 市面上的冷启动类加载实现方案
- 1:采用dex插桩的方式,单独放一个帮助类在独立的dex中让其余类调用.最后加载补丁dex获得dexFile对象,将dexFile做为参数构建一个Element对象插入到dexElements数组最前面
- 2:提供dex差量包,总体替换dex的方案:差量patch.dex和应用的classes.dex合成完整dex.加载完整dex获得dexFile对象,做为参数构建一个Element对象,而后总体替换掉旧的dexElements数组
- 1的缺点是:Dalvik下影响类加载性能,Art下类地址写死,致使必须包含父类及引用,补丁包很大
- 2的缺点是:dex的合并,内存消耗在 vm heap 上,容易致使OOM,合并失败
- Sophix采用的代码修复冷启动方案
- Dalvik下使用全量Dex方案;
- Art下本质上虚拟机已经支持多dex的加载,只要把补丁dex做为主dex(classes)便可
- Sophix在Dalvik下全量Dex方案思路
- 基线包dex里面,去掉补丁包dex中包含的class;这样补丁+去除了补丁中包含类的基线包,就等于新app中全部类;
- Sophix并无把某个class的全部信息从基线dex中移除,仅仅移除了定义的入口,让解析基线dex时候找不到这个class的定义便可;这样不会致使dex的各个部分都发生变化,防止大量调整offset.
- 只要把全部的dex都load进去,单个dex中不存在的类就能够在运行期间在其余dex中找到.补丁中的类和基线中的类能够互相访问到
3.资源热修复技术
Android资源的热修复,就是在app不从新安装的状况下,利用下发的补丁包直接更新app中的资源
Sophix的资源热修复方案数组
1:构造一个 package id 为0x66的资源包,这个包里只含有变动的资源,以及新增资源;
2:而后直接在原有AssetManager上调用addAssetPath加入这个资源包便可;
由于咱们补丁包的id和已经加载的0x7f冲突,因此直接加入原有AssetManager便可直接使用
复制代码
4.SO库热修复技术
无性能优化