第16章 NDK与JNI开发¶
学习目标:掌握Android NDK开发,理解JNI机制,实现Java/Kotlin与C/C++的交互。
预计学习时间:3-5天 实践时间:1-2天
目录¶
1. NDK概述¶
1.1 什么是NDK¶
NDK(Native Development Kit)是一套工具集,允许开发者使用C/C++代码开发Android应用。
1.2 适用场景¶
- ✅ 性能敏感计算(图像处理、音视频编解码)
- ✅ 复用现有C/C++库
- ✅ 跨平台代码共享
- ✅ 底层硬件访问
2. JNI基础¶
2.1 声明Native方法¶
Kotlin
// 声明包含 native 方法的 Kotlin 类
class NativeLib {
companion object {
init {
// 在类加载时加载名为 "native-lib" 的 C/C++ 动态库(对应 libnative-lib.so)
System.loadLibrary("native-lib")
}
}
// 使用 external 关键字声明由 C/C++ 实现的本地方法
external fun stringFromJNI(): String // 从 C++ 层获取字符串
external fun add(a: Int, b: Int): Int // 调用 C++ 层执行整数加法运算
}
2.2 C++实现¶
C++
// native-lib.cpp — JNI 本地方法实现文件
#include <jni.h> // JNI 头文件,提供 JNIEnv、jstring 等类型定义
#include <string> // C++ 标准字符串库
// extern "C" 防止 C++ 名称修饰,确保 JVM 能正确找到函数
// 函数命名规则:Java_包名_类名_方法名(包名中的 . 替换为 _)
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativelib_NativeLib_stringFromJNI(
JNIEnv* env, // JNI 环境指针,用于调用 JNI 函数
jobject /* this */) { // 调用该方法的 Java 对象引用
std::string hello = "Hello from C++";
// 将 C++ 字符串转换为 Java 的 UTF 字符串并返回
return env->NewStringUTF(hello.c_str());
}
// 实现 Kotlin 层声明的 add() 方法,接收两个 jint 参数
extern "C" JNIEXPORT jint JNICALL
Java_com_example_nativelib_NativeLib_add(
JNIEnv* env,
jobject /* this */,
jint a, // 对应 Kotlin 的 Int 类型
jint b) {
return a + b; // 在 Native 层完成加法运算后返回结果
}
2.3 数据类型映射¶
| Java/Kotlin | 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 | - |
| Object | jobject | - |
3. CMake构建¶
3.1 CMakeLists.txt¶
CMake
# 指定 CMake 最低版本要求
cmake_minimum_required(VERSION 3.22.1)
# 定义项目名称
project("native-lib")
# 创建共享库(.so 文件),SHARED 表示动态库
add_library(
native-lib # 库名称(生成 libnative-lib.so)
SHARED # 库类型:共享/动态库
native-lib.cpp) # 源文件列表
# 查找 Android 系统自带的 log 库,用于 __android_log_print 等日志函数
find_library(
log-lib # 变量名,存储找到的库路径
log) # 要查找的库名称
# 将 log 库链接到 native-lib,使其可以使用日志功能
target_link_libraries(
native-lib # 目标库
${log-lib}) # 要链接的依赖库
3.2 build.gradle配置¶
Kotlin
android {
// 配置外部 CMake 构建脚本路径和版本
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt") // CMakeLists.txt 文件位置
version = "3.22.1" // 指定 CMake 版本
}
}
defaultConfig {
externalNativeBuild {
cmake {
cppFlags += "-std=c++17" // 启用 C++17 标准
arguments += "-DANDROID_STL=c++_shared" // 使用共享的 C++ 标准库
}
}
ndk {
// 指定支持的 CPU 架构(ABI),覆盖主流 ARM 和 x86 平台
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
}
4. 性能优化¶
4.1 减少JNI调用开销¶
C++
// 批量处理数据——演示如何高效操作 Java 数组,减少 JNI 调用次数
JNIEXPORT void JNICALL
Java_processArray(JNIEnv* env, jobject thiz, jintArray arr) {
// 获取 Java int 数组的本地指针,避免逐元素跨 JNI 边界访问
jint* elements = env->GetIntArrayElements(arr, nullptr);
// 获取数组长度
jsize len = env->GetArrayLength(arr);
// 在 Native 层批量处理所有元素(示例:每个元素乘以 2)
for (int i = 0; i < len; i++) {
elements[i] *= 2;
}
// 释放数组并将修改写回 Java 层(第三个参数 0 表示拷贝回去并释放本地副本)
env->ReleaseIntArrayElements(arr, elements, 0);
}
4.2 使用Direct Buffer¶
C++
// 使用 Direct Buffer 实现零拷贝数据传输,避免 JNI 数组拷贝开销
JNIEXPORT void JNICALL
Java_processBuffer(JNIEnv* env, jobject thiz, jobject buffer) {
// 获取 Java NIO DirectByteBuffer 的本地内存地址(无需数据拷贝)
void* data = env->GetDirectBufferAddress(buffer); // 指针:存储变量的内存地址
// 获取 Buffer 的容量(字节数)
jlong capacity = env->GetDirectBufferCapacity(buffer);
// 直接在本地内存上操作数据,Java 层与 Native 层共享同一块内存
processData(data, capacity);
}
5. 实践练习¶
练习1:图像处理¶
任务:实现灰度转换
要求: - 读取Bitmap - C++实现灰度算法 - 返回处理后的图像
练习2:加密算法¶
任务:实现AES加密
要求: - 使用OpenSSL - Java层调用 - 性能对比
本章小结¶
核心要点¶
- NDK用于C/C++开发
- JNI是Java与C/C++的桥梁
- CMake是推荐的构建工具
- 注意JNI调用开销
下一步¶
完成本章学习后,请进入第17章:安全与加密技术。
本章完成时间:预计3-5天