沃梦达 / IT编程 / 移动开发 / 正文

深入理解Android热修复技术原理之so库热修复技术

通常情况下,大多数人希望android下热补丁方案能够做到补丁的全方位修复,包括类修复/资源修复/so库的修复。 这里主要介绍热补丁之so库修复思路

一、SO库加载原理

Java Api 提供以下两个接口加载一个 so 库

  • System. loadLibrary (String libName):传进去的参数:so库名称, 表示的so 库文件,位于apk压缩文件中的 libs 目录,最后复制到 apk安装目录下。
  • System, load (String pathName):传进去的参数: so库在磁盘中的完整 路径。加载一个自定义外部 so库文件。

上述两种方式加载一个 so 库,实际上最后都调用 nativeLoad 这个 native方法去加载 so库,这个方法的 fileName:so 库在磁盘中的完整路径名。

代码+图文的方式简述 so 库加载原理,下面的代码示例,stringFromJNI -> Java_com_taobao_jni_MainActivity_stringFromJNI 静态注册的 native 方 法,test->test 动态注册的 native 方法。

二、SO库热部署实时生效可行性分析

2.1、动态注册 native 方法实时生效

前面我们分析过 so 库的加载原理,我们知道动态注册的 native方法调用一次 JNI_OnLoad 方法都会重新完成一次映射,所以我们是否只要先加载原来的 so库, 然后再加载补丁 so 库,就能完成Java层 native 方法到 native 层 patch后的新方法映射,这样就完成动态注册native 方法的 patch 实时修复。一张图说明

所以为了解决 Dalvik 下面的这个问题,那么如果尝试对补丁 so进行改名,比如 此处补丁so 库的完整路径修改之后变成 /data/data/com.taobao.jni/files/ libnative-lib-123333.so,后面一串数字是当前时间戳,确保这个 bname是全局唯一的,按照上面的分析,在solist 中查找的 key已经是唯一的,所以此时可以做到Dalvik 下面动态注册的 native 方法的实时生效。

2.2、静态注册 native 方法实时生效

上面通过尝试对补丁 so库进行重命名为全局唯一的名称可以确保第二次加载补丁so 库可以做到 Dalvik 下和 Art下动态注册方法的实时生效,但要做到静态注册 native 方法的实时生效还需要更多工作。

前面我们说过静态注册 native 方法的映射是在 native方法第一次执行的时候就完成了映射,所以如果native方法在加载补丁 so 库之前已经执行过了,那么是否这种时候这个静态注册的 native 方法一定得不到修复?幸运的是,系统 JNI API提供 了解注册的接口。

2.3、SO实时生效方案总结

基于上面的分析,so库的实时生效必须满足以下几点:

  • so库为了兼容Dalvik虚拟机下动态注册native方法的实时生效,必须对so 文件进行改名。
  • 针对so库静态注册native方法的实时生效,首先需要解注册静态注册的 native方法,这个也是难点,因为我们很难知道so库中哪几个静态注册的 native方法发生了变更。假设就算我们知道如果静态注册的native方法需要解注册,重新load补丁 so库也有可能被修复也有可能不被修复。
  • 上面对补丁 so进行了第二次加载,那么肯定是多消耗了一次本地内存,如果 补丁 so库够大,补丁 so够多,那么JNI层的OOM也不是没可能
  • 另外一方面补丁 so如果新增了一个动态注册的方法而dex中没有相应方法, 直接去加载这个补丁 so文件会报NoSuchMethodError异常,具体逻辑在 dvmRegisterJNIMethod中。我们知道如果dex如果新增了—native 方法,那么走不了热部署只能冷启动重启生效,所以此时补丁so就不能第二 次load 了。这种情况下so库的修复严重依赖于dex的修复方案。

可以看到 so库实时生效方案,对于静态注册的native方法有一定的局限性, 不能满足一般的通用性,所以最后我们放弃了 so库的实时生效需求,转而求次实现 so库修复的冷部署重启生效方案。

三、SO库冷部署重启生效实现方案

为了更好的兼容通用性,我们尝试通过冷部署重启生效的角度分析下补丁 so库的修复方案。

3.1、接口调用替换方案

sdk提供接口替换System默认加载so库接口


SOPatchManager.loadLibrary(String libName) -> System.loadLibrary(String libName) 

SOPatchManager.loadLibrary接口加载 so库的时候优先尝试去加载sdk 指定目录下的补丁so,加载策略如下:

如果存在则加载补丁 so库而不会去加载安装apk安装目录下的so库

如果不存在补丁so,那么调用System.loadLibrary去加载安装apk目录下的 so库。

3.2、反射注入方案

前面介绍过 System. loadLibrary ( "native-lib"); 加载 so库的原理,其实native-lib 这个 so 库最终传给 native 方法执行的参数是 so库在磁盘中的完整路径,比如:/data/app-lib/com.taobao.jni-2/libnative-lib.so, so库会在 DexPathList.nativeLibraryDirectories/nativeLibraryPathElements 变量所表示的目录下去遍历搜索。

sdk<23 DexPathList.findLibrary 实现如下

四、如何正确复制补丁 SO库

上面提到的一个问题,这里不打算详细介绍。有需要的参考文档:Android动态 链接库加载原理及HotFix方案介绍,这篇文档有些观点不尽正确,但是我也能知道虚拟机究竟选择哪个abis目录作为参数构建PathClassLoader对象,一张图简单了解下原理:

五、本章小结

对于 so库的修复方案目前更多采取的是接口调用替换方式,需要强制侵入用户 接口调用。目前我们的so文件修复方案采取的是反射注入的方案,重启生效。具有更好的普遍性。如果有so文件修复实时生效的需求,也是可以做到的,只是有些限制情况。

以上就是深入理解Android热修复技术原理之so库热修复技术的详细内容,更多关于Android so库热修复的资料请关注编程学习网其它相关文章!

本文标题为:深入理解Android热修复技术原理之so库热修复技术