-
2021-03-16 16:14:54
0. 前言
前几天啊,在公众号发了一篇文章《优化ApK大小之ABI Filters 和 APK split》,评论区收到了一些留言说,文章讲得不够深入,关于系统是如何选择不同abi下的so库的?当前APP该如何适配?该去掉哪些该保留哪些?都存在一些疑问。
因此,决定亲自更文一篇,系统地讲一下关于Android CPU架构方面的一些东西,以及结合大厂APP如微信、支付宝、淘宝等APP的适配情况,分析我们开发APP中该如何适配。本文涉及以下几个问题:
- 什么是ABI?
- ABI有何作用?
- 目前大厂APP是如何适配不同的CPU架构的?
- ABI 是如何工作的?
- 我们自己的APP中该如何适配?
- ABI split-性能+兼容全都要
本篇文章中,就一一为你解答这些疑问。
1. 什么是ABI?
ABI是英文Application Binary Interface的缩写,及应用二进制接口。
不同Android设备,使用的CPU架构可能不同,因此支持不同的指令集。 CPU 与指令集的每种组合都有其自己的应用二进制界面(或 ABI),ABI非常精确地定义了应用程序的机器代码应如何在运行时与系统交互。您必须为要与您的应用程序一起使用的每种CPU架构指定一个ABI(Application Binary Interface)。
ABI 包含以下信息:
- 可使用的 CPU 指令集(和扩展指令集)。
- 运行时内存存储和加载的字节顺序。Android 始终是 little-endian。
- 在应用和系统之间传递数据的规范(包括对齐限制),以及系统调用函数时如何使用堆栈和寄存器。
- 可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。Android 始终使用 ELF。
- 如何重整 C++ 名称。
Android目前支持以下7种ABIs:
mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a
2. ABI有何作用?
当我们想要在项目中使用 native(C/C++) 类库,我们必须对要支持的处理器架构提供对应编译包。每个处理器架构需要我们提供一个或多个包含native代码的.so文件。
默认情况下,为了使APP有更好的兼容性,我们使用Android Studio 或者命令打包时,会默认支持所有的架构,但相应的APK size 会疯狂的增大。对于用户来说,目标设备只需要其中一个版本,但当用户下载APK时,会全部下载(对用户来说相当的不友好)。
怎么办呢?
abifilters
为我们提供了解决方案,abifilters
为我们提供了选择适配指定CPU架构的能力,只需要在app下的build.gradle
添加如下配置:android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'x86_64' } }
你可能看了上面的这些文字,还不能理解abi的作用,那么我们就用一个简单的例子来说明一下。
2.1 举例说明ABI的作用
首先,我们创建一个最简单的
Hello world
应用,只有一个Activity和一个启动图标。我们看以下打出来的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
包的大小。但是这不是我们想要的,我们只想要适配我们指定的的CPU架构,因此,我们需要在gralde.build中添加abifilters
配置来完成我们想要的效果:android { 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 共支持7种CPU架构,那么,我们在实际项目中该适配哪些CPU架构能保证最好的兼容,同时又最大限度的减少APK的大小?
在回答这个问题之前,我们不妨看一下这些顶级巨头公司,他们是是如何适配的。
3. 目前大厂APP是如何适配不同的CPU架构的?
首先,我们下载一些大厂的APK,看一下他们的适配情况,这里我分析了微信、手机QQ、支付宝和淘宝这4个APP的适配情况:
可以看到,微信适配的是
arm64-v8a
(微信应该是最近才适配到arm64-v8a
,以前是armeabi
),支付宝和手Q适配的是armeabi
,淘宝适配的是armeabi-v7a
。各个APP适配的平台不太一样,但是他们有一个共同点,那就是它们只指定了一个平台。等等,上面这些APP只适配了一中CPU架构,比如只适配了
armeabi-v7a
,那如果APP装在其他架构的手机上,如arm64-v8a
上,会蹦吗?要弄清楚这个问题,我们得先搞清楚,ABI是如何工作的。
ABI是如何工作的呢?
官方文档解释如下:
Android 系统在运行时知道它支持哪些 ABI,因为版本特定的系统属性会指示:
- 设备的主要 ABI,与系统映像本身使用的机器代码对应。
- (可选)与系统映像也支持的其他 ABI 对应的辅助 ABI。
此机制确保系统在安装时从软件包提取最佳机器代码。
为实现最佳性能,应直接针对主要 ABI 进行编译。例如,基于 ARMv5TE 的典型设备只会定义主 ABI:armeabi。相反,基于 ARMv7 的典型设备将主 ABI 定义为 armeabi-v7a,并将辅助 ABI 定义为 armeabi,因为它可以运行为每个 ABI 生成的应用原生二进制文件。
64 位设备也支持其 32 位变体。以 arm64-v8a 设备为例,该设备也可以运行 armeabi 和 armeabi-v7a 代码。但请注意,如果应用以 arm64-v8a 为目标,而非依赖于运行 armeabi-v7a 版应用的设备,则应用在 64 位设备上的性能要好得多。
许多基于 x86 的设备也可运行 armeabi-v7a 和 armeabi NDK 二进制文件。对于这些设备,主 ABI 将是 x86,辅助 ABI 是 armeabi-v7a。
上面这一段是不是有点看蒙了,这里我来简单解释以下。总的来说,就是一个Android设备可以支持多种ABI,设备主ABI和辅助ABI,以
arm64-v8a
为主ABI的设备,辅助ABI为armeabi-v7a
和armeabi
,以armeabi-v7a
为主ABI的设备,辅助ABI为armeabi
。另外,x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现对 arm .so 的兼容,也就是说有适配armeabi平台的APP是可以跑在x86手机上的。
3.1 主辅助ABI具体适配流程
前面说了ABI的工作原理,一个Android设备支持主辅ABI,那么他们具体是如何工作的呢?我们以
arm64-v8a
架构的手机为例:对于一个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会被过滤掉) } }
3.2 Android 7种CPU架构在当前市场的占有率
-
arm64-v8a
: 目前主流版本 -
armeabi-v7a
: 一些老旧的手机 -
x86 / x86_64
: x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现对 arm .so 的兼容,再考虑 x86 1% 以下的市场占有率,x86 相关的两个 .so 也是可以忽略的 -
armeabi/mips / mips64
: NDK 以前支持 ARMv5 (armeabi) 以及 32 位和 64 位 MIPS,但 NDK r17 已不再支持,极少用于手机可以忽。
目前手机市场上,
x86 / x86_64/armeabi/mips / mips6
的架构,基本可以不不考虑了,它们的占有量应很少很少了,arm64-v8a
作为最新一代架构,应该是目前的主流,armeabi-v7a
只存在少部分老旧手机。我试着在Google上查找,具体的市场占有数据,但没找到,但是从国民级应用微信只适配
arm64-v8a
就可以看出,arm64-v8a
是目前的主流,并且还有一点,Google Play 从2019年8月开始,就强制APP适配arm64-v8a
,以慢慢淘汰32位的armeabi-v7a
。4. 我们项目中该如何适配呢?
这里就可以回答前面的两个问题了。
Q1
: 只适配了armeabi-v7a
,那如果APP装在其他架构的手机上,如arm64-v8a
上,会蹦吗?A:
不会,但是反过来会。因为
armeabi-v7a
和arm64-v8a
会向下兼容:- 只适配
armeabi
的APP可以跑在armeabi
,x86
,x86_64
,armeabi-v7a
,arm64-v8
上 - 只适配
armeabi-v7a
可以运行在armeabi-v7a
和arm64-v8a
- 只适配
arm64-v8a
可以运行在arm64-v8a
上
那我们该如何适配呢?给出如下几个方案:
方案一
:只适配armeabi
-
优点:
基本上适配了全部CPU架构(除了淘汰的mips和mips_64) -
缺点:
性能低,相当于在绝大多数手机上都是需要辅助ABI或动态转码来兼容
方案二
:只适配armeabi-v7a
同理方案一,只是又筛掉了一部分老旧设备,在性能和兼容二者中比较平衡
方案三:
只适配arm64-v8
-
优点:
性能最佳 -
缺点:
只能运行在arm64-v8
上,要放弃部分老旧设备用户
这三种方案都是可以的,现在的大厂APP适配中,这三种都有,大部分是前2种方案。具体选哪一种就看自己的考量了,以性能换兼容就
arm64-v8
,以兼容换性能armeabi
,二者稍微平衡一点的就armeabi-v7a
。目前来说,大多数的大厂APP用的都是
armeabi
或armeabi-v7a
,只有像微信这种牛逼的APP,为了追求性能和用户体验,放弃了少部分设备,这也说得通吧,毕竟微信也不在乎苍蝇那点肉。5.番外篇-性能+兼容能否兼得?
其实到上一小节,本文就该结束了,但总感觉优点意犹未尽,除了适配所有全部CPU架构外,就特么不能性能和兼容同时兼得吗?其实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架构,下载不同的包啦!
但是,但是,但是,很遗憾,国内的应用商店目前还不支持!
参考文章
https://www.diycode.cc/topics/691
https://developer.android.com/ndk/guides/abis
https://android.jlelse.eu/controlling-apk-size-when-using-native-libraries-45c6c0e5b70a
更多相关内容 -
armeabi-v7a架构下的ffmpeg动态包
2021-08-03 13:34:24armeabi-v7a架构下的ffmpeg动态包 -
plugin-flutter-armeabi:颤动的armeabi兼容
2021-02-04 03:18:08Flutter Armeabi Gradle插件 buildscript { repositories { jcenter() } dependencies { classpath "io.github.lizhangqu:plugin-flutter-armeabi:1.0.4" } } apply plugin: 'flutter.armeabi' 变更记录 1.0.4... -
armeabi-v7a.zip
2020-09-11 15:33:08ijkplayer的armeabi-v7a包,包括libijkffmpeg.so、libijkplayer.so、libijksdl.so包 -
ERROR:ABIs [armeabi] are not supported for platform.Supported ABIs are [arm64-v8a, armeabi-v7的完美...
2021-03-05 15:17:00ERROR:ABIs [armeabi] are not supported for platform.Supported ABIs are [arm64-v8a, armeabi-v7的完美解决方案-附件资源 -
armeabi-v7a.rar
2020-04-18 17:54:18armeabi-v7a压缩包,用于安卓插件开发armeabi-v7a压缩包, -
autojs4.1.1打包插件 armeabi-v7a-4.1.1 Alpha2-release(打包插件)
2022-03-07 17:43:04autojs打包插件 armeabi-v7a-4.1.1 Alpha2-release(打包插件) -
armeabi.zip
2020-05-31 16:02:49android低版本串口so文件,libserial_port.so -
libijkplayer编译后armeabi-v7a、armeabi、arm64-v8a的so文件
2021-10-14 16:46:40包含libijkffmpeg.so、libijkplayer.so、libijksdl.so -
yuce-1.0-armeabi-v7a-debug.apk
2021-07-12 09:13:45根据pubmed发表的国内慢性疾病列线图转化成的慢性疾病预测的计算器, 代表了网页计算题替代列线图的一个方向。建议作为体验用,用到实际工作中请进行详细的验证。 -
libmupdf_java.so-1.9a-适用于armeabi-v7a架构
2021-03-18 11:21:14mupdf1.9a android-armeabi-v7a版本,重新编译源码,修改默认红色画笔批注为黑色批注,修改画笔粗细,亲测可用 -
友盟推送so库文件,arm64-v8a,armeabi-v7a架构
2020-01-16 14:34:07友盟推送,最新的lib库,6.0.1版本,so平台为arm64-v8a,armeabi-v7a 架构,上google 必须两个架构。项目已上线,亲测可用 -
wxa_resource_003129ae3ee99b6300c1a1d85f207dc7_armeabi-v7a.zip
2022-01-18 19:36:54wxa_resource_003129ae3ee99b6300c1a1d85f207dc7_armeabi-v7a.zip -
kodi-18.4-Leia-armeabi-v7a.apk
2019-09-04 09:37:14适于于android系统。其中又分为arm(arm是32位cpu,arm64是64位cpu)和x86两种类型架构。一般的手机、机顶盒都是arm架构,选择arm.apk下载即可。这个是安卓32位的版本,2019年9月3号更新版。 -
ZBAR SO包 -arm64-v8a,armeabi,armeabi-v7a,mips,mips64,x86,x86_64最完整版
2019-05-20 09:29:09包含arm64-v8a,armeabi,armeabi-v7a,mips,mips64,x86,x86_64 提供了最完整的SO包下载 Android 调用 Zbar 进行二维码扫描或条形码扫描的 Demo 之前尝试使用Zxing来进行扫描,无奈Zxing速度太慢,而且对扫描的二维码或... -
autojs免root脚本2019最新免费版4.1.1a Alpha2-armeabi-v7a-release和打包插件
2019-12-20 01:52:19autojs免root脚本2019最新免费版4.1.1a Alpha2-armeabi-v7a-release和打包插件 -
kodi-18.0-Leia-armeabi-v7a万能播放器
2019-03-29 13:44:43超好用的安卓播放器, 功能强大, 可播放网络视频文件. 更多功能自己百度. -
openssl armeabi-v7a 静态库
2018-12-04 11:29:25openssl的android v7a版本 静态库,支持openssl各种功能 -
sqlite3,支持arm64-v8a,armeabi-v7a,armeabi
2018-11-26 12:10:31sqlite3支持所有机型,支持arm64-v8a,armeabi-v7a,armeabi -
libcurl.a静态库(.a文件) android可用,包含armeabi、x86、arm64-v8a、x86_64
2019-07-11 11:51:25libcurl.a静态库(.a文件) android可用,包含(armeabi、x86、arm64-v8a、x86_64) 基于 curl-7.61.1编译 -
FFmpeg 编译出的 音频专用 so 库(arm64-v8a,armeabi-v7a平台)
2020-10-17 23:28:52导入项目直接使用, 如果您想自己编译so文件,并在编译过程中遇到了问题 请留言。QQ:287651776 -
Android可用的openssl相关so,支持x86 armeabi armeabi-v7a
2018-07-13 11:19:11编译好的openssl,支持x86、armeabi、armeabi-v7a三个平台。 可直接用于libcurl的集成,也可单独集成使用 -
armeabi-v7a与armeabi的区别
2017-02-24 09:44:56armeabi-v7a与armeabi的区别 -
VLC-Android-3.2.9-armeabi-v7a.apk
2020-06-10 22:54:28虽然你可以在vlc官网下载,但是下载速度和让人崩溃。这里有现成的vlc for android 最新版本 armv7-a,是目前最好的视频播放器和测试工具 -
com.brave.browser_1.31.91-413109110_minAPI21(armeabi-v7a)(nodpi)
2022-03-16 10:36:03brave浏览器,适用于安卓5+ -
ijkplayer-anddroid编译好的so库 arm64-v8a、armeabi、armeabi-v7a、x86、x86_64
2016-09-20 11:36:17ijkplayer-anddroid编译好的arm64-v8a、armeabi、armeabi-v7a、x86、x86_64 下的so库,包括libijkffmpeg.so,libijkplayer.so,libijksdl.so -
zbar以编译生成的so(包含64位)(amr64-v8a,armeabi,armeabi-v7a,x86)
2017-03-10 16:00:03从github上取下zbar源码,以编译生成的so文件,包含(amr64-v8a,armeabi,armeabi-v7a,x86)四个版本 -
jniLibs-armeabi-v7a.7z
2020-02-25 14:19:36ijkplayer编译好的支持https和解决播放mp4时只有声音没有图像的问题的so包,基于0.8.8版本。 介绍说明:https://blog.csdn.net/nnmmbb/article/details/98173936