Android包体积优化!so库动态加载!

384 阅读2分钟

1. 创建Task删除so库

  1. 创建Task,遍历build/intermediates/merged_native_lib 目录下的so文件
  2. 将自定义Task插入到mergeDebugNativeLibs之后,stripDebugDebugSymbols
task(dynamicSo) {
    println("register dynamicSo")
}.doLast {
    println("dynamicSo insert!!!! ")
    //projectDir 在哪个project下面,projectDir就是哪个路径
    print(getRootProject().findAll())

    def file = new File("${projectDir}/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a")
    // 删除指定的so文件
    if (file.exists()) {
        file.listFiles().each {
            if (it.isDirectory()) {
                it.listFiles().each {
                    target ->
                        println("=====> so file name:  ${target.name} filesize:${target.size()/1000}kb")
                        if(target.name == "libmvvmdemo.so"){
                            target.delete()
                        }
                }
            }
        }
    } else {
        print("nil")
    }
}
project.afterEvaluate {
    println("dynamicSo task start   ")
    def customer = tasks.findByName("dynamicSo")
    def merge = tasks.findByName("mergeDebugNativeLibs")
    def strip = tasks.findByName("stripDebugDebugSymbols")
    if (merge != null && strip != null) {
        println("xianyu ===>   dynamicSo task config")
        customer.mustRunAfter(merge)
        strip.dependsOn(customer)
    }else{
        println("xianyu ===>   task not find")
    }
}

2. 修改system.loadLibrary默认加载路径

  1. 参考thinker方案,反射获取classLoader的pathList,再获取nativeLibraryDirectories,这是一个List保存了查找so库的目录,在这个List首部添加自定义文件路径。
  2. 不同android版本的兼容方案不一样,反射的方法可能有调整,需要做好兼容性处理。

1699097742157.png 如上是pathList的对象内容。

// 参考thinker思路,在获取lib的路径列表前添加自定义的路径
final Field pathListField = loader.getClass().getSuperclass().getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathList = pathListField.get(loader);
Field nativeLibraryDirectories = pathList.getClass().getDeclaredField("nativeLibraryDirectories");
nativeLibraryDirectories.setAccessible(true);
ArrayList list = (ArrayList) nativeLibraryDirectories.get(pathList);
File file = getExternalFilesDir("soLib");//自定义的目录
list.add(0,file);//添加自定义的目录到列表头部

3. so库版本控制

版本控制需要考虑的问题:

  1. 不同手机的abi架构下发的so版本要和架构版本保持一致
  2. 不同apk版本下发的so版本要保持一致
  3. apk版本升级后要删除旧so,使用新的so

需要做的是每次release打包后将so库上传服务端,服务端管理每次版本发布时的so库存储,包括so对应的abi架构版本,apk版本,接口中通过参数来获取指定的so,客户端依据apk版本号为目录存储so,并在每次存储同时删除旧的so。

4. 多级依赖so库加载

Android Native 用来链接 so 库的 Linker.cpp dlopen 函数 的具体实现变化比较大(主要是引入了 Namespace 机制):以往的实现里,Linker 会在 ClassLoder 实例的 nativeLibraryDirectories 里的所有路径查找相应的 so 文件;更新之后,Linker 里检索的路径在创建 ClassLoader 实例后就被系统通过 Namespace 机制绑定了,当我们注入新的路径之后,虽然 ClassLoader 里的路径增加了,但是 Linker 里 Namespace 已经绑定的路径集合并没有同步更新,所以出现了 libxxx.so 文件能找到,而 liblog.so 找不到的情况。

至于 Namespace 机制的工作原理了,可以简单认为是一个以 ClassLoader 实例 HashCode 为 Key 的 Map,Native 层通过 ClassLoader 实例获取 Map 里存放的 Value(也就是 so 文件路径集合)。

解决方案:

  1. 自定义 System#load,加载 libxxx.so 前,先解析 libxxx.so 的依赖信息,再递归加载其依赖的 so 文件(推荐参考开源方案 github.com/facebook/So… )。
  2. 类似 Tinker,在合适的时机替换 ClassLoader 实例。

5. so加载失败的措施

如果网络无法获取到so库,则选择降级措施。

参考:

  1. juejin.cn/post/710795…
  2. bugly cloud.tencent.com/developer/a…