摘抄自<<Android 进阶解密>>一书
热修复框架核心技术主要有三类:分别是代码修复、资源修复与动态链接库修复
很多热修复框架都参考了Instant Run
资源修复原理。
Instant Run
部署有三种方式,它会根据代码的情况来决定采用哪种部署方式:
activity
。修改一个现有方式中的代码采用hot swap
activity
需要重启。修改或者删除一个现有资源文时会采用warm swap
cold swap
的情况很多,比如:添加或者删除修改一个字段与方法,添加一个类等AssetManager
,通过反射调用addAssetPath
方法加载外部资源,这样新创建的AssetManager
就含有外部资源AssetManager
类型的mAssets
字段的引用全部替换为新创建的AssetManager
主要有三个方案:分别是底层方法替换、类加载方案与Instant Run
方案
Dex
分包方案DVM
的ByteCode
限制,DVM
指令集的方法调用指令invoke-kind
索引为16bits,最多能引用65536个方法**LinerAlloc
限制:在安装应用时,可能会提示INSTALL_FAILED_DEXOPT
,产生原因就是LinerAlloc
限制,DVM
中的LinerAlloc
是一个固定的缓存区,当方法数超出了缓存区大小时会报错。
为了解决65536与LinerAlloc
限制,产生dex
分包方案。该方案主要做是在打包时将应用代码分成多个Dex
,将应用启动时必须用到类与这些类的直接引用类放到主Dex
中,其他代码放到次Dex
中。当应用启动时先加载主Dex
,等应用启动完在动态加载次Dex
,从而缓解主dex
的65536与LinerAlloc
限制。Dex
方案主要有两种,分别是Google官方方案、Dex
自动拆包与动态加载方案。类加载方案需要重启App后让ClassLoader
重新加载新的类,为什么需要重启,因为类是无法卸载的,要想重新加载类就需要重启App,因此采用类加载方案的热修复框架无法及时生效。
虽然很多热修复框架采用了类加载方案,但是具体实现还是有区别的:
Element
数组的第一个元素优先加载。diff
,得到path.dex
,再将path.dex
与手机中的apk
的classes.dex
做合并,生成新的classes.dex
,然后在运行时通过反射将classes.dex
放在Element
数组第一个元素。dex
对应的Element
取出来,之后组成新的Element
数组,在运行时候通过反射用新的Element
数组替换现有的Element
数组。与类加载方案不同,底层替换方案不会再次加载新类,而是直接在Native
层修改原有类,由于在原有类进行修改限制会比较多,且不能增减原有类的方法和字段,如果我们增加了方法数,那么方法索引也会增加,这样访问方法时会无法通过索引找到正确的方法,同样的字段也是,方法反射我们可以调用java.lang.Class.getDeclaredMethod
。
在ART虚拟机中对应一个ArtMethod
指针,ArtMethod
结构体中包含了Java
方法所有信息,包括执行入口、访问权限、所属类与代码执行地址等
替换ArtMethod
结构体中的字段或者替换正给ArtMethod
结构体,这就是底层替换方案。AndFix采用替换ArtMethod
结构体中的字段,这样会有兼容问题,因为厂商可能会修改ArtMethod
结构体,导致方法替换失败,Sophix
采用替换整个ArtMethod
结构体,这样就不存在兼容问题。底层替换直接替换了方法,可以立即生效不需要重启。采用底层替换方案主要以阿里系为主。
在第一次构建APK
时,使用ASM
在每一个方法中注入类似如下的代码:
IncrementalChange loaclIncrementalChange = $change;
if(loaclIncrementalChange !=null) {
loaclIncrementalChange .access$dispatch("onCreate.(Landroid/os/Bundle;)V",new Object[]{this,paramBundle});
return;
}
当我们点击Instant Run
,如果方法没有变换,则$change
为null,就调用return不做处理,如果方法有变化,就生成替换类。假设Mainactivity
的onCreate
方法修改,就会生成 Mainactivity$overrid
,实现了IncrementalChange
接口,同时生成一个AppPatchedLoaderImpl
类,这个类的getPatchClasses
方法会返回被修改类的列表,根据列表会将Mainactivity
的$change
设置为 Mainactivity$overrid
,方法变化会执行Mainactivity$overrid
的access$dispatch
方法,在该方法中根据参数"onCreate.(Landroid/os/Bundle;)V
执行Mainactivity$overrid
的oncreate
方法,从而实现方法修改。
借鉴该方案的热修复框架有美团的Robust与美丽说蘑菇街的Aceso。
主要指so库。热修复框架主要是更新so,基础原理就是加载so.
加载so主要用到System
类的load
和loadLibrary
方法
System
类的load
方法传入参数是so在磁盘的完整路径,用于加载指定路径的so。System
类的loadLibrary
方法传入so的名称,用于加载App安装后自动从apk包中复制到/data/data/packagename/lib下的so.
so修复一种方案,就是将so补丁插入到NativeLibraryElement
数组的前部,让so补丁的路径先返回,并调用Runtime
的doLoad
方法中会调用native
的nativeload
。
在Runtime nativeload
函数中调用JVM_NativeLoad
函数。JavaVMExt
类型指针代表一个虚拟机实例,紧接着调用JavaVMExt
的LoadNativeLibrary
函数加载so.
LoadNativeLibrary
函数总结:
so
是否加载过,两次ClassLoader
是否是同一个,避免so重复加载SharedLibrary
,如果传入path
对应的library
为空指针,就将新创建SharedLibrary
赋值给library
,并将library
存储到libraries
中JNI_OnLoad
函数指针,根据不同情况设置was_successful
值,最终返回was_successful
。so修复主要有两种方案:
NativeLibraryElement
数组的前部,让so补丁的路径先返回和加载;System
的load
方法来接管so的加载入口;so文件object的缩写,见名思义就是共享的对象,机器可以直接运行的二进制代码。大到操作系统,小到一个专用软件,都离不开so。
so主要存在于Unix和Linux系统中。so库的名称和文件名so库的名称可任意,如daking
。so库的文件名必须以lib
开头。如libdaking.so
,其中lib是必要前缀,daking
才是这个库的名称。Android
中的soso是与平台相关的二进制机器码,与ABI(Application Binary Interface)
相对应,一个ABI表示相应的CPU的指令集与内存页管理,也对应于不同的C运行环境,所以so是有不同的系统版本的。
随着Android系统的快速发展,搭载Android的硬件平台也早已多样化了(对比WinTel联盟,直到2012年才新发展了Windows RT来适配ARM平台,2015年的Win10才进入 Raspberry Pi 2这类基于ARM的新型设备中),
现在已经运行在7个ABI:armeabi,armeabi-v7a (armeabi-v7a-hard),arm64-v8a,x86,x86_64,mips 和 mips64
。为什么使用上面主要从软件开发的角度说明了为什么设计so以及开发者为什么使用so,由于Android基于Linux Kernl
的,也继承了Linux中所有so相关的设计。
除了系统方面的原因,Android
开发者还要知道以下几点:so机制让开发者最大化利用已有的C和C++
代码,达到重用的效果,利用软件世界积累了几十年的优秀代码so是二进制,没有解释编译的开消,用so实现的功能比纯java
实现的功能要快so内存分配不受Dalivik/ART
的单个应用限制,减少OOM
应用程序定义的二进制文件尤其指so文件,如何运行在相应的系统平台,从使用的指令集,内存对齐到可用的系统函数库中,在Android 系统上,每一个CPU架构对应一个ABI:armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips、mips64
不同的 Android 手机使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口,即 ABI。ABI 可以非常精确地定义应用的机器代码在运行时如何与系统交互。您必须为应用要使用的每个 CPU 架构指定 ABI。典型的 ABI 包含以下信息:机器代码应使用的 CPU 指令集。运行时内存存储和加载的字节顺序。可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。在代码与系统之间传递数据的各种规范。这些规范包括对齐限制,以及系统调用函数时如何使用堆栈和寄存器。运行时可用于机器代码的函数符号列表 - 通常来自非常具体的库集。
什么是ABI:
Application Binary Interface
的缩写。library
或操作系统。.so
)如何运行在相应的系统平台上等细节。ABI:armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips、mips64。