概要
Android NDK开发对于初学者来说,往往会有丈二和尚摸不着头脑之感,所以在此处理清一些基础知识,总体来说有以下几个方面:
- ABI概念的理解。
- 使用C++的两种方式(Android Studio)。
- Gradle配置.so文件的格式。
- 使用.so的注意事项。
ABI概念的理解
到目前为止,Android系统总共支持7种不同的CPU架构,分别是:
- ARMv5
- ARMv7
- x86
- MIPS
- ARMV8
- MIPS6
- x86_64
每一种CPU架构对应一种ABI。所谓ABI(Application Binary Interface),即应用程序二进制接口。它的作用是定义二进制文件(.so文件,也叫动态链接库)怎样运行在不同架构的系统上。在Android Studio的目录结构中,它的表现形式如下:
很多手机设备支持不止一种ABI,但为了更好的用户体验和更好的性能,最好针对不同CPU架构提供专门的.so文件,即将不同的.so文件放入jniLibs对应的子目录中,在Android包管理器安装apk时,系统会自动选择对应的ABI预编译好的.so文件。
使用C++的两种方式
- 引入预编译的二进制C++函数库(.so文件)
- 直接从C++源码编译
1、引入预编译的二进制C++函数库
这种引入方式比较简单,只需要将.so文件放入到上面对应的ABI目录中,假设每个目录中有一个名为libhello.so文件,那么在代码中可以这样使用这个库:
static {
System.loadLibrary("hello");
}
注意:文件名和使用时名字的差别;如果该.so文件来自于第三方,需要拿到对应的native方法声明类,并且将该类放到原始的包下面,否则会报找不到方法实现错误。
2、直接从C++源码编译
在Android Studio中对C++源码编译生成.so文件的过程如下:
1、 配置ndk.dir变量
打开工程根目录下的local.properties文件,在其中配置ndk目录,如下是我的环境自动生成的配置路径:
ndk.dir=D\:\\Android\\sdk\\ndk-bundle
sdk.dir=D\:\\Android\\sdk
2、 在Gradle中配置NDK模块
为了让gradle对C/C++代码进行编译,需要配置module的build.gradle文件,如下:
3、添加C/C++文件到指定目录
默认情况下,Gradle会到/src/main/jni目录查找C/C++文件进行编译,所以只需将C++文件放入该目录即可,当然可以在gradle中修改该默认选项,在build.gradle中,android项目下做如下设置
sourceSets.main{
jni.srcDirs 'src/main/source'
}
使用.so文件的注意事项
- .so文件版本兼容性问题
NDK平台是向前兼容的,所以不能将高版本的.so文件运行在低版本的设备上,当引入一个预编译的.so文件时,要检查它被编译所用的平台版本,对应App的minSdkVersion。 - 混合使用不同的C++运行时编译的.so文件
当只有一个.so文件时,C++运行时没有问题,但存在多个.so文件时,应该让所有.so文件都动态链接相同的C++运行时,否则可能会发生很奇怪的Crash。 - 应该为每一个CPU架构提供专门的.so文件
- 只提供armeabi架构的.so文件而忽略其它ABIs
所有设备都支持armeabi架构的.so文件,因此可以将armeabi架构的.so文件代替所有的CPU架构的.so文件,但是这将大大降低函数性能和兼容性,为了解决这一问题和减少APK包的大小,可以生成不同ABI版本的APK,可以做如下配置:
android {
//....
splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs
universalApk true //generate an additional APK than contains all ABIs
}
}
project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI),
0) * 1000000 + android.defaultConfig.versionCode
}
}
}