java so apk_APK安装流程详解4——安装中关于so库的那些事

APK安装流程系列文章整体内容如下:

本片文章的主要内容如下:

1、ABI简介

2、PackageManagerService#derivePackageAbi(PackageParser.Package, File,String, boolean)方法解析

3、PackageManagerService#setNativeLibraryPaths(PackageParser.Package)方法分析

一、ABI简介

ABI全程是:Application binary interface,即:应用程序二进制接口,它定义了一套规则,允许编译好的二进制目标代码在所兼容该ABI的操作系统和硬件平台中无需改动就能运行。

不同的Android手机使用不同的CPU,因此支持不同的指令集。CPU与指令集的每种组合都有其自己的应用二进制接口(或ABI)。"ABI"精确定义了"运行时,应用的机器码和系统的交互方式"。你必须为应用要使用每个CPU架构指定ABI。

典型的ABI包含以下信息:

1、机器代码应使用的CPU指令集

2、运行时内存存储和加载的字节顺序

3、可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型

4、用于解析内容与系统之间的数据的各种约定。这些约定包括对齐限制,以及系统如何使用堆栈和在调用函数时注册。

5、运行时可用于机器代码的函数符号列表 - 通常来自非常具体的库集。

由上述定义可以判断:

ABI定义了规则,而具体的实现是由编译器、CPU、操作系统共同来完成的。不同的CPU芯片(如:ARM、Intel x86、MIPS)支持不同的ABI架构,常见的ABI类型包含:armabi、armabi-v7a、x86、x86_64、mips、mips64、arm64-v8a等。

这也就是为什么我们编译出的运行于windows的二进制程序不能运行于Mac OS/Linux/Android平台了,因此CPU芯片和操作系统均不相同,支持的ABI类型也不一样,因此无法识别对方的二进制程序。

而我们说的"交叉编译"的核心原理也跟这些密切相关,交叉编译,就是使用交叉编译工具,在平台上编译生成另一个平台的二进制可执行程序,为什么可以做到?因为交叉编译工具实现了另一个平台所定义的ABI规则。我们在Window/Linux平台使用Android NDK交叉编译工具来编译出Android平台的库也是这个道理。

(一)、.so文件与ABI

如果你的项目中使用了NDK,它就生成了.so文件。如果你的项目只使用了Java语言进行编程,可能就不太关注so文件了。因为Java是跨平台的。但是其实项目中的依赖函数库或者引擎库已经嵌入了so文件。并依赖不同的ABI,比如项目中使用了百度地图,里面就会涉及相应的so文件。

Android应用支持的ABI取决于APK中位于lib/ABI目录中的so文件,其中

ABI可能是上面说过的其中ABI的一种

(二)、关于so文件的一些补充

1、so文件的重要法则

处理so文件时有一条简单但却很重的法则:

应该尽最大可能为每个ABI提供经过优化过的.so文件,且最好不要混合着使用。即你应该为每个ABI目录提供对应的so文件。

2、NDK兼容性

使用NDK时,一般人会倾向于使用最新的编译凭条,但实际上这样做是有问题的。因为NDK平台是不向后兼容的,而是向前兼容的。所以推荐使用APP的minSdkVersion对应的编译平台。这也意味着当你引入一个预编译好的.so文件时,你需要检查它被编译所用的平台版本。

3、混合使用不同的编译的so文件

so文件可以依赖于不同的C++运行时,静态编译或者动态加载,混合使用不同版本的C++运行时可能会导致很多奇怪的crash。最好避免这种情况。

PS:当只有一个so文件时,静态编译C++运行时是没有问题的。但是当存在多个so文件时,应该让所有so文件都动态链接相同的C++运行时。这意味着当引入一个新的预编译so文件,而且项目中还存在其他so文件时,我们需要首先确认新引入的so文件使用的C++运行时是否已经存在的so文件一致。

(三)、ABI和CPU的关系

1、Android CPU的基础知识

C++代码必须根据Android 设备的CPU类型(通常称为"ABIs")进行编译,常用的五种 ABI:

armeabiv-v7a:第七代及以上ARM处理器。2011年以后的生产的大部分Android设备都是用它。

arm64-v8a:第8代、64位ARM处理器,设备不多,比如三星Galaxy S6

armeabi:第5代、第6代ARM处理器,早期的手机用的比较多。

x86:平台、模拟器用得比较多。

x86_64:64位的平板。

2、 ABI支持CPU列表

ABI支持CPU列表,如下:

c9fc6743a383

ABI支持CPU列表.png

举例说明:

在x86设备上,选择ABI的先后顺序

第一步:在libs/x86目录中如果存在.so文件的话,会被安装,如果没有走第二步。

第二步:会在armeabi-v7a中的.so文件,如果有,会被安装,如果没有会走第三步。

第三步:会在armeabi目录中的.so文件寻找

PS:x86设备能够很好的运行ARM类型函数库,但并不保证100% 发生crash,特别是对旧设备,因为是运行在x86设备上模拟ARM的虚拟层上。

3、 ABI支持CPU的知识点

1、大部分CPU都支持多余一种的ABI

2、 当一个应用安装在设备上,只有设备支持的CPU架构对应的.so文件会被安装。

