1. 引言
NDK(Native Development Kit)是 Android 平台上用于开发 C/C++ 原生代码的工具集。很多开发者第一次听到 NDK 时,往往既好奇又畏惧------好奇它能为应用带来怎样的性能提升,畏惧它复杂的构建配置和 JNI 交互。
本文将从零开始,带你初探 NDK 的世界:先厘清 NDK 到底是什么、能解决什么问题,再动手搭建环境、编写第一个原生函数,最后通过 JNI 让 Java 与 C/C++ 真正对话。读完本文,你将具备独立跑通一个 NDK 示例项目的能力。
2. 什么是 NDK
NDK 全称 Native Development Kit,是一套允许你在 Android 应用中使用 C 和 C++ 代码的工具集合。它包含编译器、链接器、调试工具、头文件库以及构建脚本等组件。
2.1 NDK 与 SDK 的区别
- SDK(Software Development Kit):主要面向 Java/Kotlin 开发,提供 Android 框架 API、布局系统、四大组件等。
- NDK(Native Development Kit):面向 C/C++ 开发,提供原生编译工具链,让代码直接编译为机器码运行在设备上。
简单来说,SDK 是「用 Java 写 App」的工具,NDK 是「用 C/C++ 写底层库」的工具,两者通过 JNI 桥接。
2.2 为什么需要 NDK
| 场景 | 说明 |
|---|---|
| 性能敏感计算 | 图像处理、音视频编解码、物理引擎等计算密集型任务 |
| 复用现有 C/C++ 库 | OpenCV、FFmpeg、SQLite 等成熟原生库直接接入 |
| 跨平台代码共享 | 同一套 C/C++ 逻辑可在 Android/iOS 等多端复用 |
| 底层系统交互 | 访问某些仅通过原生接口暴露的系统能力 |
3. 环境准备
在开始写代码之前,需要先准备好开发环境。
3.1 安装 NDK 与 CMake
打开 Android Studio,依次进入 SDK Manager → SDK Tools,勾选并安装:
- NDK (Side by side):选择较新的稳定版本,例如 26.x。
- CMake:用于驱动原生代码的构建。
安装完成后,可以在 local.properties 或项目级 build.gradle 中指定 NDK 版本。
3.2 创建支持原生代码的工程
新建项目时,可以直接选择 Native C++ 模板,Android Studio 会自动生成 CMakeLists.txt 和 native-lib.cpp 示例文件,省去手动配置的麻烦。
4. 第一个 NDK 程序
下面我们动手编写第一个原生函数,并通过 JNI 在 Java 层调用它。
4.1 编写 C++ 源文件
在 src/main/cpp 目录下创建 native-lib.cpp:
cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
4.2 配置 CMakeLists.txt
在 src/main/cpp/CMakeLists.txt 中声明原生库:
cmake
cmake_minimum_required(VERSION 3.22.1)
project("myapplication")
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
4.3 在 Java 中加载并调用
在 MainActivity 中声明 native 方法并加载库:
java
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
}
运行应用,界面上的文本就会显示 Hello from C++,这意味着你的第一个 NDK 程序已经跑通了。
5. JNI 桥接机制
JNI(Java Native Interface)是连接 Java 世界与原生世界的桥梁。理解它的核心规则,是深入 NDK 的关键。
5.1 命名规则
Java 的 native 方法必须映射到 C/C++ 函数,默认命名规则为:
Java_包名_类名_方法名
例如 com.example.myapplication.MainActivity.stringFromJNI 对应 Java_com_example_myapplication_MainActivity_stringFromJNI。
5.2 数据类型映射
| Java 类型 | JNI 类型 | C/C++ 类型 |
|---|---|---|
| boolean | jboolean | unsigned char |
| byte | jbyte | signed char |
| char | jchar | unsigned short |
| short | jshort | short |
| int | jint | int |
| long | jlong | long long |
| float | jfloat | float |
| double | jdouble | double |
| String | jstring | jobject 引用 |
5.3 JNIEnv 的作用
JNIEnv* 是每个 native 方法都会收到的环境指针,它提供了大量操作 Java 对象的函数,例如:
NewStringUTF:创建 Java 字符串GetStringUTFChars:读取 Java 字符串内容FindClass:查找 Java 类CallVoidMethod:调用 Java 方法
6. 常见问题与调试技巧
初学 NDK 时,最容易踩到下面几个坑。
6.1 方法找不到(UnsatisfiedLinkError)
- 检查函数名是否与包名、类名、方法名完全一致。
- 确认
System.loadLibrary的库名与add_library中一致。 - 检查是否忘记
extern "C"导致符号被 C++ 名字修饰。
6.2 崩溃与日志定位
使用 __android_log_print 输出日志到 Logcat:
cpp
#include <android/log.h>
#define LOG_TAG "NativeLib"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
LOGI("native function called");
6.3 使用 LLDB 调试
Android Studio 支持对原生代码断点调试。在 C++ 源文件行号处打上断点,以 Debug 模式运行,即可像调试 Java 一样逐步跟踪原生代码。
7. 总结
本文带你初探了 NDK 的世界:从概念、环境准备,到编写第一个原生函数,再到理解 JNI 桥接机制。NDK 的学习曲线虽然比纯 Java 开发陡峭一些,但一旦掌握,你就能在性能优化和底层能力扩展上获得极大的自由度。
下一步建议:
- 尝试在 native 层处理更复杂的数据结构(如数组、对象)。
- 学习如何将 OpenCV、FFmpeg 等大型 C/C++ 库集成进 Android 工程。
- 了解 ABI(Application Binary Interface)与多架构打包(armeabi-v7a、arm64-v8a、x86_64)的区别。