ml
这份配置文件在 data/system/ 目录下,不要小看这份文件,因为不管系统应用还是三方应用,安装过程中都会将其自身的基本信息写入这份文件中。所以,借助这份文件,可以获取到蛮多信息的。
比如,一般排查系统应用为什么启动不了,就可以借助这份文件。
碰到过这么一个问题,我们做的一些应用是没有界面的,就纯粹在后台干活。如果是三方,也许还可以通过手动去启动这个应用来查看相关日志,但偏偏还有些应用是设备开机时就自启的,所以最怕遇到的问题就是测试人员跟你说,这个应用在某个终端上起不来。
因为这时,不清楚这个应用到底是不是因为代码问题导致一直崩溃,起不来;还是因为根本就没安装成功;所以,遇到这类问题,第一点就是要先确认这一点,而确认这一点,就可以借助 packages.xml 这份配置文件了。
如果能够在这份 packages.xml 配置文件中找到应用的信息,那么说明安装成功了,接下去就往另一个方向排查问题了。
还有一种场景借助这份配置文件分析也是很有帮助的。
我们还遇到这种情况:
首先 system/app 下是系统应用,data/app 下是三方应用,但系统是允许 system/app 和 data/app 下存在相同包名的应用,因为允许系统应用进行升级操作,只是此时系统应用将变成三方应用权限。
某次,有反馈说,system/app 下已集成了最新版本的应用,但为什么,每次启动应用时,运行的都是旧版本。这时候怎么排查,就是根据 packages.xml 中这个应用的基本信息,它包括,这个应用的版本号,apk 的来源目录,so 文件的加载地址,所申请的权限等等。
有了这些信息,足够确认,此刻运行的应用是 data/app 下的 apk,还是 system/app 下的 apk。确认了之后,再进一步去排查。
4. System.load 和 System.loadlibrary
load()
和 loadlibrary()
都是用来加载 so 文件的,区别仅在于 load()
接收的是绝对路径,比如 ”data/data/{包名}/lib/xxx.so“ 这样子,因为是绝对路径,所以最后跟着的是 so 文件全名,包括后缀名。
而 loadlibrary()
只需传入 so 文件去头截尾的名字就可以了,如 libblur.so,只需传入 blur 即可,内部会自动补全 so 文件可能存在的路径,以及补全 lib 前缀和 .so 后缀。
所以,下面要讲的,其实就是 loadlibrary()
加载 so 文件的流程。
因为之前碰到过这么个问题,有些不大理解:
我们的应用是系统应用,那么 so 文件也就是集成到 system/lib 或者 system/lib64 目录下,但不清楚,程序是根据什么决定是应该去 system/lib 目录下加载 so 文件,还是去 system/lib64 下加载,或者两处都会去?
所以,下个小节就是讲这个。
5. so 文件加载流程
这节是本篇的重点,打算亲自过下源码来梳理,但这样篇幅会特别长,基于此,就另起一篇来专门写从源码中梳理 so 文件的加载流程吧,这里就只给出链接和几点结论,感兴趣的可以去看看。
Android 的 so 文件加载机制
- 一个应用在安装过程中,系统会经过一系列复杂的逻辑确定两个跟 so 文件加载相关的 app 属性值:nativeLibraryDirectories ,primaryCpuAbi ;
- nativeLibraryDirectories 表示应用自身存放 so 文件的目录地址,影响着 so 文件的加载流程;
- primaryCpuAbi 表示应用应该运行在哪种 abi 上,如(armeabi-v7a),它影响着应用是运行在 32 位还是 64 位的进程上,进而影响到寻找系统指定的 so 文件目录的流程;
- 以上两个属性,在应用安装结束后,可在 data/system/packages.xml 中查看;
- 当调用 System 的
loadLibrary()
加载 so 文件时,流程如下:
- 先到 nativeLibraryDirectories 指向的目录中寻找,是否存在且可用的 so 文件,有则直接加载这里的 so 文件;
- 上一步没找到的话,则根据当前进程如果是 32 位的,那么依次去 vendor/lib 和 system/lib 目录中寻找;
- 同样,如果当前进程是 64 位的,那么依次去 vendor/lib64 和 system/lib64 目录中寻找;
- 当前应用是运行在 32 位还是 64 位的进程上,取决于系统的 ro.zygote 属性和应用的 primaryCpuAbi 属性值,系统的 ro.zygote 可通过执行 getprop 命令查看;
- 如果 ro.zygote 属性为 zygote64_32,那么应用启动时,会先在 ro.product.cpu.abilist64 列表中寻找是否支持 primaryCpuAbi 属性,有,则该应用运行在 64 位的进程上;
- 如果上一步不支持,那么会在 ro.product.cpu.abilist32 列表中寻找是否支持 primaryCpuAbi 属性,有,则该应用运行在 32 位的进程上;
- 如果 ro.zygote 属性为 zygote32_64,则上述两个步骤互换;
- 如果应用的 primaryCpuAbi 属性为空,那么以 ro.product.cpu.abilist 列表中第一个 abi 值作为应用的 primaryCpuAbi;
- 运行在 64 位的 abi 有:arm64-v8a,mips64,x86_64
- 运行在 32 位的 abi 有:armeabi-v7a,armeabi,mips,x86
- 通常支持 arm64-v8a 的 64 位设备,都会向下兼容支持 32 位的 abi 运行;
- 但应用运行期间,不能混合着使用不同 abi 的 so 文件;
- 比如,当应用运行在 64 位进程中时,无法使用 32 位 abi 的 so 文件,同样,应用运行在 32 位进程中时,也无法使用 64 位 abi 的 so 文件;
6. 三方库 ReLinker 和 Soloder
ReLinker 和 Soloder 都是用于解决一些 so 文件加载失败的场景,比如:
- 嵌套的 so 文件加载异常,如程序引用了三方库,三方库又引用了三方库,各自库中又都存在 so 文件加载,有时候可能会导致 so 文件加载失败。
- so 文件缺失导致加载异常,如程序的 so 文件在设备的 so 目录中不见了之类的异常。
- 等等
它们的 Github 地址:
SoLoader:https://github.com/facebook/SoLoader
ReLinker:https://github.com/KeepSafe/ReLinker
ReLinker 的原理我有去源码梳理了一遍,大体上是这样:
- 先调用系统
System.loadlibrary()
加载 so 文件,如果成功,结束;
- 如果失败,则重新解压 apk 文件,解析其中的 lib 目录,遍历 so 文件,找到所需的 so 文件时,将其缓存一份至 data/data/{包名}/app-lib 目录下,调用
System.load()
加载这份 so 文件;
- 之后每次应用重启,仍旧先调用系统的
System.loadlibrary()
去尝试加载 so 文件,失败,如果 app-lib 下有缓存,且可用,则加载这个缓存的 so 文件,否则重新解压 apk,继续 2 步骤。
- 当然,解压 apk 遍历 so 文件时,如果需要的 so 文件存在于不同的 CPU 架构目录中,并不加以区分,直接拿第一个遍历到的 so 文件。
SoLoder 的原理