工欲善其事,必先利其器
。
1. 开发环境准备
搭建开发环境是我们进行开发前首先要完成的任务,进行Android jni开发,依赖的基本开发环境包括:
- Android sdk
- android ndk
- cmake
- android studio
Android studio的sdk manager已经包括了上面所说的sdk,ndk,cmake等工具的安装,所以一般只用下载android studio,然后再使用sdk manager工具下载这些工具就可以了。
默认情况下,Android studio使用的编译工具是cmake,但很多沿用的项目都是使用NDK的ndk-build工具来编译的,所以android studio也支持ndk-build。
2. 使用android studio创建本地C++工程
1.新建工程, 在向导的 Choose your project 部分中,选择Natvie C++
项目类型 。
2.在设置工程名,包名,保存路径和语言,此处我们选择Java语言。
3.在向导的 Customize C++ Support 部分中,可以选则C++ Toolchain
,一般情况下,选择默认就可以,如果开发中需要用到C++11,或者c++14等一些较高级的C++标准的特性时,可以选择对应的Toolchain
。
4.点击finish,开始构建工程,工程构建完成以后,整个项目及其gradle配置文件如下:
默认情况下,Android studio使用cmake编译链工具,通过gradle脚本进行配置,默认cmake配置如下:
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
cmake文件和c++源代码都在src/main/cpp/
目录下。
Android studio也支持ndk-buid
,根据实际需求,我们也可以配置为ndk-build
,当然,这需要我们先写好对应的Android.mk
和Appplication.mk(可选)
配置文件,然后通过修改gradle
配置中的externalNativeBuild
配置项来进行更改。配置为nkd-build编译工具,则其配置文件如下:
externalNativeBuild {
ndkBuild {
path file('src/main/cpp/Android.mk')
}
}
3. 使用现有android studio工程链接C++工程
当一个普通的不带C++本地库支持的项目需要引入一个现有的c++本地库时,可以使用android studio的Link C++ Psroject with Gradle
功能来导入一个本地C++库,导入的库需要提供可用的cmake配置文件或Android.mk配置文件,导入工作是通过加载这些本地库编译配置文件来完成的。
4.在android studio配置javah工具
在Settings->Tools->External Tools下创建NDK group,在NDK group下创建javah工具。
详细的配置参数如下:
配置项 | 参数 |
---|---|
Programe | $JDKPath$binjavah.exe |
Arguments | -classpath $ModuleFileDir$srcmainjava -jni -d $ModuleFileDir$srcmaincpp $FileClass$ |
Working directory | $FileDir$ |
使用时,只需要在定义了native方法的java类上右键选择NDK->javah即可生成对应的c++本地函数定义的头文件。
截图中例子中,TestJni.java定义了本地函数add
package com.android.jnitest;
public class TestJni {
public native int add(int a, int b);
}
使用javah生成的对应本地c++头文件com_android_jnitest_TestJni.h
内容如下:
#include <jni.h>
/* Header for class com_android_jnitest_TestJni */
#ifndef _Included_com_android_jnitest_TestJni
#define _Included_com_android_jnitest_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_android_jnitest_TestJni
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_android_jnitest_TestJni_add
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
5. 工程配置
5.1 可选参数配置
可以在模块级 build.gradle
文件的 defaultConfig
块中配置另一个 externalNativeBuild
块,为 CMake 或 ndk-build 指定可选参数和标记 。
android {
...
defaultConfig {
...
// This block is different from the one you use to link Gradle
// to your CMake or ndk-build script.
externalNativeBuild {
// For ndk-build, instead use the ndkBuild block.
cmake {
// Passes optional arguments to CMake.
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// Sets a flag to enable format macro constants for the C compiler.
cFlags "-D__STDC_FORMAT_MACROS"
// Sets optional flags for the C++ compiler.
cppFlags "-fexceptions", "-frtti"
}
}
}
...
}
通过上面的配置,可用对cmake的编译选项及C和C++编译选项做一些配置。
5.2 指定 ABI
默认情况下,Gradle 会针对 NDK 支持的应用二进制接口 (ABI) 将您的原生库编译到单独的 .so 文件中,并将这些文件全部打包到您的 APK 中。如果您希望 Gradle 仅编译和打包原生库的部分 ABI 配置,您可以在模块级文件 build.gradle
中使用 ndk.abiFilters
标记指定这些配置,如下所示:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {...}
// or ndkBuild {...}
}
// Similar to other properties in the defaultConfig block,
// you can configure the ndk block for each product flavor
// in your build configuration.
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a'
}
}
buildTypes {...}
externalNativeBuild {...}
}
6. 原生库支持
Android NDK 会提供一组随着新的 Android API 级别的后续发布而逐渐添加的原生标头和共享库文件。 请执行以下两个基本步骤的操作,以便让您的应用使用 NDK 提供的库:
- 在您的代码中添加与您想使用的库关联的标头。
通知编译系统您的原生模块需要在加载时链接库。
- 如果您使用的是 ndk-build:将原生库添加到您 Android.mk 文件中的 LOCAL_LDLIBS 变量中。例如,要链接 /system/lib/libfoo.so,请添加以下这行代码:
LOCAL_LDLIBS := -lfoo
要列出多个库,请使用空格作为分隔符。
如果您使用的是 CMake,向 CMake 编译脚本添加 find_library() 命令以找到 NDK 库并将其路径存储为一个变量。
find_library( # Defines the name of the path variable that stores the # location of the NDK library. log-lib # Specifies the name of the NDK library that # CMake needs to locate. log )
然后再 CMake 脚本中的 [`target_link_libraries()`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) 命令来关联库:
target_link_libraries( # Specifies the target library.
native-lib
# Links the log library to the target library.
${log-lib} )
常用的android原生库包括以下一些:
API级别 | 库名 | 链接代码 | 说明 |
---|---|---|---|
3 | C 库 | 系统自动添加,无需配置 | |
3 | 动态链接器库 | LOCAL_LDLIBS := -ldl | 动态链接器的 dlopen(3) 和 dlsym(3) 功能 |
3 | Android日志库 | LOCAL_LDLIBS := -llog | 原生代码向 logcat 发送日志消息 |
3 | ZLib 压缩库 | LOCAL_LDLIBS := -lz | |
4 | OpenGL ES 1.x | LOCAL_LDLIBS := -lGLESv1_CM | |
5 | OpenGL ES 2.0 | LOCAL_LDLIBS := -lGLESv2 | |
8 | jnigraphics | LOCAL_LDLIBS += -ljnigraphics | |
9 | EGL | LOCAL_LDLIBS += -lEGL | 分配和管理 OpenGLES 表面的原生平台接口 |
9 | OpenSL ES | LOCAL_LDLIBS += -lOpenSLES | 原生音频处理库 |
9 | Android 原生应用api | LOCAL_LDLIBS += -landroid | 使用原生代码编写整个 Android 应用 |
14 | OpenMAX AL | LOCAL_LDLIBS += -lOpenMAXAL | 原生多媒体处理库 |
14 | OpenSL ES | LOCAL_LDLIBS += -lOpenSLES | 增加了 PCM 支持 |
18 | OpenGL ES 3.0 | LOCAL_LDLIBS := -lGLESv3 | |
21 | OpenGL ES 3.1 | LOCAL_LDLIBS := -lGLESv3 | |
24 | OpenGL ES 3.2 | LOCAL_LDLIBS := -lGLESv3 |
7. C++ 库支持
7.1 C++ 运行时库
NDK 支持多种 C++ 运行时库。
名称 | 库文件 | 功能 |
---|---|---|
libc++ | 共享库为 libc++_shared.so <br/>静态库为 libc++_static.a | C++17 支持。 |
system | /system/lib/libstdc++.so | new 和 delete 。(在 r18 中已弃用。) |
none | 无头文件,有限 C++。 |
libc++
libc++ 同时提供静态库和共享库 。LLVM 的 libc++ 是 C++ 标准库,自 Lollipop 以来 Android 操作系统便一直使用该库,并且从 NDK r18 开始成为 NDK 中唯一可用的 STL。libc++ 的共享库为 libc++_shared.so,静态库为 libc++_static.a。
system
系统运行时指的是 /system/lib/libstdc++.so
。请勿将该库与 GNU 的全功能 libstdc++ 混淆。在 Android 系统中,libstdc++ 只是 new
和 delete
。对于全功能 C++ 标准库,请使用 libc++。
none
不包括STL。在这种情况下,没有关联或授权要求。不提供 C++ 标准头文件。
7.2 配置C++ 运行时
如果您要使用 CMake,则可使用模块级 build.gradle
文件中的 ANDROID_STL
变量,指定表表格中的一个运行时 。如果您要使用 ndk-build,则可使用 Application.mk 文件中的 APP_STL 变量指定表 1 中的一个运行时。
APP_STL := c++_shared
只能为应用选择一个运行时,并且只能在 Application.mk 中进行选择。
7.3 共享运行时
如果应用包括多个共享库,则应使用 libc++_shared.so
。
在 Android 系统中,NDK 使用的 libc++ 不是操作系统的一部分。这使得 NDK 用户能够获得最新的 libc++ 功能和问题修复程序,即使应用以旧版 Android 为目标。需要权衡的是,如果使用 libc++_shared.so
,则必须将其纳入 APK 中。如果使用 Gradle 编译应用,则此步骤会自动完成。
7.4 C++ 异常
C++ 异常受 libc++ 支持,但其在 ndk-build 中默认为停用状态。这是因为之前 NDK 并不支持 C++ 异常。CMake 和独立工具链默认启用 C++ 异常。
要在 ndk-build 中针对整个应用启用异常,请将下面这一行代码添加至 Application.mk
文件:
APP_CPPFLAGS := -fexceptions
要针对单一 ndk-build 模块启用异常,请将下面这一行代码添加至相应模块的Android.mk
中:
LOCAL_CPP_FEATURES := exceptions
或者,您可以使用:
LOCAL_CPPFLAGS := -fexceptions
7.4 RTTI
与异常一样,RTTI 也受 libc++ 支持,但在 ndk-build 中默认为停用状态。CMake 和独立工具链默认启用 RTTI。
要在 ndk-build 中针对整个应用启用 RTTI,请将下面这一行代码添加至 Application.mk
文件:
APP_CPPFLAGS := -frtti
要针对单一 ndk-build 模块启用 RTTI,请将下面这行代码添加至相应模块的 Android.mk
中:
LOCAL_CPP_FEATURES := rtti
或者,您可以使用:
LOCAL_CPPFLAGS := -frtti
参考: