Android开发中libs目录下so文件的正确放置“姿式”

0X0 前言java

Android 系统中,当咱们安装apk文件的时候,lib 目录下的 so 文件会被解压到 app 的原生库目录,通常来讲是放到 /data/data/<package-name>/lib 目录下,而根据系统和CPU架构的不一样,其拷贝策略也是不同的,在咱们测试过程当中发现不正确地配置了 so 文件,好比某些 app 使用第三方的 so 时,只配置了其中某一种 CPU 架构的 so,可能会形成 app 在某些机型上的适配问题。因此这篇文章主要介绍一下在不一样版本的 Android 系统中,安装 apk 时,PackageManagerService 选择解压 so 库的策略,并给出一些 so 文件配置的建议。架构

0x1 Android4.0之前app

apk 被安装时,执行路径虽然有差异,但最终要调用到的一个核心函数是 copyApk,负责拷贝 apk 中的资源。ide

参考2.3.6 Android 源码,它的 copyApk 其内部函数一段选取原生库 so 逻辑:函数

public static int listPackageNativeBinariesLI(ZipFile zipFile, List> nativeFiles) throws ZipException, IOException {
  
 }

篇幅有限,这里还有下面全部的源代码没法展现出来,若是要查看完整源码的版本,请戳这里性能


这段代码中的 Build.CPU_ABI "ro.product.cpu.abi2" 分别为手机支持的主 abi 和次 abi 属性字符串,abi 为手机支持的指令集所表明的字符串,好比 armeabi-v7aarmeabix86mips 等,而主 abi 和次 abi 分别表示手机支持的第一指令集和第二指令集。代码首先调用 listPackageSharedLibsForAbiLI 来遍历主 abi 目录。当主 abi 目录不存在时,才会接着调用 listPackageSharedLibsForAbiLI 遍历次 abi 目录。测试

private static int listPackageSharedLibsForAbiLI(ZipFile zipFile, String cpuAbi, List> libEntries) throws IOException, ZipException {
 }


listPackageSharedLibsForAbiLI 中判断当前遍历的 apk 中文件的 entry 名是否符合 so 命名的规范且含相应 abi 字符串名。若是符合则规则则将 so entry 名加入 list,若是遍历失败或者规则不匹配则返回相应错误码。ui

拷贝 so 策略:spa

遍历 apk 中文件,当 apk lib 目录下主 abi 子目录中有 so 文件存在时,则所有拷贝主 abi 子目录下的 so;只有当主 abi 子目录下没有 so 文件的时候即 PACKAGE_INSTALL_NATIVE_ABI_MISMATCH 的状况,才会拷贝次 ABI 子目录下的 so 文件。code

策略问题:

so 放置不当时,安装 apk 时会致使拷贝不全。好比 apk lib 目录下存在 armeabi/libx.so , armeabi/liby.so , armeabi-v7a/libx.so 3 so 文件,那么在主 ABI armeabi-v7a 且系统版本小于4.0的手机上, apk 安装后,按照拷贝策略,只会拷贝主 abi 目录下的文件即 armeabi-v7a/libx.so,当加载 liby.so 时就会报找不到 so 的异常。另外若是主 abi 目录不存在,这个策略会遍历2 apk,效率偏低。

0x2 Android 4.0-Android 4.0.3

参考4.0.3 Android 源码,同理,找处处理 so 拷贝的核心逻辑( native 层):

static install_status_t iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2, iterFunc callFunc, void* callArg) {
     ...
 }


拷贝 so 策略:

遍历 apk 中全部文件,若是符合 so 文件的规则,且为主 ABI 目录或者次 ABI 目录下的 so,就解压拷贝到相应目录。

策略问题:

存在同名 so覆盖,好比一个 app armeabi armeabi-v7a 目录下都含同名的 so,那么就会发生覆盖现象,覆盖的前后顺序根据 so 文件对应 ZipFileR0 中的 hash 值而定,考虑这样一个例子,假设一个 apk 同时有 armeabi/libx.so armeabi-v7a/libx.so,安装到主 ABI armeabi-v7a 的手机上,拷贝 so 时根据遍历顺序,存在一种可能即 armeab-v7a/libx.so 优先遍历并被拷贝,随后 armeabi/libx.so 被遍历拷贝,覆盖了前者。原本应该加载 armeabi-v7a 目录下的 so,结果按照这个策略拷贝了 armeabi 目录下的 so

apk 中文件 entry 的散列计算函数以下:

unsigned int ZipFileRO::computeHash(const char* str, int len)
 {   
 }


0x3 Android 4.0.4之后

4.1.2系统为例,遍历选择 so 逻辑以下:

static install_status_t iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2, iterFunc callFunc, void* callArg) {
     
 }


拷贝 so 策略:

