Android NDK(Native Development Kit)是一套工具集,允许你使用 C 和 C++ 等原生代码语言来开发 Android 应用的部分功能。
一、NDK 核心概念
1. 什么是 NDK?
定义:NDK 是 Android SDK 的补充工具集,用于将 C/C++ 代码编译成 (Shared Object,共享库)文件,供 Android 应用(Java/Kotlin)调用。核心作用:
.so
复用现有 C/C++ 代码(如算法库、游戏引擎、硬件驱动)。提升性能敏感场景(如实时渲染、音视频处理、加密计算)的执行效率。访问 Android 平台的底层 API(如 OpenSL ES 音频、OpenGL ES 图形、传感器)。 适用场景:
高性能计算(如数学建模、图像处理)。跨平台开发(如 Unity、Cocos 游戏引擎)。硬件交互(如蓝牙、USB 设备通信)。代码保护(C/C++ 反编译难度高于 Java)。
2. 关键术语
JNI(Java Native Interface):Java 与 C/C++ 通信的桥梁,定义了一套规范,允许 Java 调用 C/C++ 函数,反之亦然。.so 文件:C/C++ 编译后的动态链接库,Android 按 CPU 架构(armeabi-v7a、arm64-v8a、x86、x86_64)生成对应版本。CMake:跨平台构建工具,用于定义 NDK 项目的编译规则(替代旧版 ndk-build)。NDK 工具链:包含编译器(clang)、链接器(ld)、调试器(gdb)等工具的集合。ABI(Application Binary Interface):应用二进制接口,定义了 CPU 架构、指令集、函数调用约定等,决定了 文件的兼容性。
.so
二、NDK 开发环境搭建
1. 安装 NDK 和 CMake
通过 Android Studio 安装:
打开 Android Studio → →
File →
Settings →
Appearance & Behavior →
System Settings。切换到
Android SDK 选项卡,勾选:
SDK Tools
NDK (Side by side)(推荐最新稳定版)。CMake。LLDB(调试 C/C++ 代码用)。 点击 下载安装。
Apply
2. 配置项目的
build.gradle
build.gradle
在模块级 (
build.gradle)中配置 NDK 相关参数:
app/build.gradle
groovy
android {
compileSdk 34
defaultConfig {
applicationId "com.example.ndkdemo"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
// 配置支持的 CPU 架构(按需添加)
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
// 配置 CMake 构建脚本路径
externalNativeBuild {
cmake {
cppFlags "" // 可添加额外的 C++ 编译 flags(如 -std=c++17)
}
}
}
// 配置外部原生构建(CMake)
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt" // CMake 脚本路径
version "3.22.1" // CMake 版本(需与安装的版本匹配)
}
}
// 其他配置...
}
三、NDK 开发流程(Hello World 示例)
1. 创建 C/C++ 源文件
在 下创建
app/src/main/ 目录,新建
cpp 文件:
native-lib.cpp
cpp
运行
#include <jni.h>
#include <string>
// JNI 函数:Java_包名_类名_方法名
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
// 将 C++ string 转换为 Java String 并返回
return env->NewStringUTF(hello.c_str());
}
说明:
:确保函数名按 C 语言规则编译(避免 C++ 名称修饰,让 Java 能正确找到函数)。
extern "C" 和
JNIEXPORT:JNI 关键字,标记该函数为外部可调用。
JNICALL:JNI 环境指针,提供访问 Java 虚拟机的接口(如创建 Java 对象、调用 Java 方法)。
JNIEnv*:对应 Java 中的
jobject(非静态方法),静态方法为
this(对应 Java 类)。
jclass
2. 编写 CMake 构建脚本(CMakeLists.txt)
在 下创建
app/src/main/cpp/,定义编译规则:
CMakeLists.txt
cmake
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.22.1)
# 定义项目名称(可选)
project("ndkdemo")
# 添加源文件:将 native-lib.cpp 编译成动态库
add_library(
# 动态库名称(需与 Java 中 System.loadLibrary("ndkdemo") 对应)
ndkdemo
# 库类型:SHARED 表示动态库(.so 文件)
SHARED
# 源文件路径
native-lib.cpp)
# 查找 Android NDK 提供的日志库(用于在 C++ 中打印日志)
find_library(
# 日志库变量名(后续链接时使用)
log-lib
# 日志库名称(Android NDK 内置库,无需手动添加源文件)
log)
# 链接库:将 ndkdemo 库与日志库链接起来
target_link_libraries(
# 目标库
ndkdemo
# 链接的库(${log-lib} 对应前面查找的日志库)
${log-lib})
3. 在 Java 中调用 C/C++ 函数
在 中加载
MainActivity.java 库并声明 native 方法:
.so
java
运行
package com.example.ndkdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// 加载 C/C++ 编译后的动态库(库名与 CMakeLists.txt 中 add_library 的名称一致)
static {
System.loadLibrary("ndkdemo");
}
// 声明 native 方法(与 C++ 中函数名对应)
public native String stringFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 调用 native 方法,获取 C++ 返回的字符串
String result = stringFromJNI();
TextView textView = findViewById(R.id.sample_text);
textView.setText(result);
}
}
4. 编译运行
点击 Android Studio 的「Run」按钮,NDK 会自动编译 C/C++ 代码生成 文件,并打包到 APK 中。运行后,界面会显示
.so,表示调用成功。
Hello from C++
四、JNI 核心交互(Java ↔ C/C++)
1. 数据类型映射
JNI 定义了 Java 类型与 C/C++ 类型的对应关系:
| Java 类型 | JNI 类型 | C/C++ 类型(示例) |
|---|---|---|
| byte | jbyte | signed char |
| short | jshort | short |
| int | jint | int |
| long | jlong | long long |
| float | jfloat | float |
| double | jdouble | double |
| boolean | jboolean | unsigned char(0=false,1=true) |
| char | jchar | unsigned short |
| String | jstring | const char*(需转换) |
| int[] | jintArray | jint*(需转换) |
| Object | jobject | jobject(需通过 JNI 操作) |
2. 字符串交互(Java String ↔ C/C++ char*)
C++ 接收 Java String:
cpp
运行
extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_passString(JNIEnv* env, jobject thiz, jstring str) {
// 将 jstring 转换为 C++ char*(UTF-8 编码)
const char* c_str = env->GetStringUTFChars(str, nullptr);
if (c_str != nullptr) {
// 使用 c_str(如打印日志)
__android_log_print(ANDROID_LOG_INFO, "NDK", "Java 传递的字符串:%s", c_str);
// 释放资源(避免内存泄漏)
env->ReleaseStringUTFChars(str, c_str);
}
}
C++ 返回 Java String:
cpp
运行
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_returnString(JNIEnv* env, jobject thiz) {
const char* c_str = "C++ 返回的字符串";
// 将 C++ char* 转换为 jstring(UTF-8 编码)
return env->NewStringUTF(c_str);
}
3. 数组交互(Java 数组 ↔ C/C++ 数组)
C++ 接收 Java 数组:
cpp
运行
extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_passIntArray(JNIEnv* env, jobject thiz, jintArray arr) {
// 获取数组长度
jsize length = env->GetArrayLength(arr);
// 将 jintArray 转换为 C++ jint*(获取数组元素的指针)
jint* c_arr = env->GetIntArrayElements(arr, nullptr);
if (c_arr != nullptr) {
// 操作数组(如打印所有元素)
for (int i = 0; i < length; i++) {
__android_log_print(ANDROID_LOG_INFO, "NDK", "数组元素 %d:%d", i, c_arr[i]);
}
// 释放资源(0 表示复制回 Java 数组并释放 C++ 数组)
env->ReleaseIntArrayElements(arr, c_arr, 0);
}
}
C++ 返回 Java 数组:
cpp
运行
extern "C" JNIEXPORT jintArray JNICALL
Java_com_example_ndkdemo_MainActivity_returnIntArray(JNIEnv* env, jobject thiz) {
jsize length = 5;
// 创建 Java int 数组
jintArray j_arr = env->NewIntArray(length);
// 定义 C++ 数组
jint c_arr[] = {1, 2, 3, 4, 5};
// 将 C++ 数组复制到 Java 数组
env->SetIntArrayRegion(j_arr, 0, length, c_arr);
return j_arr;
}
4. 调用 Java 方法(C++ → Java)
cpp
运行
extern "C" JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_callJavaMethod(JNIEnv* env, jobject thiz) {
// 1. 获取 Java 类(MainActivity.class)
jclass clazz = env->GetObjectClass(thiz);
if (clazz == nullptr) return;
// 2. 查找 Java 方法(方法签名:(Ljava/lang/String;)V)
// 方法签名格式:(参数类型)返回类型,String 对应 Ljava/lang/String;,void 对应 V
jmethodID method_id = env->GetMethodID(clazz, "onNativeCall", "(Ljava/lang/String;)V");
if (method_id == nullptr) {
env->DeleteLocalRef(clazz); // 释放类引用
return;
}
// 3. 准备方法参数(C++ char* → Java String)
jstring param = env->NewStringUTF("C++ 调用 Java 方法");
// 4. 调用 Java 方法(thiz 是调用对象,method_id 是方法 ID,param 是参数)
env->CallVoidMethod(thiz, method_id, param);
// 5. 释放资源
env->DeleteLocalRef(param);
env->DeleteLocalRef(clazz);
}
对应的 Java 方法:
java
运行
// 与 C++ 中 GetMethodID 的方法名、签名一致
public void onNativeCall(String message) {
Log.d("NDK", "Java 收到 C++ 调用:" + message);
// 可执行 UI 更新等操作
runOnUiThread(() -> Toast.makeText(this, message, Toast.LENGTH_SHORT).show());
}
五、NDK 调试
1. 配置调试环境
在 中启用调试:
build.gradle
groovy
android {
buildTypes {
debug {
debuggable true // 启用调试
jniDebuggable true // 启用 JNI 调试
}
}
}
选择「Run」→「Edit Configurations」,确保 选择「Auto」或「Native」。
Debugger
2. 调试步骤
在 C/C++ 代码中设置断点(点击代码行号左侧)。以调试模式运行应用(点击「Debug」按钮)。当程序执行到断点时,会暂停在 Android Studio 的调试面板,可查看变量值、单步执行(F10)、步入函数(F11)等。
3. 日志调试
使用 Android NDK 内置的 函数打印日志,在 Logcat 中查看:
__android_log_print
cpp
运行
#include <android/log.h>
// 打印日志:级别(ANDROID_LOG_INFO)、标签("NDK")、内容(格式化字符串)
__android_log_print(ANDROID_LOG_INFO, "NDK", "调试信息:%d", 123);
日志级别:(V)、
ANDROID_LOG_VERBOSE(D)、
ANDROID_LOG_DEBUG(I)、
ANDROID_LOG_INFO(W)、
ANDROID_LOG_WARN(E)。
ANDROID_LOG_ERROR
六、NDK 开发注意事项
1. 内存管理
JNI 引用管理:
局部引用(如 、
jstring):通过
jobject 创建,作用域为当前 JNI 函数,需通过
env->New... 手动释放(避免内存泄漏)。全局引用:通过
env->DeleteLocalRef 创建,作用域为整个应用,需通过
env->NewGlobalRef 释放(否则会导致内存泄漏)。 C/C++ 内存管理:
env->DeleteGlobalRef
使用 /
new 分配的内存,需通过
malloc/
delete 手动释放(避免内存泄漏)。
free
2. 线程安全
JNI 函数默认不线程安全,若在多线程中调用 JNI 方法,需注意:
每个线程有独立的 ,不能跨线程共享。若需在后台线程调用 JNI,需通过
JNIEnv* 附加线程到 JVM,获取当前线程的
AttachCurrentThread:
JNIEnv*
cpp
运行
JNIEnv* env;
// 附加当前线程到 JVM(g_vm 是全局 JVM 指针,需在 JNI_OnLoad 中初始化)
if (g_vm->AttachCurrentThread(&env, nullptr) == JNI_OK) {
// 使用 env 执行 JNI 操作
g_vm->DetachCurrentThread(); // 分离线程(在线程退出前调用)
}
3. 兼容性问题
ABI 兼容性:
不同 CPU 架构(armeabi-v7a、arm64-v8a 等)的 文件不能混用,需为目标架构编译对应的版本。建议只保留必要的 ABI(如
.so 和
arm64-v8a),减少 APK 体积。 Android 版本兼容性:
armeabi-v7a
避免使用高版本 NDK 中新增的 API,若需使用,需通过 判断 Android 版本:
__ANDROID_API__
cpp
运行
#if __ANDROID_API__ >= 24
// 使用 Android 7.0(API 24)及以上的 API
#else
// 兼容低版本的替代实现
#endif
4. 性能优化
减少 JNI 调用:JNI 调用有一定开销,避免在循环中频繁调用 JNI 方法(可批量传递数据)。使用原生数据类型:优先使用 JNI 定义的原生类型(如 、
jint),避免类型转换开销。优化内存拷贝:使用
jlong 直接访问 Java NIO 缓冲区,避免数据拷贝。
GetDirectBufferAddress
七、常用 NDK 工具
ndk-build:旧版 NDK 构建工具(基于 Makefile),现已逐步被 CMake 替代。CMake:跨平台构建工具,NDK 官方推荐使用。LLDB:Android Studio 内置的原生调试器,用于调试 C/C++ 代码。ndk-gdb:命令行调试工具,适用于无 Android Studio 的场景。objdump/:查看
readelf 文件的符号表、依赖库等信息。ndk-stack:解析原生崩溃日志(
.so 文件),定位崩溃位置。
tombstone
总结
Android NDK 开发的核心是通过 JNI 实现 Java 与 C/C++ 的交互,适用于高性能、跨平台、底层交互等场景。实际开发中需注意内存管理、线程安全、兼容性和性能优化,同时熟练使用 CMake 构建和 LLDB 调试工具。随着 Android 平台的发展,NDK 也在不断更新,建议关注官方文档(Android NDK 官方指南)获取最新特性和最佳实践。




