Hello JNI:如何在Java中调用C代码?ndk-samples入门样本完整教程
ndk-samples 是 Android 官方的 NDK 示例仓库,其中 Hello JNI 是最经典的入门样本:它用最少的代码演示了如何在 Java(Kotlin)中调用 C 代码——在界面上显示一行由 C 函数返回的字符串。如果你想学 JNI 编程、了解 Android NDK 的工作方式,这个不到 30 行 C 代码的样本就是最好的起点。
一、什么是 JNI?为什么要在 Java 里调 C?
JNI(Java Native Interface,Java 本地接口) 是 Android/Java 生态中连接托管代码(Java/Kotlin)与本地代码(C/C++)的桥梁。
📌 在以下场景中,你会真正需要它:
- 复用已有的 C/C++ 算法库(图像处理、音频、密码学等)
- 对性能敏感的核心逻辑提速
- 调用 Android NDK 提供的系统 API(相机、传感器图、Neural Networks API 等)
工作流程一句话概括:Java 声明 external 方法 → C 按约定命名实现同名函数 → 运行时通过 JNI 找到并调用 C 函数。
二、快速上手:克隆仓库并运行 Hello JNI 样本
只需 4 步即可看到运行效果:
1️⃣ 克隆仓库:
git clone https://gitcode.com/gh_mirrors/nd/ndk-samples
2️⃣ 用 Android Studio 4.2+ 打开整个项目(需安装 NDK 组件) 3️⃣ 在顶部工具栏选择 hello-jni 样本(必要时先 Sync Gradle) 4️⃣ 点击运行按钮,设备屏幕上将显示 C 代码返回的文本
💡 官方说明:hello-jni/README.md 中给出了同样的 Getting Started 步骤;仓库整体结构可参考 ARCHITECTURE.md。
三、源码拆解:3 个文件看懂"Java 调 C"全过程
整个样本的核心代码只有 3 个文件,我们逐一拆解。
3.1 Kotlin 侧:声明 external 方法并加载 so 库
在 HelloJni.kt 中做了两件事:
external fun stringFromJNI(): String? // 声明本地方法,不写实现
companion object {
init { System.loadLibrary("hello-jni") } // 加载 C 编译出的动态库
}
external关键字表示"这个方法在别处(C 库里)实现",JVM 会在运行时按命名规则自动匹配 C 函数System.loadLibrary("hello-jni")加载安装时由包管理器解压到应用私有目录的libhello-jni.so
然后在 onCreate 中直接调用并显示结果:
binding.helloTextview.text = stringFromJNI()
3.2 C 侧:一个约定命名的函数就是 JNI 的全部
C 实现只有几行,见 hello-jni.cpp:
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject) {
std::string hello = "Hello from JNI.";
return env->NewStringUTF(hello.c_str());
}
⚠️ 函数名不是随便起的,它由 4 段拼接而成,这就是 JNI 的核心规则:
| 片段 | 含义 |
|---|---|
Java_ | JNI 固定前缀 |
com_example_hellojni | Kotlin/Java 包名(. 换成 _) |
HelloJni | 类名 |
stringFromJNI | 方法名 |
只要包名、类名、方法名任一变一个字符,JVM 就找不到实现,调用时会抛出 UnsatisfiedLinkError——样本里特意留了一个未实现的 unimplementedStringFromJNI() 方法(见 HelloJni.kt)来演示这个"报错"行为。
另外两个细节值得注意:
JNIEnv* env是指向当前线程 JNI 环境的指针,C 侧靠它创建返回给 Java 的字符串(NewStringUTF)jstring、jobject都是 JNI 对 Java 类型的"镜像",jstring≈String,jobject≈ 任意 Java 对象
3.3 构建侧:CMake 把 C 编译成 .so
CMakeLists.txt 定义了动态库与链接依赖:
add_library(hello-jni SHARED hello-jni.cpp)
target_link_libraries(hello-jni android log)
而 Kotlin 侧能找到这个 CMakeLists.txt,靠的是 build.gradle 中的一行配置:
externalNativeBuild {
cmake { path "src/main/cpp/CMakeLists.txt" }
}
也就是说:Gradle 负责整体打包,CMake 负责编译 C 代码,两者在 build.gradle 里会师。这就是现代 Android 原生开发的标准构建链路。
四、界面与其余文件速览
运行效果就是页面中央一行文字——来自 C 的 Hello from JNI.:
- 界面布局很简单:activity_hello_jni.xml 只有一个居中的
TextView - 入口 Activity 在 AndroidManifest.xml 中注册为启动器
五、学会 Hello JNI 之后:还能做点什么?
Hello JNI 只是"握手"级别的演示,真正上手 NDK 后,你能做出性能敏感的完整原生应用。仓库里的游戏样本全部用本地代码直接渲染画面,不依赖 Android 视图系统:
这些样本的共同点:C/C++ 完成重活,Java/Kotlin 只负责薄薄的壳。
其他推荐的进阶入门样本
| 样本 | 模块路径 | 演示内容 |
|---|---|---|
| Hello CMake | hello-cmake 见仓库目录 | CMake 构建链(与 Hello JNI 配套) |
| Native Activity | native-activity/ | 无 Java 壳的纯原生 Activity |
| Hello Oboe | hello-oboe/ | 用 NDK 音频 API 播放声音 |
| Hello Vulkan | hello-vulkan/ | Vulkan 图形渲染入门 |
| Endless Tunnel | endless-tunnel/ | 完整的游戏引擎(OpenGL ES) |
📚 更多样本清单与仓库目录结构,参见仓库根目录的 README.md 与 REFERENCE.md。
六、常见问题(FAQ)
Q1:为什么调用 external 方法时崩溃,提示 UnsatisfiedLinkError? 九成是 C 函数名与"包名_类名_方法名"约定不匹配,或者 so 库没被 System.loadLibrary 加载。
Q2:Kotlin 里用 external 还是 Java 的 native? 完全可以。两者对 JNI 而言是等价的,Hello JNI 样本现在用 Kotlin 声明(external fun)。
Q3:C 里能传复杂对象给 Java 吗? 可以,但建议优先用基础类型、字符串、字节数组。JNI 对对象的操作较繁琐,实践中常见做法是 C 侧用 POD 结构体/字节缓冲,Java 侧用 ByteBuffer 或 byte[] 交接数据。
Q4:这个样本能直接当生产项目模板吗? 官方明确提醒:样本以"简洁演示 API"为目标,并非生产级项目。做正式项目建议用 Android Studio 的 "Native C++" 新工程模板,把本仓库当参考。
七、写在最后
回顾一下 Hello JNI 教会我们的最小知识闭环:
- Java/Kotlin:
external声明方法 +System.loadLibrary加载 so 库 - C/C++:按
Java_包名_类名_方法名实现函数,通过JNIEnv*与 Java 世界交互 - 构建:CMake 编译 C 代码,Gradle 通过
externalNativeBuild接管整条流水线
吃透这 30 行 C 代码,你就拿到了打开 Android 本地世界大门的钥匙 🗝️。接下来可以顺着 native-activity、hello-oboe 等样本继续深入,逐步掌握相机、音频、图形、神经网络等 NDK API。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考