遍历 apk 中文件,当遍历到有主 Abi 目录的 so 时,拷贝并设置标记 hasPrimaryAbi 为真,之后遍历则只拷贝主 Abi 目录下的 so,当标记为假的时候,若是遍历的 so entry 名含次 abi 字符串,则拷贝该 so

策略问题:

通过实际测试, so 放置不当时,安装 apk 时存在 so 拷贝不全的状况。这个策略想解决的问题是在 4.0 ~ 4.0.3 系统中的 so 随意覆盖的问题,即若是有主 abi 目录的 so 则拷贝,若是主 abi 目录不存在这个 so 则拷贝次 abi 目录的 so,但代码逻辑是根据 ZipFileR0 的遍历顺序来决定是否拷贝 so,假设存在这样的 apk lib 目录下存在 armeabi/libx.so , armeabi/liby.so , armeabi-v7a/libx.so 这三个 so 文件,且 hash 的顺序为 armeabi-v7a/libx.so armeabi/liby.so 以前,则 apk 安装的时候 liby.so 根本不会被拷贝,由于按照拷贝策略, armeabi-v7a/libx.so 会优先遍历到,因为它是主 abi 目录的 so 文件,因此标记被设置了,当遍历到 armeabi/liby.so 时,因为标记被设置为真, liby.so 的拷贝就被忽略了,从而在加载 liby.so 的时候会报异常。

0x4 64位系统支持

Android 5.0以后支持64 ABI,以5.1.0系统为例:

public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot, String abiOverride) {   
 }


copyNativeBinariesWithOverride 分别处理32位和64 so 的拷贝,内部函数 copyNativeBinariesForSupportedAbi 首先会根据 abilist 去找对应的 abi

public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, String[] abiList, boolean useIsaSubdir) throws IOException {
    
 }


findSupportedAbi 内部实现是 native 函数,首先遍历 apk,若是 so 的全路径中含 abilist 中的 abi 字符串,则记录该 abi 字符串的索引,最终返回全部记录索引中最靠前的,即排在 abilist 中最前面的索引。

static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {   
 }


举例说明,在某64位测试手机上的abi属性显示以下,它有2 abilist,分别对应该手机支持的32位和64 abi 的字符串组。

当处理32 so 拷贝时, findSupportedAbi 索引返回以后,若返回为0,则拷贝 armeabi-v7a 目录下的 so,若是为1,则拷贝 armeabi 目录下 so

拷贝 so 策略:

分别处理32位和64 abi 目录的 so 拷贝, abi 由遍历 apk 结果的全部 so 中符合 abilist 列表的最靠前的序号决定,而后拷贝该 abi 目录下的 so 文件。

策略问题:

策略假定每一个 abi 目录下的 so 都放置彻底的,这是和2.3.6同样的处理逻辑,存在遗漏拷贝 so 的可能。

0x5 建议

针对 Android 系统的这些拷贝策略的问题,咱们给出了一些配置 so 的建议:

1.    1)针对 armeabi armeabi-v7a 两种 ABI

方法1:因为 armeabi-v7a 指令集兼容 armeabi 指令集,因此若是损失一些应用的性能是能够接受的,同时不但愿保留库的两份拷贝,能够移除 armeabi-v7a 目录和其下的库文件,只保留 armeabi 目录;好比 apk 使用第三方的 so 只有 armeabi 这一种 abi 时,能够考虑去掉 apk lib 目录下 armeabi-v7a 目录。

方法2:在 armeabi armeabi-v7a 目录下各放入一份 so

2.    2)针对x86

目前市面上的x86机型,为了兼容 arm 指令,基本都内置了 libhoudini 模块,即二进制转码支持,该模块负责把 ARM 指令转换为 X86 指令,因此若是是出于 apk package大小的考虑,而且能够接受一些性能损失,能够选择删掉 x86 库目录, x86 下配置的 armeabi 目录的 so 库同样能够正常加载使用;

3.    3)针对64 ABI

若是 app 开发者打算支持64位,那么64位的 so 要放全,不然能够选择不单独编译64位的 so,所有使用32位的 so64位机型默认支持32 so 的加载。好比 apk 使用第三方的 so 只有32 abi so,能够考虑去掉 apk lib 目录下的64 abi 子目录,保证 apk 安装后正常使用。

0x6 备注

其实本文是由于在 Android so 加载上遇到不少坑,相信不少朋友都遇到过 UnsatisfiedLinkError 这个错误,反应在用户的机型上也是千差万别,可是有没有想过,可能不是 apk 逻辑的问题,而是 Android 系统在安装 APK 的时候,因为 PackageManager 的问题,并无拷贝相应的 SO 呢?能够参考下面第4个连接,做者给出了解决方案,就是当出现 UnsatisfiedLinkError 错误时,手动拷贝 so 来解决的。

因为oschina篇幅有限,更详细文章请点击原文Android 系统安装 apk 时解压 so 的逻辑问题

相关文章
相关标签/搜索