Android开发艺术探索 第14章:JNI和NDK编程
Java本地接口Java调用C/C++代码的桥梁允许Java代码与本地应用或库交互为什么需要JNI?场景原因性能敏感C/C++比Java快(图像处理、音视频编解码)复用已有代码使用现有的C/C++库(FFmpeg、OpenCV)硬件访问直接操作硬件(摄像头、传感器底层驱动)代码保护C/C++代码反编译难度大跨平台库同一套C/C++代码在Android和iOS使用知识点重要性难度JNI基本概念⭐⭐⭐
第14章:JNI和NDK编程
章节定位:本章介绍Android Native开发技术,包括JNI调用、NDK开发、Java与C/C++互调等核心知识。
14.1 JNI概述
14.1.1 什么是JNI
JNI(Java Native Interface):Java本地接口
- Java调用C/C++代码的桥梁
- 允许Java代码与本地应用或库交互
为什么需要JNI?
| 场景 | 原因 |
|---|---|
| 性能敏感 | C/C++比Java快(图像处理、音视频编解码) |
| 复用已有代码 | 使用现有的C/C++库(FFmpeg、OpenCV) |
| 硬件访问 | 直接操作硬件(摄像头、传感器底层驱动) |
| 代码保护 | C/C++代码反编译难度大 |
| 跨平台库 | 同一套C/C++代码在Android和iOS使用 |
14.1.2 JNI vs NDK
| 概念 | 定义 | 作用 |
|---|---|---|
| JNI | Java Native Interface | Java与C/C++互调的规范 |
| NDK | Native Development Kit | Android官方提供的Native开发工具集 |
关系:NDK是实现JNI的工具集,JNI是规范。
14.2 NDK开发环境搭建
14.2.1 安装NDK
方式1:Android Studio安装
- 打开 SDK Manager
- SDK Tools → 勾选 NDK (Side by side)、CMake
- 点击 Apply 下载安装
方式2:手动下载
# 下载NDK
https://developer.android.com/ndk/downloads
# 配置环境变量(macOS/Linux)
export NDK_HOME=/path/to/ndk
export PATH=$PATH:$NDK_HOME
14.2.2 配置项目
在build.gradle中启用NDK:
android {
defaultConfig {
ndk {
// 指定支持的CPU架构
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt" // CMake配置文件路径
version "3.18.1"
}
}
}
创建CMakeLists.txt(C/C++构建配置):
# 指定CMake最低版本
cmake_minimum_required(VERSION 3.18.1)
# 项目名称
project("native-lib")
# 添加库
add_library(
native-lib # 库名称
SHARED # 动态库(SHARED)或静态库(STATIC)
native-lib.cpp # C++源文件
)
# 查找Android日志库
find_library(
log-lib
log
)
# 链接库
target_link_libraries(
native-lib
${log-lib}
)
14.3 JNI开发流程
14.3.1 完整开发步骤
Step 1:在Java中声明native方法
public class JNIHelper {
// 加载native库
static {
System.loadLibrary("native-lib");
}
// 声明native方法(无方法体)
public native String stringFromJNI();
public native int add(int a, int b);
public native void callJavaMethod();
}
Step 2:生成C/C++头文件(可选)
# 旧方法(javah,已废弃)
javah -jni -classpath . -d jni com.example.app.JNIHelper
# 新方法(Android Studio自动生成)
# 或手动编写,遵循JNI命名规范
Step 3:实现C/C++函数
#include <jni.h>
#include <string>
#include <android/log.h>
// JNI函数命名规范:Java_{包名}_{类名}_{方法名}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_JNIHelper_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_example_app_JNIHelper_add(
JNIEnv* env,
jobject /* this */,
jint a,
jint b) {
__android_log_print(ANDROID_LOG_DEBUG, "JNI", "add called: %d + %d", a, b);
return a + b;
}
Step 4:编译生成.so库
# Android Studio会自动编译,生成:
# app/build/intermediates/cmake/debug/obj/
# ├── armeabi-v7a/libnative-lib.so
# ├── arm64-v8a/libnative-lib.so
# ├── x86/libnative-lib.so
# └── x86_64/libnative-lib.so
Step 5:在Java中调用
JNIHelper helper = new JNIHelper();
String result = helper.stringFromJNI(); // "Hello from C++"
int sum = helper.add(10, 20); // 30
14.3.2 JNI函数命名规范
格式:Java_{package}_{class}_{method}
示例:
Java类:com.example.app.JNIHelper
方法名:stringFromJNI
JNI函数名:
Java_com_example_app_JNIHelper_stringFromJNI
特殊字符处理:
- 下划线
_替换为_1 - 分号
;替换为_2 - 左中括号
[替换为_3
示例:
Java方法:my_method
JNI函数:Java_com_example_app_JNIHelper_my_1method
14.4 JNI数据类型
14.4.1 基本数据类型映射
| Java类型 | JNI类型 | C/C++类型 | 字节数 |
|---|---|---|---|
| boolean | jboolean | unsigned char | 1 |
| byte | jbyte | signed char | 1 |
| char | jchar | unsigned short | 2 |
| short | jshort | short | 2 |
| int | jint | int | 4 |
| long | jlong | long long | 8 |
| float | jfloat | float | 4 |
| double | jdouble | double | 8 |
| void | void | void | - |
示例:
// Java: public native int add(int a, int b);
extern "C" JNIEXPORT jint JNICALL
Java_com_example_JNIHelper_add(JNIEnv* env, jobject thiz, jint a, jint b) {
return a + b;
}
14.4.2 引用数据类型
| Java类型 | JNI类型 | 说明 |
|---|---|---|
| Object | jobject | Java对象 |
| Class | jclass | Java类 |
| String | jstring | Java字符串 |
| Throwable | jthrowable | Java异常 |
| Object[] | jobjectArray | 对象数组 |
| boolean[] | jbooleanArray | boolean数组 |
| int[] | jintArray | int数组 |
| … | … | 其他数组类型 |
重要:引用类型都是指针,操作需要通过JNIEnv的方法。
14.5 JNIEnv详解
14.5.1 JNIEnv是什么
定义:
- JNI环境指针,指向一个函数表
- 提供了200+个JNI函数
- 每个线程独立的JNIEnv
获取方式:
// 方式1:作为参数传入(最常用)
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_nativeMethod(JNIEnv* env, jobject thiz) {
// env已经可以直接使用
}
// 方式2:从JavaVM获取
JavaVM* jvm;
JNIEnv* env;
jvm->AttachCurrentThread(&env, NULL);
14.5.2 常用JNIEnv方法
1. 字符串操作
// Java String → C字符串
jstring jstr = ... ; // Java传入的String
const char* cstr = env->GetStringUTFChars(jstr, NULL);
printf("String: %s\n", cstr);
env->ReleaseStringUTFChars(jstr, cstr); // ⚠️ 必须释放
// C字符串 → Java String
const char* cstr = "Hello";
jstring jstr = env->NewStringUTF(cstr);
return jstr;
⚠️ 内存管理:
GetStringUTFChars会分配内存,必须调用ReleaseStringUTFChars释放- 否则会导致内存泄漏
2. 数组操作
// Java int[] → C数组
jintArray jarray = ... ; // Java传入的int[]
jint* carray = env->GetIntArrayElements(jarray, NULL);
jsize length = env->GetArrayLength(jarray);
for (int i = 0; i < length; i++) {
carray[i] *= 2; // 修改数组元素
}
env->ReleaseIntArrayElements(jarray, carray, 0); // 同步修改回Java
// C数组 → Java int[]
jint carr[] = {1, 2, 3, 4, 5};
jintArray jarray = env->NewIntArray(5);
env->SetIntArrayRegion(jarray, 0, 5, carr);
return jarray;
3. 调用Java方法
// 调用Java对象的方法
jclass clazz = env->GetObjectClass(thiz); // 获取类
// 获取方法ID(方法签名见下节)
jmethodID mid = env->GetMethodID(clazz, "javaMethod", "(I)V");
// 调用方法
env->CallVoidMethod(thiz, mid, 123);
4. 访问Java字段
// 获取字段ID
jfieldID fid = env->GetFieldID(clazz, "count", "I");
// 读取字段
jint count = env->GetIntField(thiz, fid);
// 修改字段
env->SetIntField(thiz, fid, count + 1);
5. 创建Java对象
// 获取类
jclass clazz = env->FindClass("java/lang/String");
// 获取构造方法
jmethodID constructor = env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;)V");
// 创建对象
jstring param = env->NewStringUTF("Hello");
jobject obj = env->NewObject(clazz, constructor, param);
14.6 JNI方法签名
14.6.1 方法签名规则
格式:(参数类型)返回值类型
基本类型签名:
| Java类型 | 签名 |
|---|---|
| boolean | Z |
| byte | B |
| char | C |
| short | S |
| int | I |
| long | J |
| float | F |
| double | D |
| void | V |
引用类型签名:
| Java类型 | 签名 |
|---|---|
| Object | Ljava/lang/Object; |
| String | Ljava/lang/String; |
| int[] | [I |
| String[] | [Ljava/lang/String; |
14.6.2 方法签名示例
示例1:无参无返回
public void method()
签名:()V
示例2:int参数,String返回
public String method(int a)
签名:(I)Ljava/lang/String;
示例3:多个参数
public int method(String str, int num, boolean flag)
签名:(Ljava/lang/String;IZ)I
示例4:数组参数
public void method(int[] array)
签名:([I)V
14.6.3 自动生成签名
方式1:javap命令
# 编译Java类
javac JNIHelper.java
# 查看方法签名
javap -s JNIHelper
# 输出示例
public native java.lang.String stringFromJNI();
descriptor: ()Ljava/lang/String;
public native int add(int, int);
descriptor: (II)I
方式2:在线工具
- http://www.javadecompilers.com/jni
14.7 JNI调用Java方法
14.7.1 调用实例方法
// Java类
public class JNIHelper {
public void javaMethod(int value) {
Log.d("JNI", "javaMethod called: " + value);
}
public native void callJavaMethod();
}
// C++实现
extern "C" JNIEXPORT void JNICALL
Java_com_example_JNIHelper_callJavaMethod(JNIEnv* env, jobject thiz) {
// 1. 获取类
jclass clazz = env->GetObjectClass(thiz);
// 2. 获取方法ID
jmethodID mid = env->GetMethodID(clazz, "javaMethod", "(I)V");
if (mid == NULL) {
__android_log_print(ANDROID_LOG_ERROR, "JNI", "Method not found");
return;
}
// 3. 调用方法
env->CallVoidMethod(thiz, mid, 123);
// 4. 检查异常
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
14.7.2 调用静态方法
public class JNIHelper {
public static void staticMethod(String str) {
Log.d("JNI", "staticMethod: " + str);
}
}
extern "C" JNIEXPORT void JNICALL
Java_com_example_JNIHelper_callStaticMethod(JNIEnv* env, jobject thiz) {
// 1. 获取类(使用FindClass)
jclass clazz = env->FindClass("com/example/JNIHelper");
// 2. 获取静态方法ID
jmethodID mid = env->GetStaticMethodID(clazz, "staticMethod", "(Ljava/lang/String;)V");
// 3. 调用静态方法
jstring param = env->NewStringUTF("Hello from C++");
env->CallStaticVoidMethod(clazz, mid, param);
}
14.7.3 调用父类方法
// 调用父类的方法(非虚方法调用)
jclass superClazz = env->GetSuperclass(clazz);
jmethodID mid = env->GetMethodID(superClazz, "superMethod", "()V");
env->CallNonvirtualVoidMethod(thiz, superClazz, mid);
14.8 JNI引用类型
14.8.1 三种引用类型
| 引用类型 | 生命周期 | 使用场景 |
|---|---|---|
| 局部引用(Local Reference) | 函数返回后自动释放 | 大部分情况 |
| 全局引用(Global Reference) | 手动释放 | 跨函数、跨线程使用 |
| 弱全局引用(Weak Global Reference) | 不阻止GC | 缓存Java对象 |
14.8.2 局部引用
特点:
- JNI函数默认返回局部引用
- 函数返回后自动释放
- 不能跨函数、跨线程使用
示例:
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_localRefDemo(JNIEnv* env, jobject thiz) {
jstring str = env->NewStringUTF("Hello"); // 局部引用
// 函数结束,str自动释放
// 手动释放(可选)
env->DeleteLocalRef(str);
}
⚠️ 局部引用表溢出:
// 错误示例:循环创建大量局部引用
for (int i = 0; i < 100000; i++) {
jstring str = env->NewStringUTF("Hello"); // ❌ 局部引用表溢出
// 未释放
}
// 正确做法
for (int i = 0; i < 100000; i++) {
jstring str = env->NewStringUTF("Hello");
// ... 使用str
env->DeleteLocalRef(str); // ✅ 手动释放
}
14.8.3 全局引用
使用场景:
- 在多个函数间共享Java对象
- 在C++成员变量中保存Java对象
- 在子线程中使用Java对象
示例:
jclass g_clazz = NULL; // 全局变量
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_init(JNIEnv* env, jobject thiz) {
// 创建全局引用
jclass localClazz = env->FindClass("com/example/MyClass");
g_clazz = (jclass)env->NewGlobalRef(localClazz);
// 释放局部引用
env->DeleteLocalRef(localClazz);
}
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_destroy(JNIEnv* env, jobject thiz) {
// 释放全局引用
if (g_clazz != NULL) {
env->DeleteGlobalRef(g_clazz);
g_clazz = NULL;
}
}
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_useGlobalRef(JNIEnv* env, jobject thiz) {
// 使用全局引用
jmethodID mid = env->GetMethodID(g_clazz, "method", "()V");
// ...
}
14.8.4 弱全局引用
特点:
- 不阻止Java对象被GC回收
- 使用前需检查是否已被回收
示例:
jweak g_weakRef = NULL;
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_setWeakRef(JNIEnv* env, jobject thiz, jobject obj) {
// 创建弱全局引用
g_weakRef = env->NewWeakGlobalRef(obj);
}
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_useWeakRef(JNIEnv* env, jobject thiz) {
// 检查对象是否被回收
if (env->IsSameObject(g_weakRef, NULL)) {
__android_log_print(ANDROID_LOG_WARN, "JNI", "Object has been GCed");
return;
}
// 使用弱引用
// ...
}
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_destroyWeakRef(JNIEnv* env, jobject thiz) {
if (g_weakRef != NULL) {
env->DeleteWeakGlobalRef(g_weakRef);
g_weakRef = NULL;
}
}
14.9 JNI异常处理
14.9.1 检查异常
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_exceptionDemo(JNIEnv* env, jobject thiz) {
jclass clazz = env->FindClass("com/example/NonExistClass");
// 方式1:检查是否有异常
if (env->ExceptionCheck()) {
env->ExceptionDescribe(); // 打印异常堆栈到logcat
env->ExceptionClear(); // 清除异常
return;
}
// 方式2:检查返回值
if (clazz == NULL) {
// 发生了异常
env->ExceptionClear();
return;
}
}
14.9.2 抛出异常到Java
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_throwException(JNIEnv* env, jobject thiz) {
// 方式1:抛出指定异常类
jclass exceptionClazz = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(exceptionClazz, "Invalid argument from native");
// 方式2:抛出已存在的异常对象
jthrowable exception = ... ; // 获取异常对象
env->Throw(exception);
}
Java端捕获:
try {
jniHelper.throwException();
} catch (IllegalArgumentException e) {
Log.e("JNI", "Native exception: " + e.getMessage());
}
14.10 JNI线程操作
14.10.1 在Native线程中调用Java
问题:
- JNIEnv是线程局部的
- 子线程没有JNIEnv
解决方案:
JavaVM* g_jvm = NULL; // 全局保存JavaVM
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_jvm = vm; // 保存JavaVM
return JNI_VERSION_1_6;
}
void* thread_function(void* arg) {
JNIEnv* env;
// 将当前线程附加到JavaVM
int status = g_jvm->AttachCurrentThread(&env, NULL);
if (status != JNI_OK) {
return NULL;
}
// 现在可以使用env调用Java方法
jclass clazz = env->FindClass("com/example/JNIHelper");
jmethodID mid = env->GetStaticMethodID(clazz, "callback", "()V");
env->CallStaticVoidMethod(clazz, mid);
// 分离线程
g_jvm->DetachCurrentThread();
return NULL;
}
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_startNativeThread(JNIEnv* env, jobject thiz) {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
pthread_detach(thread);
}
14.10.2 JNI_OnLoad和JNI_OnUnload
JNI_OnLoad:
- 动态库加载时调用(
System.loadLibrary()) - 用于初始化(保存JavaVM、注册Native方法)
JNI_OnUnload:
- 动态库卸载时调用
- 用于清理资源
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
__android_log_print(ANDROID_LOG_INFO, "JNI", "JNI_OnLoad called");
g_jvm = vm;
// 获取JNIEnv
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// 动态注册Native方法(可选)
// registerNativeMethods(env);
return JNI_VERSION_1_6;
}
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) {
__android_log_print(ANDROID_LOG_INFO, "JNI", "JNI_OnUnload called");
// 清理全局引用等资源
JNIEnv* env;
vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (g_clazz != NULL) {
env->DeleteGlobalRef(g_clazz);
g_clazz = NULL;
}
}
14.11 JNI性能优化
14.11.1 减少JNI调用次数
问题:
JNI调用有开销(参数转换、类型检查)
优化:
// ❌ 不好:频繁调用JNI
for (int i = 0; i < 1000; i++) {
nativeProcess(i); // 调用1000次
}
// ✅ 好:批量处理
int[] data = new int[1000];
for (int i = 0; i < 1000; i++) {
data[i] = i;
}
nativeProcessBatch(data); // 只调用1次
14.11.2 缓存jclass和jmethodID
问题:
每次查找类和方法ID都有开销
优化:
// ❌ 不好:每次都查找
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_method(JNIEnv* env, jobject thiz) {
jclass clazz = env->FindClass("com/example/MyClass"); // 每次都查找
jmethodID mid = env->GetMethodID(clazz, "method", "()V");
env->CallVoidMethod(thiz, mid);
}
// ✅ 好:缓存到全局变量
jclass g_clazz = NULL;
jmethodID g_mid = NULL;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
vm->GetEnv((void**)&env, JNI_VERSION_1_6);
// 缓存jclass
jclass localClazz = env->FindClass("com/example/MyClass");
g_clazz = (jclass)env->NewGlobalRef(localClazz);
env->DeleteLocalRef(localClazz);
// 缓存jmethodID(methodID不需要创建全局引用)
g_mid = env->GetMethodID(g_clazz, "method", "()V");
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_example_JNIHelper_method(JNIEnv* env, jobject thiz) {
// 直接使用缓存的ID
env->CallVoidMethod(thiz, g_mid);
}
14.11.3 使用Critical Native(Android 8.0+)
特点:
- 更快的JNI调用(无需JNIEnv参数)
- 限制:不能调用JNI函数、不能触发GC
声明:
// Java端使用@CriticalNative注解
@CriticalNative
public native int fastAdd(int a, int b);
实现:
// 无需JNIEnv和jobject参数
extern "C" JNIEXPORT jint
Java_com_example_JNIHelper_fastAdd(jint a, jint b) {
return a + b; // 只能使用C++原生操作
}
14.12 常见JNI错误
14.12.1 局部引用表溢出
错误信息:
JNI ERROR: local reference table overflow (max=512)
原因:
循环中创建大量局部引用未释放
解决:
for (int i = 0; i < 100000; i++) {
jstring str = env->NewStringUTF("Hello");
// 使用str...
env->DeleteLocalRef(str); // 释放局部引用
}
14.12.2 跨线程使用JNIEnv
错误信息:
JNI WARNING: threadid=XXX using env from threadid=YYY
原因:
在子线程使用主线程的JNIEnv
解决:
// 在子线程中附加到JavaVM
JNIEnv* env;
g_jvm->AttachCurrentThread(&env, NULL);
// 使用env...
g_jvm->DetachCurrentThread();
14.12.3 返回错误的引用类型
错误:
// ❌ 返回局部引用,函数返回后被释放
JNIEXPORT jobject JNICALL
Java_com_example_JNIHelper_getObject(JNIEnv* env, jobject thiz) {
jclass clazz = env->FindClass("com/example/MyClass");
return clazz; // 局部引用,返回后无效
}
// ✅ 返回全局引用
JNIEXPORT jobject JNICALL
Java_com_example_JNIHelper_getObject(JNIEnv* env, jobject thiz) {
jclass localClazz = env->FindClass("com/example/MyClass");
jclass globalClazz = (jclass)env->NewGlobalRef(localClazz);
env->DeleteLocalRef(localClazz);
return globalClazz; // 需要在Java端或后续调用中释放
}
14.13 实战案例
14.13.1 图像处理(像素操作)
Java端:
public class ImageProcessor {
static {
System.loadLibrary("image-processor");
}
public native void processImage(Bitmap bitmap);
}
C++实现:
#include <android/bitmap.h>
extern "C" JNIEXPORT void JNICALL
Java_com_example_ImageProcessor_processImage(JNIEnv* env, jobject thiz, jobject bitmap) {
AndroidBitmapInfo info;
void* pixels;
// 获取Bitmap信息
AndroidBitmap_getInfo(env, bitmap, &info);
// 锁定Bitmap像素缓冲区
AndroidBitmap_lockPixels(env, bitmap, &pixels);
// 处理像素(例如:灰度化)
uint32_t* pixel = (uint32_t*)pixels;
for (int y = 0; y < info.height; y++) {
for (int x = 0; x < info.width; x++) {
uint32_t p = pixel[y * info.width + x];
uint8_t r = (p >> 16) & 0xFF;
uint8_t g = (p >> 8) & 0xFF;
uint8_t b = p & 0xFF;
// 灰度值 = 0.299R + 0.587G + 0.114B
uint8_t gray = (uint8_t)(0.299 * r + 0.587 * g + 0.114 * b);
pixel[y * info.width + x] = (0xFF << 24) | (gray << 16) | (gray << 8) | gray;
}
}
// 解锁Bitmap
AndroidBitmap_unlockPixels(env, bitmap);
}
CMakeLists.txt:
find_library(jnigraphics-lib jnigraphics)
target_link_libraries(image-processor ${jnigraphics-lib})
14.13.2 音视频编解码(FFmpeg集成)
引入FFmpeg:
# 添加预编译的FFmpeg库
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavcodec.so)
target_link_libraries(native-lib avcodec avformat avutil)
使用示例:
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
JNIEXPORT jint JNICALL
Java_com_example_VideoDecoder_decode(JNIEnv* env, jobject thiz, jstring path) {
const char* filePath = env->GetStringUTFChars(path, NULL);
AVFormatContext* formatCtx = avformat_alloc_context();
avformat_open_input(&formatCtx, filePath, NULL, NULL);
avformat_find_stream_info(formatCtx, NULL);
// 解码逻辑...
avformat_close_input(&formatCtx);
env->ReleaseStringUTFChars(path, filePath);
return 0;
}
本章总结
核心知识点
| 知识点 | 重要性 | 难度 |
|---|---|---|
| JNI基本概念 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 数据类型映射 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| JNIEnv使用 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 方法签名 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 引用类型管理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 异常处理 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 线程操作 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 性能优化 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
开发流程速查
1. 在Java中声明native方法
↓
2. 加载so库:System.loadLibrary("native-lib")
↓
3. 编写C/C++实现(遵循命名规范)
↓
4. 配置CMakeLists.txt
↓
5. 编译生成.so库
↓
6. 在Java中调用native方法
常见应用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 图像处理 | 像素级操作性能要求高 | 美颜、滤镜、图像识别 |
| 音视频 | 编解码、格式转换 | FFmpeg、OpenSL ES |
| 游戏引擎 | 3D渲染、物理引擎 | Unity、Cocos2d-x |
| 加密算法 | 安全性要求高 | RSA、AES加密 |
| 科学计算 | 大量数学运算 | 机器学习、信号处理 |
| 第三方库集成 | 复用C/C++库 | OpenCV、Protobuf |
面试高频问题
Q1: JNI的工作原理?
答:Java通过native关键字声明方法,JVM在运行时查找对应的C/C++函数(根据命名规范),通过JNIEnv调用。
Q2: 局部引用和全局引用的区别?
答:局部引用函数返回后自动释放,全局引用需要手动释放;局部引用不能跨线程,全局引用可以。
Q3: 如何在Native线程中调用Java方法?
答:使用JavaVM的AttachCurrentThread()获取JNIEnv,使用完后调用DetachCurrentThread()。
Q4: JNI性能优化的方法?
答:1) 减少JNI调用次数(批量处理);2) 缓存jclass和jmethodID;3) 使用Critical Native(Android 8.0+);4) 避免频繁的字符串转换。
Q5: 如何排查JNI崩溃?
答:1) 使用ndk-stack解析崩溃日志;2) 使用Android Studio的Native调试;3) 添加日志打印定位问题;4) 检查JNIEnv使用是否正确(线程安全、引用释放)。
更多推荐
所有评论(0)