博客> Android 系统安装 apk 时解压 so 的逻辑问题
Android 系统安装 apk 时解压 so 的逻辑问题
2020-02-26 19:08 评论:0 阅读:1647 Julylylyly
sdk Android apk

0X0 前言 在 Android 系统中,当我们安装apk文件的时候,lib 目录下的 so 文件会被解压到 app 的原生库目录,一般来说是放到 /data/data//lib 目录下,而根据系统和CPU架构的不同,其拷贝策略也是不一样的,在我们测试过程中发现不正确地配置了 so 文件,比如某些 app 使用第三方的 so 时,只配置了其中某一种 CPU 架构的 so,可能会造成 app 在某些机型上的适配问题。所以这篇文章主要介绍一下在不同版本的 Android 系统中,安装 apk 时,PackageManagerService 选择解压 so 库的策略,并给出一些 so 文件配置的建议。 0x1 Android4.0以前 当 apk 被安装时,执行路径虽然有差别,但最终要调用到的一个核心函数是 copyApk,负责拷贝 apk 中的资源。 参考2.3.6的 Android 源码,它的 copyApk 其内部函数一段选取原生库 so 逻辑:

public static int listPackageNativeBinariesLI(ZipFile zipFile, List> nativeFiles) throws ZipException, IOException { String cpuAbi = Build.CPU_ABI; int result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi, nativeFiles); /*

  • Some architectures are capable of supporting several CPU ABIs
  • for example, 'armeabi-v7a' also supports 'armeabi' native code
  • this is indicated by the definition of the ro.product.cpu.abi2
  • system property.
  • only scan the package twice in case of ABI mismatch / if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) { final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2", null); if (cpuAbi2 != null) { result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi2, nativeFiles); } if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) { Slog.w(TAG, "Native ABI mismatch from package file"); return PackageManager.INSTALL_FAILED_INVALID_APK; } if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) { cpuAbi = cpuAbi2; } } /
  • Debuggable packages may have gdbserver embedded, so add it to
  • the list to the list of items to be extracted (as lib/gdbserver)
  • into the application's native library directory later. */ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) { listPackageGdbServerLI(zipFile, cpuAbi, nativeFiles); } return PackageManager.INSTALL_SUCCEEDED; }

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

private static int listPackageSharedLibsForAbiLI(ZipFile zipFile, String cpuAbi, List> libEntries) throws IOException, ZipException { final int cpuAbiLen = cpuAbi.length(); boolean hasNativeLibraries = false; boolean installedNativeLibraries = false; if (DEBUG_NATIVE) { Slog.d(TAG, "Checking " + zipFile.getName() + " for shared libraries of CPU ABI type " + cpuAbi); } Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); // skip directories if (entry.isDirectory()) { continue; } String entryName = entry.getName(); /*

  • Check that the entry looks like lib//lib.so
  • here, but don't check the ABI just yet.
    • must be sufficiently long
    • must end with LIB_SUFFIX, i.e. ".so"
    • must start with APK_LIB, i.e. "lib/" / if (entryName.length() < MIN xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed xss=removed> 0) { copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, Build.SUPPORTED_32_BIT_ABIS, true / use isa specific subdirs /); if (copyRet < 0 xss=removed xss=removed copyRet=" +copyRet); return copyRet; } } if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, Build.SUPPORTED_64_BIT_ABIS, true / use isa specific subdirs /); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { Slog.w(TAG, " copyRet=" +copyRet); return copyRet; } } } else { String cpuAbiOverride = null; if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) { cpuAbiOverride = null; } else if (abiOverride != null) { cpuAbiOverride = abiOverride; } String[] abiList = (cpuAbiOverride != null) ? new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS; if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null && hasRenderscriptBitcode(handle)) { abiList = Build.SUPPORTED_32_BIT_ABIS; } int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList, true / use isa specific subdirs /); if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { Slog.w(TAG, " errorCode=" + copyRet + " xss=removed>= 0) { /
  • If we have a matching instruction set, construct a subdir under the native
  • library root that corresponds to this instruction set. */ final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]); final File subDir; if (useIsaSubdir) { final File isaSubdir = new File(libraryRoot, instructionSet); createNativeLibrarySubdir(isaSubdir); subDir = isaSubdir; } else { subDir = libraryRoot; } int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]); if (copyRet != PackageManager.INSTALL_SUCCEEDED) { return copyRet; } } return abi; }

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

