接上篇Android中常见SDK类型区别-CSDN博客
一些重要的加密算法或者核心协议一般都在C中编写,然后给java调用。这样可以避免反编译后查看到应用的源码。此时就需要了解一下NDK中的ABI(Application Binary Interface的缩写,也就是应用二进制接口)。
ABI现状及原理分析
Android 目前共支持7种CPU架构:
一类:mips, mips64, X86, X86–64, armeabi, 二类:armeabi-v7a,arm64-v8a,
基于平台:
平台 | 32位库文件夹 | 64位库文件夹 |
ARM | lib/armeabi-v7a | lib/arm64-v8a |
X86 | lib/x86 | lib/x86-64 |
市场占有率上,一类架构基本可以不不考虑了,它们的占有量应很少很少了,arm64-v8a作为最新一代架构,应该是目前的主流,armeabi-v7a只存在少部分老旧手机。
根据参考资料:微信适配的是arm64-v8a(部分渠道下载的apk可能适配的是armeabi-v7a,不做过多纠结,有可能采用的是动态下发的方案,具体不详,以前是armeabi),支付宝和手Q适配的是armeabi,淘宝适配的是armeabi-v7a。各个APP适配的平台不太一样,但是他们有一个共同点,那就是它们只指定了一个平台。
一个Android设备可以支持多种ABI(设备主ABI和辅助ABI)
- 以arm64-v8a为主ABI的设备,辅助ABI为armeabi-v7a和armeabi;
- 以armeabi-v7a为主ABI的设备,辅助ABI为armeabi。
另外,x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现对 arm .so 的兼容,也就是说有适配armeabi平台的APP是可以跑在x86手机上的。
ABI工作原理
不同Android设备,使用的CPU架构可能不同,因此支持不同的指令集。 CPU 与指令集的每种组合都有其自己的应用二进制界面(或 ABI),ABI非常精确地定义了应用程序的机器代码应如何在运行时与系统交互。您必须为要与您的应用程序一起使用的每种CPU架构指定一个ABI(Application Binary Interface),一个APK可以同时包含32位(例如armeabi-v7a、x86)和64位例如arm64-v8a、x86_64)的二进制版本,以便在各种设备上提供最佳性能
ABI 包含以下信息:
- 可使用的 CPU 指令集(和扩展指令集)。
- 运行时内存存储和加载的字节顺序。Android 始终是 little-endian。
- 在应用和系统之间传递数据的规范(包括对齐限制),以及系统调用函数时如何使用堆栈和寄存器。
- 可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。Android 始终使用 ELF。
- 如何重整 C++ 名称。
如何判断APK的架构
- 使用APK分析工具:Android Studio 提供了一个内置工具叫做APK Analyzer,它可以帮助你查看APK的内容,包括其支持的架构。打开Android Studio,选择“Build” > “Analyze APK…”,然后选择你的APK文件进行分析。
- 手动检查APK文件:更改APK文件扩展名为.zip,然后解压缩它。在解压缩的文件夹中,导航到lib目录。查看lib目录下的子目录名称,这些名称代表了APK支持的架构。例如,arm64-v8a表示64位ARM架构,而armeabi-v7a表示32位ARM架构。
- 使用命令行工具:通过aapt工具来查看APK文件的信息,包括其支持的架构。
aapt dump badging your_app.apk | grep native-code
这条命令会输出APK支持的架构信息。aapt工具包含在Android SDK的Build-tools中。
- 查看zygote和zygote64的进程号
//查看zygote和zygote64的进程号
adb shell ps -A|grep zygote
//查看app的进程号,new表示app packagename中的内容
adb shell ps -A|grep new
new进程24351是进程号772的进程创建的,772的进程是64位的(773进程时32位)。所以new是运行在64位系统中
简单做一个so加载流程时序图以arm64-v8a架构的手机为例:
特别需要注意的情况是在命中了文件夹,而未命中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了哦。
我们该如何适配?
明确一个基本原则,abi是向下兼容的,即:
- 只适配armeabi的APP可以跑在armeabi,x86,x86_64,armeabi-v7a,arm64-v8上
- 只适配armeabi-v7a可以运行在armeabi-v7a和arm64-v8a
- 只适配arm64-v8a 可以运行在arm64-v8a上
因此我们有如下适配方案:
-
只适配armeabi:
优点:基本可以适配所有手机机型,除了淘汰的mips和mips_64
缺点:在大多数手机上都需要利用辅助ABI或者动态转码来兼容,性能较差 -
只适配 armeabi-v7a
只是又筛掉了一部分老旧设备,在性能和兼容二者中比较平衡 -
只适配 arm64-v8
优点:性能最佳(微信大哥采用的)
缺点:只能运行在arm64-v8上,要放弃部分老旧设备用户
上述方案只是我们给出的建议,具体还要看实际的需求和考量:以性能换兼容就arm64-v8,以兼容换性能armeabi,二者稍微平衡一点的就armeabi-v7a。
能否做到性能+兼容都要
上述方案多多少少有一些取舍的,有没有完美的方案,既不放弃性能,也能完全兼容。答案是肯定的,Google已经安排上了,通过abi split,分包,为每一个CUP架构打一个apk,该apk 中就只包含一个平台。 google play支持上传多个apk,但是,据我所知,目前国内的应用商店是不支持的。
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
}
}
打包配置
split分包
这个命令可以按照各种规则去分包,比如按照abi,屏幕密度(即ldpi,hdpi等)分包
splits {
abi {
enable true
reset()
include 'x86','armabi'//包含
exclude 'armeabi', 'armeabi-v7a', "arm64-v8a"//不包含
universalApk true
}
}
dk{abiFilters:}过滤
这个指令可以配置只打包你配置的so库,没有配置的就不打包,很灵活。 第三方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 配置,来避免一些未知的错误
//过滤x86的so库
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'//指定ndk需要兼容的ABI(这样其他依赖包里x86,armeabi之类的so会被过滤掉)
}
这样配置会将armeabi-v7a(32位),arm64-v8a(64位)这2个包下的so库都打包到一个apk,而不像splits会每一个包打一个apk.