安卓学习思路方法总结(五)
字数 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 的主要作用

  1. 实现 Java 层与 Native 层(C/C++)相互调用
  2. 作为 Java 和本地代码之间的桥梁

1.3 JNI 的特点

  • 平台移植性:使用 JNI 通常会丧失平台可移植性,但在某些情况下是必要且可接受的
  • 强大功能:结合 Java 的成熟框架和底层 C/C++/汇编代码,可以释放更强大的功能

二、JNI 核心接口与类型

2.1 JNI 接口函数分类

  1. 调用 Java 方法

    • CallObjectMethod - 调用方法,返回 Object
    • Call<Type>Method - 调用返回特定类型的方法(如 CallIntMethod
  2. 字段操作

    • GetFieldID - 获取字段 ID
    • Get<Type>Field - 获取实例字段的值
    • Set<Type>Field - 设置实例字段的值
  3. 静态成员操作

    • GetStaticMethodID - 获取静态方法 ID
    • CallStatic<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 方法

  1. 在 Java 类中声明 native 方法:
public native CharSequence Getstring();
  1. 使用 javah 生成头文件:
javah -jni com.example.aaa.MainActivity
  1. 实现 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 开发步骤

  1. 编写 Java 类:声明 native 方法
  2. 生成头文件:使用 javah 命令
  3. 实现 C/C++ 代码:根据头文件实现 native 方法
  4. 编译动态库:使用 NDK 编译生成 .so 文件
  5. 加载动态库:在 Java 中使用 System.loadLibrary()

5.2 NDK 编译

  1. 创建 JNI 目录,包含:

    • 源文件(如 aaa.c)
    • Application.mk
    • Android.mk
  2. 在 JNI 目录下执行:

ndk-build

5.3 示例:从 Native 层返回字符串

  1. Java 代码:
public class MainActivity {
    public native CharSequence Getstring();
    
    static {
        System.loadLibrary("native-lib");
    }
    
    void showString() {
        Toast.makeText(this, Getstring(), Toast.LENGTH_SHORT).show();
    }
}
  1. 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 引用管理

  1. 局部引用

    • 在 native 方法中创建的大多数对象都是局部引用
    • 方法返回时自动释放
    • 可以手动释放:DeleteLocalRef
  2. 全局引用

    • 使用 NewGlobalRef 创建
    • 必须手动释放:DeleteGlobalRef
    • 跨多个 native 调用有效
  3. 弱全局引用

    • 使用 NewWeakGlobalRef 创建
    • 不会阻止垃圾回收
    • 必须手动释放:DeleteWeakGlobalRef

6.2 线程与 JNI

  1. AttachCurrentThread:将当前线程附加到 JVM
  2. DetachCurrentThread:从 JVM 分离当前线程
  3. 线程局部存储:使用 PushLocalFramePopLocalFrame 管理局部引用

6.3 直接缓冲区

jobject NewDirectByteBuffer(JNIEnv*, void*, jlong);
void* GetDirectBufferAddress(JNIEnv*, jobject);
jlong GetDirectBufferCapacity(JNIEnv*, jobject);

七、JNI 最佳实践

  1. 错误检查:每次 JNI 调用后检查异常
  2. 资源释放:及时释放获取的资源(字符串、数组等)
  3. 性能优化
    • 减少 JNI 调用次数
    • 使用直接缓冲区处理大量数据
    • 缓存方法 ID 和字段 ID
  4. 线程安全:注意多线程环境下的 JNI 使用
  5. 内存管理:避免在本地代码中长时间持有 Java 对象引用

八、常见问题与解决方案

  1. UnsatisfiedLinkError

    • 检查库名是否正确
    • 确认库已正确打包到 APK
    • 检查 ABI 兼容性
  2. NoSuchMethodError

    • 检查方法签名是否正确
    • 确认方法是否静态/非静态
  3. 性能问题

    • 减少 JNI 边界调用
    • 批量处理数据而非单个处理
  4. 内存泄漏

    • 确保释放全局引用
    • 使用局部引用帧管理局部引用

通过掌握这些 JNI 知识和技巧,开发者可以有效地在 Android 应用中结合 Java 和本地代码,实现高性能、低级别的功能。

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 - 调用方法,返回 Object Call<Type>Method - 调用返回特定类型的方法(如 CallIntMethod ) 字段操作 : GetFieldID - 获取字段 ID Get<Type>Field - 获取实例字段的值 Set<Type>Field - 设置实例字段的值 静态成员操作 : GetStaticMethodID - 获取静态方法 ID CallStatic<Type>Method - 调用静态方法 GetStatic<Type>Field - 获取静态字段的值 SetStatic<Type>Field - 设置静态字段的值 2.2 JNI 基本数据类型 JNI 定义了一套与 Java 类型对应的原生类型: 2.3 JNI 引用类型 在 C++ 中: 在 C 中: 三、JNI 开发实践 3.1 静态注册 Native 方法 在 Java 类中声明 native 方法: 使用 javah 生成头文件: 实现 native 方法: 3.2 动态注册 Native 方法 动态注册使用 JNI_OnLoad 函数和 JNINativeMethod 结构体: 示例: 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 类操作函数 4.2 异常处理函数 4.3 对象操作函数 4.4 方法调用函数 4.5 字段操作函数 4.6 字符串操作函数 4.7 数组操作函数 五、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 目录下执行: 5.3 示例:从 Native 层返回字符串 Java 代码: C 实现: 六、JNI 高级主题 6.1 引用管理 局部引用 : 在 native 方法中创建的大多数对象都是局部引用 方法返回时自动释放 可以手动释放: DeleteLocalRef 全局引用 : 使用 NewGlobalRef 创建 必须手动释放: DeleteGlobalRef 跨多个 native 调用有效 弱全局引用 : 使用 NewWeakGlobalRef 创建 不会阻止垃圾回收 必须手动释放: DeleteWeakGlobalRef 6.2 线程与 JNI AttachCurrentThread :将当前线程附加到 JVM DetachCurrentThread :从 JVM 分离当前线程 线程局部存储 :使用 PushLocalFrame 和 PopLocalFrame 管理局部引用 6.3 直接缓冲区 七、JNI 最佳实践 错误检查 :每次 JNI 调用后检查异常 资源释放 :及时释放获取的资源(字符串、数组等) 性能优化 : 减少 JNI 调用次数 使用直接缓冲区处理大量数据 缓存方法 ID 和字段 ID 线程安全 :注意多线程环境下的 JNI 使用 内存管理 :避免在本地代码中长时间持有 Java 对象引用 八、常见问题与解决方案 UnsatisfiedLinkError : 检查库名是否正确 确认库已正确打包到 APK 检查 ABI 兼容性 NoSuchMethodError : 检查方法签名是否正确 确认方法是否静态/非静态 性能问题 : 减少 JNI 边界调用 批量处理数据而非单个处理 内存泄漏 : 确保释放全局引用 使用局部引用帧管理局部引用 通过掌握这些 JNI 知识和技巧,开发者可以有效地在 Android 应用中结合 Java 和本地代码,实现高性能、低级别的功能。