写点什么

Android 关于 CPU 类型的 so 文件兼容问题(ABI)

发布于: 2021 年 04 月 26 日
Android 关于CPU类型的so文件兼容问题(ABI)

当我们想要在项目中使用 native(C/C++)类库或者依赖一些第三方库的时候,往往需要导入包含 native 代码的.so 文件,默认情况下,为了使 APP 有更好的兼容性,我们使用 Android Studio 或者命令打包时,会默认支持所有的架构,但相应的 APK size 会疯狂的增大。APK 文件的大小,也是会直接影响用户的安装率,不利于 app 的推广的,为了解决这个问题,Google 为我们提供了 abifilters,abifilters 为我们提供了选择适配指定 CPU 架构的能力,只需要在 app 下的 build.gradle 添加如下配置:


        defaultConfig {            ndk {                abiFilters 'arm64-v8a', 'x86_64'            }        }    }
复制代码

ABI

ABI 的英文缩写是 Application Binary Interface,即应用二进制接口。不同 Android 设备,使用的 CPU 架构可能不同,因此支持不同的指令集。CPU 与指令集的每种组合都有其自己的 ABI,ABI 非常精确地定义了应用程序的机器代码应如何在运行时与系统交互。

ABI 的作用

以最简单的“Hello World”应用为例,我们看他的 apk 文件:



没有任何的原生库使用,大小为 2.1MB,现在我们为它添加多 ABI 原生库支持,我们在项目中集成 Realm,然后打包。



apk 大小从 2.1MB 增加到了 11.2MB,多了一个原生 so 库的文件夹,大小为 8.8MB,如图所示,Realm 为 5 种 CPU 架构生成了.so 库,分别是 mips、x86、x86_64、arm64-v8a、armeabi-v7a。增加了 8.8MB 包的大小。实际上用户使用的时候,只需要一种 so 库版本,我们只需要适配我们指定的的 CPU 架构,因此,我们需要在 gralde.build 中添加 abifilters 配置来达到我们想要的效果:


    compileSdkVersion 28 // 编译sdk版本    defaultConfig {        applicationId "com.example.zhouwei.helloworld"        minSdkVersion 15        targetSdkVersion 28        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"        // 适配指定CPU架构        ndk {            abiFilters 'arm64-v8a', 'x86_64'        }    }}
复制代码


效果如下:



可以看到,只生成了我们指定 CPU 架构的 so 文件,包的大小也减少了 5.3MB。

Android CPU 架构在当前市场的占有率

Android 目前支持 7 种 ABIs:mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a


  • arm64-v8a: 第 8 代、64 位 ARM 处理器,目前主流版本。

  • armeabi-v7a: 第 7 代及以上的 ARM 处理器,2011 年 15 月以后的生产的大部分 Android 设备,现在以 arm64-v8a 为多。

  • armeabi: 第 5 代、第 6 代的 ARM 处理器,早期的手机用的比较多,可以兼容所有 ARM 设备,速度比较慢。

  • x86 / x86_64: 平板、模拟器用得比较多,x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现对 arm .so 的兼容,而且目前 x86 市场占有率很低,可能只有 1%,所以 x86 相关的两个 so 文件是可以忽略的。

  • mips / mips64: NDK 以前支持 ARMv5 (armeabi) 以及 32 位和 64 位 MIPS,但 NDK r17 已不再支持,极少用于手机,可以忽略。


目前手机市场上,x86 / x86_64/armeabi/mips / mips6 的架构,基本可以不不考虑了,它们的占有量应很少很少了,arm64-v8a 作为最新一代架构,应该是目前的主流,armeabi-v7a 只存在少部分老旧手机。


Google Play 从 2019 年 8 月开始,就强制 APP 适配 arm64-v8a,以慢慢淘汰 32 位的 armeabi-v7a。


查看手机的 CPU ABI

通过 adb 命令查看

  1. 连接手机到电脑上

  2. 打开 cmd 命令窗口,输入命令 adb shell

  3. 然后输入命令 cat /proc/cpuinfo

通过代码获取

Build.CPU_ABI、Build.CPU_ABI2,API level 大于等于 21 时,使用 Build.SUPPORTED_ABIS

如何适配

ABI 是如何工作

一般来说,一个 Android 设备可以支持多种 ABI,设备主 ABI 和辅助 ABI,以 arm64-v8a 为主 ABI 的设备,辅助 ABI 为 armeabi-v7a 和 armeabi,以 armeabi-v7a 为主 ABI 的设备,辅助 ABI 为 armeabi,也就是说他是向下兼容的,即 arm64-v8a>armeabi-v7a>armeabi


