Android解耦式so库加载方案

背景说明

在业务开发过程当中常常会进入一些三方sdk,这些三方的sdk引入so库,有些so库文件还比较大,这时候咱们就须要考虑so库从网络获取异步加载,减小发布包的体积java

image.png

传统方案

关于so异步加载方案,网上的资料随便搜下大把,核心思想
so库文件放到网络->下载到本地沙盒->经过System.load载入
看起来挺简单的 然而挺多资料没提到的是android

so库存在依赖关系

好比当你把libunity.so下载下来经过
System.load("/data/data/com.example.soload/files/libunity.so")
加载的时候会获得以下异常git

java.lang.UnsatisfiedLinkError: dlopen failed: library “libmain.so” not found
at java.lang.Runtime.loadLibrary0(Runtime.java:1071)
at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
at java.lang.System.loadLibrary(System.java:1667)

缘由分析github

image.png

解决方案编程

载入libunity.so以前须要先载入libmain.sojson

扩展思考api

咱们写代码的时候怎么控制加载顺序呢
https://github.com/facebook/S...
https://github.com/KeepSafe/R...
以上2个开源库 都有处理相关逻辑
核心思想就是解析so库ELF格式,分析依赖并递归,直到依赖库都加载完成再载入自身
这里不扩展开来 有兴趣的伙伴能够看源码研究下网络

text relocations问题

当你把liblbs.so下载下来经过app

System.load("/data/data/com.example.soload/files/liblbs.so")的时候又出现意外了
java.lang.UnsatisfiedLinkError: dlopen failed: “/data/data/com.example.soload/files/liblbs.so” has text relocations ( https://android.googlesource....
at java.lang.Runtime.load0(Runtime.java:938)
at java.lang.System.load(System.java:1631)==

缘由分析:异步

https://blog.csdn.net/chjqxxx...

扩展思考:

咱们能够经过命令进行自查

image.png

有源码的能够从新编译,没有源码的只能经过下降targetSdkVersion处理(然而也是治标不治本)

进阶方案

除了System.load方案 还有没有其余方案呢
答案固然是确定的
https://github.com/Tencent/ti...
核心思路:经过反射把自定义的native库path插入nativeLibraryDirect ories最前面,即便安装包libs目录里面有同名的so,也优先加载指定路径的外部so
这里截取tinker部分代码
com.tencent.tinker.lib.library.TinkerLoadLibrary.java

image.png

image.png

这个方案是否是看起来更简单了,因为这个方案是很是规手段,存在兼容风险, 咱们须要验证该方案的兼容性,写个helloword的so,经过该方案加载,提早找个版本线上带上去,进行埋点统计
截至目前最新版上线2天 统计到数据以下

image.png

其中失败的1例是上线前自测故意让校验失败产生的
也就是说截至目前能够认为改方案是靠谱的
固然了若是后续有新的Android系统版本更新
咱们还须要关注新版本的兼容状况

项目应用

上面铺垫了那么多,如今进入正题
技术方案是一回事,落实到实际项目是另外一回事
如今开始咱们思考下以下几个问题

  1. 项目中引用的so库都是那些功能再用什么时候载入
  2. 三方jar(aar)引入的so库加载时机不受咱们代码逻辑控制
  3. 使用的so库那些适合作异步加载
  4. so库文件没有下载完成以前 用户用相关功能如何处理
  5. 版本迭代so文件升级如何处理
  6. 打包阶段如何把so文件进行剔除

解决问题一、二、3

咱们能够经过切面编程的思路解决,这里用到了开源库https://github.com/HujiangTec...
因为so库的加载都是经过调用系统函数System.loadLibrary进行,
那么咱们全局拦截该函数的调用并打印调用链,
运行app,配合日志,就能分析so库的具体使用状况

image.png
image.png

这里咱们须要根据本身业务场景分析,好比app进入到首页依赖的库不建议进行异步加载,须要异步加载的库最好是完成一个功能(咱们须要按照经验进行逻辑分组 参考文章开头的图)

解决问题四、5

Android是依据Activity做为活动单位,启动Activity是经过startActivity函数调用进行的,那么咱们就能够拦截织入本身的逻辑
大体思路以下
image.png

对应实现部分代码截图
image.png

image.png

image.png
image.png

经过规则配置化+字节码拦截的逻辑 对原有业务无侵入便可实现动态加载
研发关注正常的业务逻辑 不须要针对性编写代码
后续引入新的so库,修改配置文件便可
在实现的过程当中有些细节是须要考虑的
好比校验下载文件的完整性,下载要不要支持断点续传,so文件须要更新如何处理等

解决问题6

经过上面的介绍,咱们实现了so文件的异步加载
可是打包的时候 咱们如何剔除so 减小最终发布包的大小
能够在build.gradle进行配置
image.png

可是上面提到了咱们是经过json配置的规则
这样再重复配置一遍
并且还有可能2处配置不一致
并且咱们的大原则是解耦
那么咱们就继续hook打包流程
image.png

image.png

汇总小结

经过上面的介绍大体说明了原理

有兴趣的伙伴能够评论区留言,提出本身的想法

目前还在灰度验证阶段,等线上验证稳定后在评论区放出相关代码

so异步加载方案大同小异,该方案跟其余方案相比

我的感受特点就是侵入性低

接入只须要3个步骤
image.png
image.png
image.png

未完待续

目前该方案还存在几个点有待优化

====================

问题1

目前用的是进阶方案实现的so库加载 比较理想的方式是传统方案和进阶方案都应该支持 一开始其实我想用传统方案去实现 可是该方案在unity那块有问题(unity相关so的加载逻辑有点特殊 存在经过jni直接加载so的状况 因为没有源码 内部逻辑不太清楚 ) 后续还须要研究完善 毕竟官方System.load是官方提供的api 更可靠

问题2

进度加载的dialog是依附于调用startActivity的窗口
若是startActivuty以后里面 立马调用finish会致使dialog泄露
这个是须要注意的一个点 也就意味着对原有逻辑存在必定依赖
这个能够经过启动一个FLAG_ACTIVITY_NEW_TASK类型的activity
代理解决 可是感受这样有点重 目前还有点纠结

问题3

目前文件只是放到了阿里云,用户的网络环境复杂多变,须要考虑支持腾讯云,七牛云等更多的服务商。

问题4

上传到oss的动做目前是动手完成的,须要作成脚本。

相关文章
相关标签/搜索