背景
第一次开发Native项目,需要在AndroidStudio(简称AS)上配置好NDK。
环境配置:AGP版本=3.4.2,compileSdkVersion=28,targetVersion=26,NDK版本=22.0.7026061,cmake.abiFilters="armeabi-v7a", "arm64-v8a"。
问题
- 在下载并配置好NDK后(下载:AS的SDK Tools),sync报错NullPointerException,但它其实是“NDK is missing a "platforms" directory.”引发的,只是这一句日志被标记为警告。
- 基于问题一,在网上找了个解决方式(我信了它的邪😭),在ndk目录下新建一个“platforms”的文件夹就可以sync过了。但是接下来又编译报错“Error:ABIs [arm64-v8a] are not supported for platform. Supported ABIs are [armeabi-v7a, x86]”。
解决过程
问题二的难点在于造成类似“ABIs [arm64-v8a] are not supported for platform”的原因有好几种,我归纳总结为:
- NDK升级到17之后,不再支持armeabi架构。因此NDK升级是有可能造成类似 “ABIs [xxx] are not supported for platform”的错误;
- compileSdkVersion设置为19,也会导致这个问题,因为版本号为19的系统只能是32位的;
- 对自己和对NDK的不信任:对自己不信任是因为对编译的东西还不够熟悉,总觉得自己哪里配置出错了;对NDK不信任是觉得从官网下载的NDK为什么会出现“问题一”呢?是不是NDK下载错了,或者xxxxx的原因。
解决方式
根据问题二报出的堆栈信息,进行断点调试:
adb命令:./gradlew :[module]:installDebug -Dorg.gradle.debug=true --no-daemon 。(AGP7以上需要自己处理下Java11兼容问题)
关键断点行:NdkHandler -> supports64Bits() (找不到这个类的同学在app的build.gradle下implementation一下AGP,例如 "com.android.tools.build:gradle:3.4.2")。
问题原因:编译时Gradle会判断这次编译实际支持的abi,当在build.gradle下配置的abi不属于实际支持的abi时,就会报错“ABIs [arm64-v8a] are not supported for platform...”。
实际支持的abi的判断方式为:
判断前提:gradle认为highestVersion>=20时,abi能够支持到64位,反之只能支持32位
//root 即配置NDK路径
File platformDir = new File(root, "/platforms");
File[] platformSubDirs = platformDir.listFiles(File::isDirectory);
int highestVersion = 0;
assert platformSubDirs != null;
for (File platform : platformSubDirs) {
if (platform.getName().startsWith("android-")) {
try {
int version = Integer.parseInt(
platform.getName().substring("android-".length()));
//targetVersion即在build.gradle下配置的目标版本 此次为26
if (version > highestVersion && version < targetVersion) {
highestVersion = version;
}
} catch (NumberFormatException ignore) {
// Ignore unrecognized directories.
}
}
}
return highestVersion;
根据上面的代码举个例子:
targetVersion=26, “platforms”目录下有三个文件夹“android-19”、“android-25”、“android-31”时,经过上面的程序后,会输出highestVersion=25。而根据前提,25>=20,就认为arm64可用。
解决方式:在NDK目录下新建了“platforms”文件夹,并在“platforms”文件夹下新建了“android-19”、"android-21"、"android-31"三个文件夹。
补充
应该有同学会疑问新建空文件夹会不会导致其它的问题产生?实际上并不会。“/platforms/android-N”这个路径实际上在SDK的目录下就有,它的含义为第N个版本的android sdk文件。我猜想在这里Gradle只是想知道当前允许编译哪几个版本的apk,而哪个版本的sdk文件夹存在就认为能够编译对应版本的apk。如果只能编译版本号20以下的apk,那么就可以认为当前并不支持arm64。而判断方式则是根据一个固定路径下的文件名称去判断(无语...)。
为了进一步验证上面的猜想,我新建了一个基于AGP7.0的Cpp项目。这次不用新建“/platforms"文件夹也能编译过去了,因为在AGP7.0上supports64Bits的判断条件为 compileSdkVersion >= AndroidVersion.SUPPORTS_64_BIT (build.gradle下配置的compileSdkVersion)。显然Google也认为在AGP3.4.2上的supports64Bits判断方式并不稳妥。