本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
1、ABI是什么
ABI全称为 Application Binary Interface,即应用二进制接口,是应用程序和操作系统在二进制级别交互的一种接口标准。
不同的Android设备使用不同的CPU,而不同的CPU支持不同的指令集。CPU与指令集的每种组合都有专属的应用二进制接口(ABI)。
常见的 ABI 类型:
在Android生态系统中,x86架构(Intel)的非常少见,常用于模拟器或是早期的“古董”设备,主流架构是ARM。
而在ARM架构中,又以64位架构(arm64-v8a)为主流,由Android 5.0开始支持,相比于32位架构(armeabi-v7a),寄存器和指令集的增强,显著提高了执行速度和运算性能,同时还能兼容32位架构。
2、分包是什么
虽然ARM 64位架构是Android生态系统中的主流设备,但还是有一些老的设备是32位架构,而为了更好的用户体验,这部分存量设备用户也要兼容到,这也是今天要介绍的内容——分包。
分包是指由传统的构建一个APK文件变为根据架构来构建出多个APK文件,通常是arm64-v8a的64位包、armeabi-v7a的32位包、以及arm64-v8a加armeabi-v7a的合包(整包) 。
3、分包的优点
除了应用市场要求支持64位架构适配之外,对于应用来说,分包也有如下优点:
- 减少APK大小:每个分包的APK只包含对应 ABI 的共享库(.so文件),减少了APK的大小;
- 提高安装速度:用户设备只会下载和安装与其架构匹配的APK,可以提高下载和安装的速度,并节省下载资源;
- 提升性能:64位架构相比于32位架构,在运算能力和执行速度上有显著提升;
4、分包前
以测试项目GradleX为例,分包前ndk中配置为arm64-v8a和armeabi-v7a:
ndk {
abiFilters.addAll(mutableSetOf("arm64-v8a", "armeabi-v7a"))
}
测试依赖了阿里云音视频SDK:
implementation("com.aliyun.aio:AliVCSDK_Premium:6.4.0")
其中包含多个共享库文件(.so)。
然后执行打包:
./gradlew assembleDebug
打包完成后,在build/outputs/apk/debug/目录中双击APK文件,进入APK文件分析器窗口。
如下图:
在lib目录下可以找到不同架构的so文件,这里包含了arm64-v8a和armeabi-v7a两种类型,即为前面介绍的合包,合包的APK大小要比单架构的APK大很多。
5、分包实现
实现分包的方式有两种:多渠道配置和splits配置。
5.1、多渠道
5.1.1、简介
在上一章中我们介绍了多渠道打包相关知识,多渠道配置如下:
android {
defaultConfig {
//...
ndk { ... }
}
// 多渠道打包配置
flavorDimensions += listOf("version")
productFlavors {
create("huawei") {
dimension = "version"
applicationIdSuffix = ".huawei"
versionNameSuffix = "-huawei"
versionCode = 1
buildConfigField("int", "CHANNEL_CODE", "1001")
}
create("oppo") {
dimension = "version"
applicationIdSuffix = ".oppo"
versionNameSuffix = "-oppo"
versionCode = 1
buildConfigField("int", "CHANNEL_CODE", "1002")
}
}
}
在productFlavors{ }中,我们可以对defaultConfig{ }中的配置进行覆写和追加,所以我们也可以在多渠道配置中对ndk{ }属性进行配置。
5.1.2、配置
配置如下:
flavorDimensions += listOf("version")
productFlavors {
create("huawei-armeabi-v7a") {
dimension = "version"
ndk {
abiFilters.addAll(mutableSetOf("armeabi-v7a"))
}
}
create("huawei-arm64-v8a") {
dimension = "version"
ndk {
abiFilters.addAll(mutableSetOf("arm64-v8a"))
}
}
create("huawei-all") {
dimension = "version"
ndk {
abiFilters.addAll(mutableSetOf("arm64-v8a", "armeabi-v7a"))
}
}
}
配置三个渠道,分别是:
- huawei-armeabi-v7a 渠道,只包含32架构armeabi-v7a;
- huawei-arm64-v8a 渠道,只包含64架构arm64-v8a;
- huawei-all 渠道,则是合包,包含32位架构armeabi-v7a和64位架构arm64-v8a;
这里需要注意的是,如果defaultConfig{ }中已经有ndk配置了,要先defaultConfig{ }中的ndk配置进行注释或移除。
android {
defaultConfig {
ndk {
//abiFilters.addAll(mutableSetOf("arm64-v8a", "armeabi-v7a"))
}
}
}
在上一章介绍到DefaultConfig和ProductFlavor都继承于同一个父类接口BaseFlavor,所以只要是BaseFlavor中包含的属性配置,渠道配置productFlavors{ }都可以对defaultConfig{ }中的配置进行覆写或追加。
然而ndk{ }配置并不属于BaseFlavor中的属性,而是在VariantDimension接口类中,所以defaultConfig{ }中的ndk配置依然会应用于所有构建变体,为了使productFlavors{ }中的ndk{ }配置生效,需要先注释或移除defaultConfig{ }中的ndk{ }配置。
5.1.3、效果
多渠道配置完成后,重新Sync让配置生效。
然后在Gradle面板中双击执行assembleDebug看看打包后的产物是否符合分包预期。
打包完成后如下图:
此时,在/app/build/outputs/apk/下有三个目录:
- huawei-all
- huawei-arm64-v8a
- huawei-armeabi-v7a
这三个目录就是我们定义的渠道名称,现在再双击huawei-arm64-v8a/debug/目录下的app-huawei-arm64-v8a-debug.apk文件,进入APK文件分析器窗口。
如下图:
可以看到在app-huawei-arm64-v8a-debug.apk文件中,lib目录下只有arm64-v8a类型的.so文件,这就表示我们通过多渠道配置的方式实现了ABI 分包的功能,相比于合包,APK的大小也有明显减小。
5.2、splits
5.2.1、简介
虽然说上面我们通过多渠道配置的方式实现了ABI 分包的功能,但其实官方也提供了正统的分包能力,就是splits。
splits是分裂/分开的意思,支持屏幕密度(Density)和应用二进制接口(ABI)两种方式构建多个APK,所以我们可以通过splits来做 ABI 分包,即根据不同的架构生成不同的APK文件。
5.2.2、配置
在app>build.gradle文件中配置如下:
android {
// ...
// ABI 分包
splits {
abi {
isEnable = true
reset()
include("arm64-v8a", "armeabi-v7a")
isUniversalApk = true
}
}
}
abi{ }闭包中的属性配置介绍:
- isEnable,是否开启 ABI 分包,true表示开启,默认关;
- reset(),清除默认的 ABI 列表,要与include属性结合使用,毕竟不能没有ABI 配置,清除了要再添加;
- include(),指定生成哪些APK的 ABI 列表,多个由逗号分割;
- isUniversalApk,除了按照include配置的 ABI 列表生成多个APK之外,是否生成一个包含所有 ABI 列表的合包,true表示开启,默认关;
在使用splits来配置 abi { }时,同样需要注释或移除defaultConfig{ }中的ndk { } 配置,否则会有冲突。
> com.android.builder.errors.EvalIssueException: Conflicting configuration : 'armeabi-v7a,arm64-v8a' in ndk abiFilters cannot be present when splits abi filters are set : arm64-v8a
5.2.3、效果
配置完成之后,重新Sync。
然后在Gradle面板中双击执行assembleDebug打包,或执行Gradle命令行打包:
./gradlew assembleDebug
打包完成后,会在build/outputs/apk/debug/目录中找到每个 ABI 单独的APK文件。
如下图:
有三个APK文件:
- app-arm64-v8a-debug.apk:64位包;
- app-armeabi-v7a-debug.apk:32位包;
- app-universal-debug.apk:合包;
如果是直接在Android Studio中点击Run的情况下,属于debug编译环境,会根据手机设备的架构类型来选择一种 ABI 进行编译构建,比如手机设备的架构是arm64-v8a,则默认选择64位架构来编译构建。
到此,基本效果已经完成了。
可以看到,splits配置的方式要比多渠道配置的方式更简单,也更灵活一些。
5.2.4、进阶
场景一:本地开发时,编译构建debug包的时候不进行 ABI 分包,使用合包进行开发验证,可以简化调试和缩短编译时长。
前面介绍到isEnable属性表示是否开启 ABI 分包,所以我们可以增加一个项目属性作为开关,如果是debug编译则不进行分包,release编译再分包。
配置如下:
val isRelease = project.hasProperty("isRelease") && project.property("isRelease") == "true"
// ABI 分包
splits {
abi {
isEnable = isRelease
reset()
include("arm64-v8a", "armeabi-v7a")
isUniversalApk = true
}
}
通过变量isRelease来控制是否分包,变量isRelease为项目属性。
如果是release构建且需要分包,则需要在打包命令中加上isRelease参数。
执行命令如下:
./gradlew assembleRelease -PisRelease=true
其实也可以根据构建任务(Task)的名字来判断是否需要开启 ABI 分包,但release构建的时候可能还是有依赖debug之类的组件,所以release构建环境可能并不能作为准确条件,而且项目属性也能灵活一些,也可以作为一个公共属性全局复用。
场景二:在不涉及 ABI 兼容性测试的情况下,日常测试只构建主流的arm64-v8a包,缩短编译时长提升开发效率。
结合场景一的经验,我们可以增加一个项目属性来控制splits.abi.include属性的值。
配置如下:
val isRelease = project.hasProperty("isRelease") && project.property("isRelease") == "true"
val onlyArm64 = project.hasProperty("onlyArm64") && project.property("onlyArm64") == "true"
// ABI 分包
splits {
abi {
isEnable = isRelease
reset()
if (onlyArm64) {
include("arm64-v8a")
isUniversalApk = false
} else {
include("arm64-v8a", "armeabi-v7a")
isUniversalApk = true
}
}
}
项目属性onlyArm64为true表示只构建64位包(arm64-v8a),其他情况则分别构建32/64位包以及合包。
打包命令中加上onlyArm64参数:
./gradlew assembleRelease -PisRelease=true -PonlyArm64=true
这里需要注意的是,只构建64位包的情况下,isUniversalApk属性要设置为false,虽然 ABI 类型只有arm64-v8a,但是如果isUniversalApk属性为true的话,还是会构建一个合包,相当于重复构建了,所以需要设置为false。
除了用项目属性onlyArm64来实现之外,include()参数的值也可以用外部List来替换,是否构建合包的isUniversalApk属性根据List的size来判断。
示例:
val abiFilters = listOf("arm64-v8a")
// ABI 分包
splits {
abi {
isEnable = isRelease
reset()
include(*abiFilters.toTypedArray())
isUniversalApk = abiFilters.size > 1
}
}
这个list可以作为项目属性,也可以定义在local.properties文件中。
6、注意
6.1、适配
分包本身并不难,但是要确保项目中的依赖是支持32/64位共享库的,这可能需要升级依赖库以及重新导入so文件到src/main/jniLibs/目录下,并且检查打包后的APK文件是否都包含所需要的so文件。
6.2、兼容
特别是在第一次进行分包之后,在上传应用市场之前,要分别测试APK在不同架构的设备上能否正常运行。
7、总结
本章主要是Android ABI 分包的知识点,并分别介绍了两种实现方式,推荐使用splits能力来进行分包。
ABI 分包不仅满足应用市场对于64位架构适配的要求,还能显著减小APK大小,提升用户体验。