例如:对于一个 cpu 是 arm64-v8a 架构的手机,它运行 app 时,进入 jnilibs 去读取库文件时,先看有没有 arm64-v8a 文件夹,如果没有该文件夹,去找 armeabi-v7a 文件夹,如果没有,再去找 armeabi 文件夹,如果连这个文件夹也没有,就抛出异常;


如果有 arm64-v8a 文件夹,那么就去找特定名称的.so 文件,注意:如果没有找到想要的.so 文件,不会再往下(armeabi-v7a 文件夹)找了,而是直接抛出异常。


Exception:Java.lang.UnsatisfiedLinkError: dlopen failed: library “/***.so” not found


特别需要注意的情况是在命中了文件夹,而未命中 so 文件这种情况:


  • 比如命中了 arm64-v8a 文件夹,没有找到需要的 so 文件,就不会再往下(armeabi-v7a 文件夹)找了,而是直接抛出异常。

  • 如果你的项目用到了第三方依赖,如果只保留一个 ABI 的时候,建议在 Build 中加入 ndk.abiFilters


例如:第三方 aar 文件,如果这个 sdk 对 abi 的支持比较全,可能会包含 armeabi、armeabi-v7a、x86、arm64-v8a、x86_64 五种 abi,而你应用的其它 so 只支持 armeabi、armeabi-v7a、x86 三种,直接引用 sdk 的 aar,会自动编译出支持 5 种 abi 的包。但是应用的其它 so 缺少对其它两种 abi 的支持,那么如果应用运行于 arm64-v8a、x86_64 为首选 abi 的设备上时,就会 crash 了。


因此,我们需要在我们的 app 中配置 abiFilter 配置,来避免一些未知的错误。


defaultConfig {    ndk {        abiFilters "armeabi"// 指定ndk需要兼容的ABI(这样其他依赖包里x86,armeabi,arm-v8之类的so会被过滤掉)    }}
复制代码

如何去设置、去适配

根据 ABI 向下兼容性的特点,我们可以得出一下这些结论:


因为 armeabi-v7a 和 arm64-v8a 会向下兼容:


  • 只适配 armeabi 的 APP 可以跑在 armeabi,x86,x86_64,armewabi-v7a,arm64-v8 上

  • 只适配 armeabi-v7a 可以运行在 armeabi-v7a 和 arm64-v8a

  • 只适配 arm64-v8a 可以运行在 arm64-v8a 上 ye 那我们该如何适配呢?


一、只适配 armeabi


  • 优点:基本上适配了全部 CPU 架构(除了淘汰的 mips 和 mips_64)

  • 缺点:性能低,相当于在绝大多数手机上都是需要辅助 ABI 或动态转码来兼容


二、只适配 armeabi-v7a


能运行在 arm64-v8 和 armeabi-v7a 机器上,在性能和兼容二者中比较平衡


三、只适配 arm64-v8


只能运行在 arm64-v8 上,要放弃部分老旧设备用户,优点就是:性能最佳


这三种方案都是可以的,现在的大厂 APP 适配中,这三种都有,大部分是前 2 种方案。具体选哪一种就看自己的考量了,以性能换兼容就 arm64-v8,以兼容换性能 armeabi,二者稍微平衡一点的就 armeabi-v7a。最后,根据市场上 ABI 的占有率情况,我们可以选择第二种方案,性能和兼容二者中比较平衡,一则 armeabi 类型的手机已经很少了,二则可以兼容 armeabi-v7a;若你比较看重性能的也可以选择第一种方案。

abi split 分包

abi split,分包就是为每个 CPU 架构单独打一个 APK,为了性能和兼容同时兼得,Google 提供了 abi split 分包的机制:


在 gradle 中添加如下配置:


android {      ...      splits {
// Configures multiple APKs based on ABI. abi {
// Enables building multiple APKs per ABI. enable true
// By default all ABIs are included, so use reset() and include to specify that we only // want APKs for x86 and x86_64.
// Resets the list of ABIs that Gradle should create APKs for to none. reset()
// Specifies a list of ABIs that Gradle should create APKs for. include "x86", "x86_64", "arm64-v8a", "armeabi", "armeabi-v7a"
// Specifies that we do not want to also generate a universal APK that includes all ABIs. universalApk false } } }
复制代码


然后,就能为每个 CPU 架构单独打一个 APK,该 apk 中就只包含一个平台,如下:



这样,又能保证性能,又能不额外增加 APK 的大小,同时又又很完美的兼容,因为可以为所有架构都单独打一个包,一举多得。


同时,Google Play 支持上传多个 APK:



这样,就能根据不同的 CPU 架构,下载不同的包啦!


注意:但是,很遗憾,国内的应用商店目前还不支持这种方式哦!!!!

用户头像

还未添加个人签名 2020.10.09 加入

Android领域,打工人

评论

发布
暂无评论
Android 关于CPU类型的so文件兼容问题(ABI)