SillyBoy

Project Url: TestPlanB/SillyBoy
Introduction: android 动态加载 so 库实现
More: Author   ReportBugs   
Tags:
Off-

so dynamically loading (dy so) android 动态加载 so 库实现 你所不知道的“船新”版本

关于 demo

本项目是动态 so 加载的核心流程,所以并不直接演示 so 的下载过程,需要 demo 使用者将 test_so_package 的三个 demo so 库放到指定的演示位置(模拟 so 已经下载好了,可对接自己使用的下载方式),本例子是放在目录

data/data/com.example.sillyboy/files/dynamic_so

image

设计原理

文档请看这里:https://juejin.cn/post/7107958280097366030

使用说明

声明

注意:由于这是一个基础库,如果需要在正式项目中使用,还是需要继续封装或者优化的,这里只是动态加载 so 的实现,在实战中应当配合 so 版本管理,下载器等实际使用

使用步骤

  1. 如何删除本地的 so 库【可选】
    方式 1
    packagingOptions 下增加
    exclude 'lib/arm64-v8a/xxx.so'
    exclude 'lib/armeabi-v7a/xxx.so'
    
方式 2
复制以下 task 到 app 的 build.gradle

如果需要删除 so 的过程中进行一些列的定制化操作,可参考如下 task,见 app 目录例子
ext {
    deleteSoName = ["libnativecpptwo.so","libnativecpp.so"]
}
// 这个是初始化 -配置 -执行阶段中,配置阶段执行的任务之一,完成 afterEvaluate 就可以得到所有的 tasks,从而可以在里面插入我们定制化的数据
task(dynamicSo) {
}.doLast {
    println("dynamicSo insert!!!! ")
    //projectDir 在哪个 project 下面,projectDir 就是哪个路径
    print(getRootProject().findAll())

    def file = new File("${projectDir}/build/intermediates/merged_native_libs/debug/out/lib")
    //默认删除所有的 so 库
    if (file.exists()) {
        file.listFiles().each {
            if (it.isDirectory()) {
                it.listFiles().each {
                    target ->
                        print("file ${target.name}")
                        def compareName = target.name
                        deleteSoName.each {
                            if (compareName.contains(it)) {
                                target.delete()
                            }
                        }
                }
            }
        }
    } else {
        print("nil")
    }
}
afterEvaluate {
    print("dynamicSo task start")
    def customer = tasks.findByName("dynamicSo")
    def merge = tasks.findByName("mergeDebugNativeLibs")
    def strip = tasks.findByName("stripDebugDebugSymbols")
    if (merge != null || strip != null) {
        customer.mustRunAfter(merge)
        strip.dependsOn(customer)
    }

}
  1. 初始化

通过 initDynamicSoConfig 方法初始化,第一个参数是 context,第二个参数是 path,即下载完的 so 的 path,第三个参数是一个回调,在调用动态 so 加载的时候会先回调,如果返回值为 true,才会真正进入到 so 的加载逻辑,提供给使用者版本校验等一些列活动

    // 在合适的时候将自定义路径插入 so 检索路径 需要使用者自己负责在这个路径上有写入权限
        DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> {
            // 处理一些自定义逻辑
            return true;
        });
  1. 调用 so 加载【有两种使用姿势】

3.1 手动加载

在使用到某个 so 方法前,需要调用 loadSoDynamically(代替 System.loadLibrary 方法),参数是 so 的名称或者一个 File,该 File 位于初始化时传入的 path 之内即可

DynamicSoLauncher.INSTANCE.loadSoDynamically(file)

3.2 注解加载

在需要采取动态 so 加载的类上,添加@DynamicLoad 注解即可,内部会采用字节码插桩的方式,会把类中所有的 System.loadLibrary 替换为 DynamicSoLauncher 内部的 so 加载

//@DynamicLoad
public class MainActivity extends AppCompatActivity {

注意,执行这个步骤需要发布当前的 lib_sillyplugin 插件 image 之后在需要的工程 build.gradle 引入即可,跟一般的插件引入方式一样

apply plugin: 'com.plugins.core'

ps:这里其实已经可以实现了无痕替代,只是考虑到大多数可能只动态加载少数的 so,才提供出来注解的方式去限定范围。

项目层级介绍

  • app 下是使用例子
  • lib_sillyboy 是 dyso 的封装实现
  • lib_sillyplugin 是 dyso 的插件,用于替换代码的 System.loadLibrary,默认关闭了,可看代码注释打开

环境准备

建议直接用最新的稳定版本 Android Studio 打开工程。目前项目已适配Android Studio Arctic Fox | 2020.3.1

后续 todo

  • maven 发布
  • 贡献者名单
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools