安卓学习思路方法总结(五)
字数 2329 2025-08-10 08:28:55
Android JNI 开发全面指南
一、JNI 基础概念
1.1 什么是 JNI
JNI (Java Native Interface) 是 Java 本地接口的缩写,从 Java 1.1 开始成为 Java 平台的标准部分。它允许 Java 代码与其他语言(主要是 C 和 C++)编写的代码进行交互。
1.2 JNI 的主要作用
- 实现 Java 层与 Native 层(C/C++)相互调用
- 作为 Java 和本地代码之间的桥梁
1.3 JNI 的特点
- 平台移植性:使用 JNI 通常会丧失平台可移植性,但在某些情况下是必要且可接受的
- 强大功能:结合 Java 的成熟框架和底层 C/C++/汇编代码,可以释放更强大的功能
二、JNI 核心接口与类型
2.1 JNI 接口函数分类
-
调用 Java 方法:
CallObjectMethod- 调用方法,返回 ObjectCall<Type>Method- 调用返回特定类型的方法(如CallIntMethod)
-
字段操作:
GetFieldID- 获取字段 IDGet<Type>Field- 获取实例字段的值Set<Type>Field- 设置实例字段的值
-
静态成员操作:
GetStaticMethodID- 获取静态方法 IDCallStatic<Type>Method- 调用静态方法GetStatic<Type>Field- 获取静态字段的值SetStatic<Type>Field- 设置静态字段的值
2.2 JNI 基本数据类型
JNI 定义了一套与 Java 类型对应的原生类型:
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
typedef jint jsize; /* 表示数组的大小 */
2.3 JNI 引用类型
在 C++ 中:
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
// 其他数组类型继承自 _jarray
在 C 中:
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
// 其他数组类型
三、JNI 开发实践
3.1 静态注册 Native 方法
- 在 Java 类中声明 native 方法:
public native CharSequence Getstring();
- 使用 javah 生成头文件:
javah -jni com.example.aaa.MainActivity
- 实现 native 方法:
JNIEXPORT jobject JNICALL Java_com_example_aaa_MainActivity_Getstring
(JNIEnv *env, jobject obj) {
// 实现代码
}
3.2 动态注册 Native 方法
动态注册使用 JNI_OnLoad 函数和 JNINativeMethod 结构体:
typedef struct {
const char* name; // Java 方法名
const char* signature; // 方法签名
void* fnPtr; // 函数指针
} JNINativeMethod;
示例:
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// 注册native方法
JNINativeMethod methods[] = {
{"Getstring", "()Ljava/lang/CharSequence;", (void*)native_Getstring}
};
jclass clazz = (*env)->FindClass(env, "com/example/aaa/MainActivity");
if ((*env)->RegisterNatives(env, clazz, methods, sizeof(methods)/sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
3.3 方法签名
JNI 使用特定的签名格式描述方法的参数和返回值:
-
基本类型:
- Z: boolean
- B: byte
- C: char
- S: short
- I: int
- J: long
- F: float
- D: double
- V: void
-
引用类型:
- Lfully/qualified/ClassName; - 如 Ljava/lang/String;
- [type - 数组,如 [I 表示 int[]
示例:
- "()V" - 无参数,返回 void
- "(II)V" - 两个 int 参数,返回 void
- "(Ljava/lang/String;)Z" - 一个 String 参数,返回 boolean
四、JNI 常用函数详解
4.1 类操作函数
jclass DefineClass(JNIEnv*, const char*, jobject, const jbyte*, jsize);
jclass FindClass(JNIEnv*, const char*);
jclass GetSuperclass(JNIEnv*, jclass);
jboolean IsAssignableFrom(JNIEnv*, jclass, jclass);
4.2 异常处理函数
jint Throw(JNIEnv*, jthrowable);
jint ThrowNew(JNIEnv*, jclass, const char*);
jthrowable ExceptionOccurred(JNIEnv*);
void ExceptionDescribe(JNIEnv*);
void ExceptionClear(JNIEnv*);
void FatalError(JNIEnv*, const char*);
4.3 对象操作函数
jobject AllocObject(JNIEnv*, jclass);
jobject NewObject(JNIEnv*, jclass, jmethodID, ...);
jobject NewObjectV(JNIEnv*, jclass, jmethodID, va_list);
jobject NewObjectA(JNIEnv*, jclass, jmethodID, jvalue*);
jclass GetObjectClass(JNIEnv*, jobject);
jboolean IsInstanceOf(JNIEnv*, jobject, jclass);
4.4 方法调用函数
// 调用实例方法
jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...);
jboolean CallBooleanMethod(JNIEnv*, jobject, jmethodID, ...);
// 其他基本类型类似...
// 调用静态方法
jobject CallStaticObjectMethod(JNIEnv*, jclass, jmethodID, ...);
jboolean CallStaticBooleanMethod(JNIEnv*, jclass, jmethodID, ...);
// 其他基本类型类似...
4.5 字段操作函数
// 获取字段ID
jfieldID GetFieldID(JNIEnv*, jclass, const char*, const char*);
// 获取字段值
jobject GetObjectField(JNIEnv*, jobject, jfieldID);
jboolean GetBooleanField(JNIEnv*, jobject, jfieldID);
// 其他基本类型类似...
// 设置字段值
void SetObjectField(JNIEnv*, jobject, jfieldID, jobject);
void SetBooleanField(JNIEnv*, jobject, jfieldID, jboolean);
// 其他基本类型类似...
4.6 字符串操作函数
jstring NewString(JNIEnv*, const jchar*, jsize);
jsize GetStringLength(JNIEnv*, jstring);
const jchar* GetStringChars(JNIEnv*, jstring, jboolean*);
void ReleaseStringChars(JNIEnv*, jstring, const jchar*);
jstring NewStringUTF(JNIEnv*, const char*);
jsize GetStringUTFLength(JNIEnv*, jstring);
const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*);
void ReleaseStringUTFChars(JNIEnv*, jstring, const char*);
4.7 数组操作函数
jsize GetArrayLength(JNIEnv*, jarray);
// 创建数组
jobjectArray NewObjectArray(JNIEnv*, jsize, jclass, jobject);
jbooleanArray NewBooleanArray(JNIEnv*, jsize);
// 其他基本类型数组类似...
// 获取数组元素
jboolean* GetBooleanArrayElements(JNIEnv*, jbooleanArray, jboolean*);
// 其他基本类型类似...
// 释放数组元素
void ReleaseBooleanArrayElements(JNIEnv*, jbooleanArray, jboolean*, jint);
// 其他基本类型类似...
// 数组区域操作
void GetBooleanArrayRegion(JNIEnv*, jbooleanArray, jsize, jsize, jboolean*);
void SetBooleanArrayRegion(JNIEnv*, jbooleanArray, jsize, jsize, const jboolean*);
// 其他基本类型类似...
五、JNI 开发流程
5.1 开发步骤
- 编写 Java 类:声明 native 方法
- 生成头文件:使用 javah 命令
- 实现 C/C++ 代码:根据头文件实现 native 方法
- 编译动态库:使用 NDK 编译生成 .so 文件
- 加载动态库:在 Java 中使用 System.loadLibrary()
5.2 NDK 编译
-
创建 JNI 目录,包含:
- 源文件(如 aaa.c)
- Application.mk
- Android.mk
-
在 JNI 目录下执行:
ndk-build
5.3 示例:从 Native 层返回字符串
- Java 代码:
public class MainActivity {
public native CharSequence Getstring();
static {
System.loadLibrary("native-lib");
}
void showString() {
Toast.makeText(this, Getstring(), Toast.LENGTH_SHORT).show();
}
}
- C 实现:
#include <jni.h>
JNIEXPORT jstring JNICALL Java_com_example_aaa_MainActivity_Getstring
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello from JNI!");
}
六、JNI 高级主题
6.1 引用管理
-
局部引用:
- 在 native 方法中创建的大多数对象都是局部引用
- 方法返回时自动释放
- 可以手动释放:
DeleteLocalRef
-
全局引用:
- 使用
NewGlobalRef创建 - 必须手动释放:
DeleteGlobalRef - 跨多个 native 调用有效
- 使用
-
弱全局引用:
- 使用
NewWeakGlobalRef创建 - 不会阻止垃圾回收
- 必须手动释放:
DeleteWeakGlobalRef
- 使用
6.2 线程与 JNI
- AttachCurrentThread:将当前线程附加到 JVM
- DetachCurrentThread:从 JVM 分离当前线程
- 线程局部存储:使用
PushLocalFrame和PopLocalFrame管理局部引用
6.3 直接缓冲区
jobject NewDirectByteBuffer(JNIEnv*, void*, jlong);
void* GetDirectBufferAddress(JNIEnv*, jobject);
jlong GetDirectBufferCapacity(JNIEnv*, jobject);
七、JNI 最佳实践
- 错误检查:每次 JNI 调用后检查异常
- 资源释放:及时释放获取的资源(字符串、数组等)
- 性能优化:
- 减少 JNI 调用次数
- 使用直接缓冲区处理大量数据
- 缓存方法 ID 和字段 ID
- 线程安全:注意多线程环境下的 JNI 使用
- 内存管理:避免在本地代码中长时间持有 Java 对象引用
八、常见问题与解决方案
-
UnsatisfiedLinkError:
- 检查库名是否正确
- 确认库已正确打包到 APK
- 检查 ABI 兼容性
-
NoSuchMethodError:
- 检查方法签名是否正确
- 确认方法是否静态/非静态
-
性能问题:
- 减少 JNI 边界调用
- 批量处理数据而非单个处理
-
内存泄漏:
- 确保释放全局引用
- 使用局部引用帧管理局部引用
通过掌握这些 JNI 知识和技巧,开发者可以有效地在 Android 应用中结合 Java 和本地代码,实现高性能、低级别的功能。