static int findSupportedAbi(JNIEnv env, jlong apkHandle, jobjectArray supportedAbisArray) { const int numAbis = env->GetArrayLength(supportedAbisArray); Vector supportedAbis; for (int i = 0; i < numAbis>GetObjectArrayElement(supportedAbisArray, i))); } ZipFileRO zipFile = reinterpret_cast(apkHandle); if (zipFile == NULL) { return INSTALL_FAILED_INVALID_APK; } UniquePtr it(NativeLibrariesIterator::create(zipFile)); if (it.get() == NULL) { return INSTALL_FAILED_INVALID_APK; } ZipEntryRO entry = NULL; char fileName[PATH_MAX]; int status = NO_NATIVE_LIBRARIES; while ((entry = it->next()) != NULL) { // We're currently in the lib/ directory of the APK, so it does have some native // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the // libraries match. if (status == NO_NATIVE_LIBRARIES) { status = INSTALL_FAILED_NO_MATCHING_ABIS; } const char fileName = it->currentEntry(); const char lastSlash = it->lastSlash(); // Check to see if this CPU ABI matches what we are looking for. const char* abiOffset = fileName + APK_LIB_LEN; const size_t abiSize = lastSlash - abiOffset; for (int i = 0; i < numAbis xss=removed>size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) { // The entry that comes in first (i.e. with a lower index) has the higher priority. if (((i < status>= 0)) || (status < 0) ) { status = i; } } } } for (int i = 0; i < numAbis; ++i) { delete supportedAbis[i]; } return status; }

举例说明,在某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)针对 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)针对x86 目前市面上的x86机型,为了兼容 arm 指令,基本都内置了 libhoudini 模块,即二进制转码支持,该模块负责把 ARM 指令转换为 X86 指令,所以如果是出于 apk 包大小的考虑,并且可以接受一些性能损失,可以选择删掉 x86 库目录, x86 下配置的 armeabi 目录的 so 库一样可以正常加载使用; 3)针对64位 ABI 如果 app 开发者打算支持64位,那么64位的 so 要放全,否则可以选择不单独编译64位的 so,全部使用32位的 so,64位机型默认支持32位 so 的加载。比如 apk 使用第三方的 so 只有32位 abi 的 so,可以考虑去掉 apk 中 lib 目录下的64位 abi 子目录,保证 apk 安装后正常使用。 0x6 备注 其实本文是因为在 Android 的 so 加载上遇到很多坑,相信很多朋友都遇到过 UnsatisfiedLinkError 这个错误,反应在用户的机型上也是千差万别,但是有没有想过,可能不是 apk 逻辑的问题,而是 Android 系统在安装 APK 的时候,由于 PackageManager 的问题,并没有拷贝相应的 SO 呢?可以参考下面第4个链接,作者给出了解决方案,就是当出现 UnsatisfiedLinkError 错误时,手动拷贝 so 来解决的。

参考链接: Android 源码:https://android.googlesource.com/platform/frameworks/base apk 安装过程及原理说明:http://blog.csdn.net/hdhd588/article/details/6739281 网易云加密:http://apk.aq.163.com/ UnsatisfiedLinkError 的错误及解决方案:https://medium.com/keepsafe-engineering/the-perils-of-loading-native-libraries-on-android-befa49dce2db#.hell8vvdm

收藏
0
sina weixin mail 回到顶部