文章首发于公众号
「技术最 TOP」
,每天都有干货文章继续更新,能够微信搜寻「技术最 TOP」
第一工夫浏览!
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 适配的是armwabi
,淘宝适配的是armwabi-v7a
。各个 APP 适配的平台不太一样,然而他们有一个共同点,那就是它们只指定了一个平台。
等等,下面这些 APP 只适配了一中 CPU 架构,比方只适配了 armwabi-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
:只适配了 armwabi-v7a
, 那如果 APP 装在其余架构的手机上,如arm64-v8a
上,会蹦吗?
A:
不会,然而反过来会。
因为 armwabi-v7a
和arm64-v8a
会向下兼容:
- 只适配
armeabi
的 APP 能够跑在armeabi
,x86
,x86_64
,armwabi-v7a
,arm64-v8
上 - 只适配
armwabi-v7a
能够运行在armwabi-v7a
和arm64-v8a
- 只适配
arm64-v8a
能够运行在arm64-v8a
上
那咱们该如何适配呢?给出如下几个计划:
计划一
:只适配armeabi
长处:
基本上适配了全副 CPU 架构(除了淘汰的 mips 和 mips_64)毛病:
性能低,相当于在绝大多数手机上都是须要辅助 ABI 或动静转码来兼容
计划二
:只适配 armwabi-v7a
同理计划一,只是又筛掉了一部分老旧设施, 在性能和兼容二者中比拟均衡
计划三:
只适配 arm64-v8
长处:
性能最佳毛病:
只能运行在arm64-v8
上,要放弃局部老旧设施用户
这三种计划都是能够的,当初的大厂 APP 适配中,这三种都有,大部分是前 2 种计划。具体选哪一种就看本人的考量了,以性能换兼容就arm64-v8
, 以兼容换性能armeabi
, 二者略微均衡一点的就armwabi-v7a
。
目前来说,大多数的大厂 APP 用的都是 armeabi
或armwabi-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…
- https://developer.android.com…
- https://android.jlelse.eu/con…
以上就是本文的全部内容,如有谬误,欢送评论区指出。原创不易,如果你喜爱本文,欢送点赞、转发、珍藏三连一下。