3、64位设备(arm64-v8a、x86_64、mips64)能够运行32位的函数库,但是以32位版本的ART和Android组件,将丢失64位优化过的性能(ART、webview、media等等)。

4、最好针对特定平台提供相应平台的二进制包,这种情况下运行时就少了一个模拟层(例如x86设备上模拟arm模拟层),从而得到更好的性能(归功与最近的架构更新,例如硬件fpu,更多的寄存器,更好的向量化)。

5、会优先安装优先级较高的ABI目录,则其他优先级较低的ABI目录(包括其他module中的ABI目录),都无法安装。例如:在cpu是ARMv7架构的手机上,如果检测到armeabi-v7a,就会选择安装armeabi-v7a,则armeabi下的文件,就无法安装了。

6、相应的ABI二进制文件,要放进相应的ABI目录中

7、一般情况下不要随便修改架构目录名

(四)、常见问题:

1、so文件 放进了优先级低的ABI目录

问题:

如果你的项目中,有其他优先级更好的ABI目录,但是你把ABI文件方法放到了优先级低的目录,最后导致你的ABI文件无法被加载

举例:

某手机CPU架构是ARMv7,ABI文件是armeabi-v7a,但是放进了armeabi目录中:

导致结果

项目中有armeabi-v7a的目录,armeabi目录的文件,无法被加载,然后运行报错,出现类似于如下log信息。

Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/.xx../base.apk"],nativeLibraryDirectories=[/data/app/.xx../lib/arm, /vendor/lib, /system/lib]]] couldn't find "lib..xx...so"

解决方案:建议armeabi-v7a的目录下的文件和armeabi目录的文件保持一致。

2、两个第三方SDK中的ABI文件优先级不一样

问题:

两个第三方的SDK中ABI文件优先级不一样,手机加载运行时,会导致优先级低的库,无法被加载。

例子:

某手机CPU架构是ARMv7,项目中使用了两个第三方SDK:假设是"支付宝"和"银联".

支付宝:ABI文件是armeabi-v7a,所以放到armeabi-v7a目录中。

银联:ABI文件是armeabi,所以放到armeabi目录中。

导致结果:

在运行时,会发现运行后crash,出现如下日志:

Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/.xx../base.apk"],nativeLibraryDirectories=[/data/app/.xx../lib/arm, /vendor/lib, /system/lib]]] couldn't find "lib..xx...so"

解决方案:

解决方案1:

使用同一优先级的ABI文件,ABI文件放入到优先级相同的ABI目录

比如:

支付宝:ABI文件是armeabi-v7a,放到armeabi-v7a目录中。

银联:ABI文件是armeabi-v7a,放到armeabi-v7a目录中。

支付宝:ABI文件是armeabi,放到armeabi目录中。

银联:ABI文件是armeabi,放到armeabi目录中。

解决方案2:

如果两个第三方提供的是不同优先级的ABI文件,则将ABI文件放入到优先级相同的ABI。

比如:

支付宝:ABI文件是armeabi-v7a,放到armeabi目录中。

银联:ABI文件是armeabi,放到armeabi目录中。

二、 PackageManagerService#derivePackageAbi(PackageParser.Package, File,String, boolean)方法解析

这个方法在PackageManagerService的installPackageLI方法里面被调用。代码在代码在PackageManagerService.java 12442行

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {

...

derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride,true /* extract libs */);

...

}

在讲解这个方法的时候我们先来了解一个概念是"primaryCpuAbi",关于ABI的概念可以参考我的文章Android ABI简介 ,那"primaryCpuAbi"又是什么?

因为一个系统支持的ABI有很多,不止一个,比如一个64位的机器上它的supportAbiList,可能如下所示:

public static final String[] SUPPORTED_ABIS = getStringList("ro.product.cpu.abilist", ",");

root@:/ # getprop ro.product.cpu.abilist

arm64-v8a,armeabi-v7a,armeabi

所以它能支持的abi有如上的3个,这个primaryCpuAbi就是要知道当前程序的abi在他支持的abi中最靠前的的哪一个。同时依靠这个primaryCpuAbi的值可以决定我们的程序是运行在32位还是64位的。

那我们来看下derivePackageAbi这个方法的内部实现

代码在PackageManagerService.java 7553行

/**

* Derive the ABI of a non-system package located at {@code scanFile}. This information

* is derived purely on the basis of the contents of {@code scanFile} and

* {@code cpuAbiOverride}.

*

* If {@code extractLibs} is true, native libraries are extracted from the app if required.

*/

public void derivePackageAbi(PackageParser.Package pkg, File scanFile,

String cpuAbiOverride, boolean extractLibs)

throws PackageManagerException {

// TODO: We can probably be smarter about this stuff. For installed apps,

// we can calculate this information at install time once and for all. For

// system apps, we can probably assume that this information doesn't change

// after the first boot scan. As things stand, we do lots of unnecessary work.

// Give ourselves some initial paths; we'll come back for another

// pass once we've determined ABI below.

// *********** 第一步 ***********

// 设置so库的安装路径

setNativeLibraryPaths(pkg);

// We would never ne

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值