名词解释
JNI(Java Native Interface):
Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口(也是一个标准)。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了jni专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。
NDK(Native Development Kit) :
原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。
Android.mk/Application.mk
NDK 工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具,而 Android.mk 和 Application.mk 你可以认为是描述编译参数和一些配置的文件。比如指定使用c++11还是c++14编译,会引用哪些共享库,并描述关系等,还会指定编译的 abi。只有有了这些 NDK 中的编译工具才能准确的编译 c/c++ 代码。
ndk-build
ndk-build 文件是 Android NDK r4 中引入的一个 shell 脚本。其用途是调用正确的 NDK 构建脚本。其实最终还是会去调用 NDK 自己的编译工具。
CMake
那 CMake 又是什么呢。脱离 Android 开发来看,c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用 makefile 文件编译,Windows 下会使用 project 文件编译。而 CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译。
在Android Studio 2.2 之后,工具中增加了 CMake 的支持,你可以这么认为,在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是 ndk-build + Android.mk + Application.mk 组合,另一个是 CMake + CMakeLists.txt 组合。这2个组合与Android代码和c/c++代码无关,只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。(也是Android现在主推的)
ABI
ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。
armeabi 设备只兼容 armeabi;
armeabi-v7a设备兼容armeabi-v7a、armeabi;
arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
X86设备兼容X86、armeabi;
X86_64设备兼容X86_64、X86、armeabi;
mips64设备兼容mips64、mips;
mips只兼容mips;
当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库。(库越多,包越大,所以要有选择)
CMake 使用
新建项目,勾选Include C++ support
项目创建好以后我们可以看到和普通Android项目有以下4个不同。
1.main 下面增加了 cpp 目录,即放置 c/c++ 代码的地方
2.app 的 build.gradle 有修改
3.增加了 CMakeLists.txt 文件
4.多了一个 .externalNativeBuild 目录,在 执行sync 的时候。执行一次后会生成 makefile 的文件缓存之类的东西放在externalNativeBuild 中。
build.gradle
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions -std=c++14"//指定 C++标准
arguments "-DANDROID_ARM_NEON=TRUE"
}
}
}
buildTypes {
...
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
...
由于 CMake 的命令集成在了 gradle - externalNativeBuild 中,所以在 gradle 中有2个地方配置 CMake。
defaultConfig外面的 externalNativeBuild - cmake,指明了 CMakeList.txt 的路径;
defaultConfig 里面的 externalNativeBuild - cmake,主要填写 CMake 的命令参数。即由 arguments 中的参数最后转化成一个可执行的 CMake 的命令,可以在 .externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt 中查到。
CMakeLists文件解析
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
1.cmake_minimum_required(VERSION 3.4.1)
用来设置在编译本地库时我们需要的最小的cmake版本,AndroidStudio自动生成,我们几乎不需要自己管。
2.add_library
add_library用来设置编译生成的本地库的名字为native-lib,SHARED表示编译生成的是动态链接库(这个概念前面已经提到过了),src/main/cpp/native-lib.cpp表示参与编译的文件的路径,这里面可以写多个文件的路径。
3.target_link_libraries
target_link_libraries 是为了关联我们自己的库和一些第三方库或者系统库,这里把我们把自己的库native-lib库和log库关联起来。
常用操作
添加多个参与编译的C/C++文件
add_library( HelloNDK
SHARED
src/main/cpp/HelloNDK.c
src/main/cpp/HelloJNI.c)
或者:
file(GLOB helloworld_SRC "src/main/cpp/*.cpp")
add_library(testlala SHARED ${helloworld_SRC})
要注意的是,你在写路径的时候一定要注意当前的CMakeLists.txt在项目中的位置,上面的路径是相对于CMakeLists.txt 写的
我们想编译出多个so库
先放上我的项目文件夹结构图:
one文件夹内的CMakeLists.txt文件的内容:
ADD_LIBRARY(one-lib SHARED one-lib.c)
target_link_libraries(one-lib log)
two文件夹内的CMakeLists.txt文件的内容:
ADD_LIBRARY(two-lib SHARED two-lib.c)
target_link_libraries(two-lib log)
app目录下的CMakeLists.txt文件的内容:
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
add_library( HelloNDK
SHARED
src/main/cpp/HelloNDK.c
src/main/cpp/HelloJNI.c)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries(HelloNDK log)
ADD_SUBDIRECTORY(src/main/cpp/one)
ADD_SUBDIRECTORY(src/main/cpp/two)
CMakeLists.txt 文件的配置是支持继承的,所以我们在子配置文件中只是写了不同的特殊配置项的配置,最后在最上层的文件中配置子配置文件的路径即可
更改动态链接库生成的目录
上面的so库的路径太深了,不好找,没事,可以配置,我们只需要在顶层的CMakeLists.txt文件中加入下面这句就可以了
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
然后我们就可以在app/src/main下看到jniLibs目录,在其中看到我们的动态链接库的文件夹和文件(这里直接配置到了系统默认的路径,如果配置到其他路径需要在gradle文件中使用jinLibs.srcDirs = ['newDir']进行指定)。
调试 CMakeLists.txt 中的代码
使用 message 方法
cmake_minimum_required(VERSION 3.4.1)
message(STATUS "execute CMakeLists")
...
然后再在.externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt 中查看 log。
资料文献
CMake手册
https://www.zybuluo.com/khan-lau/note/254724)。
Android NDK 官方文档
https://link.jianshu.com/t=https://developer.android.com/ndk/guides/index.html