文档章节

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

网易云捕
 网易云捕
发布于 2016/04/25 13:05
字数 2352
阅读 1.4K
收藏 0

0X0 前言

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以前

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

参考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,如果遍历失败或者规则不匹配则返回相应错误码。

拷贝 so 策略:

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

策略问题:

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 的逻辑问题

 


© 著作权归作者所有

网易云捕
粉丝 9
博文 9
码字总数 12305
作品 0
杭州
私信 提问
加载中

评论(0)

初探 PhoneGap 框架在 Android 上的表现

phonegap是由温哥华的一家小公司研发的多平台的移动开发框架,支持流行的大多数移动设备(iPhone,Android ,BlackBerry,Symbian,Palm,Window Phone).目前phonegap获得Apple,IBM,NOKIA,pal...

无鸯
2011/09/09
7.7K
8
开发者教程之地图SDK系列丨第二期:《集成Andriod百度地图SDK》

本文作者:用户_123456789 原文链接地址:https://developer.baidu.com/topic/show/290280

百度开发者中心
2019/08/28
2
0
android开发目录结构说明

src目录是放源码的地方。 gen目录是自生成文件,程序员不应该改此目录下的文件。 下面两个Android目录是android自带的支持文件及低版本支持文件。 assets目录是资源文件,放置音频图片等,此...

Sadhu
2015/02/02
394
0
Android工具HierarchyViewer 代码导读(2) -- 建立Eclipse调试环境

在上文<Android工具HierarchyViewer 代码导读(1) -- 功能实现演示>中,我们介绍了HierarchyViewer主要技术点的实现。虽然我们还没有涉及到HierarchyViewer的源代码,但是利用上节所讲到的知识...

知平软件
2012/08/03
485
0
『转』搭建基于 PhoneGap 框架的 Mobile 应用

简介: 目前,随着 Google 的 Android 手机和苹果的 iPhone 手机成为手机市场的主流,越来越多的开发者加入到移动应用开发的大军中,但是基于 Java 的 Android 应用和基于 C 语言的 iPhone 应...

老盖
2011/08/25
3.5K
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Boot 如何以 Web 应用的方式启动

在 Spring Boot 启动的时候,在进程完成后会自动退出。 如何让 Spring Boot 以 Web 方式启动,并且进程不退出呢? 需要确定下 Web 的这个依赖是否在你的依赖中。 <dependency> ...

honeymoose
23分钟前
36
0
leetcode892(三维形体的表面积)--C语言实现

求: 在 N * N 的网格上,我们放置一些 1 * 1 * 1 的立方体。 每个值 v = grid[i][j] 表示 v 个正方体叠放在对应单元格 (i, j) 上。 请你返回最终形体的表面积。 示例 1: 输入:[[2]] 输出:...

拓拔北海
28分钟前
48
0
使用* args和** kwargs [重复] - Use of *args and **kwargs [duplicate]

问题: This question already has answers here : 这个问题已经在这里有了答案 : What does ** (double star/asterisk) and * (star/asterisk) do for parameters? **(双星号/星号)和*(......

技术盛宴
34分钟前
42
0
spring-boot之@ConfigurationProperties的使用

@ConfigurationProperties是什么? Using the @Value("${property}") annotation to inject configuration properties can sometimes be cumbersome, especially if you are working with mu......

书中迷梦
35分钟前
67
0
让你快速掌握_正则表达式_的技巧(二)

经过上篇的快速入门了正则表达式,今天就带你快速掌握正则表达式的技巧, 话不多说,直接上干货! 正则表达式-附录【重点】 一. 规则 规则:. 含义:代表的是某一位,可以是任何字符 例如:匹配规...

煌sir
37分钟前
39
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部