ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

JNI开发——如何在 Native 层访问 Java 的属性和方法

JNI开发——如何在 Native 层访问 Java 的属性和方法 在 Android 系统开发中JNI (Java Native Interface) 是连接 Java 世界与 C/C 世界的桥梁。深入理解如何在 Native 层访问 Java 的属性和方法是进行系统级开发、性能优化和底层调试的必备技能。这篇指南将以系统开发的视角为你提供一份详尽的 JNI 访问实战手册并补充一些高阶技巧和避坑指南。 1. 基础概念速览方法签名与 JNI 类型在开始之前我们必须厘清两个核心概念这是编写健壮 JNI 代码的基础。方法签名 (Method Signature)由于 Java 支持方法重载JNI 需要通过方法名 签名来唯一确定一个方法。签名描述了方法的参数类型和返回值类型。格式(参数类型签名)返回值类型签名示例void setName(String name)-(Ljava/lang/String;)Vint add(int a, int b)-(II)I快速获取技巧使用javap -s 类名.class命令可以直接反编译出类的所有方法签名可以避免手写出错。JNI 类型映射Java 的基本类型和引用类型在 JNI 中都有对应的类型如下表所示。Java 类型JNI 类型签名字符booleanjbooleanZbytejbyteBcharjcharCshortjshortSintjintIlongjlongJfloatjfloatFdoublejdoubleDvoidvoidVStringjstringLjava/lang/String;ObjectjobjectLjava/lang/Object;int[]jintArray[I️ 2. 访问 Java 属性 (Field)在 Native 层访问 Java 对象的属性无论是实例属性还是静态属性都遵循“先获取 ID再操作数据”的模式。Java 层示例javapublic class User { // 实例属性 public String name Default Name; // 静态属性 public static int totalCount 0; }2.1 访问实例属性 (Instance Field)实例属性依附于具体的对象实例。C 层操作步骤获取jclass通过GetObjectClass函数从对象引用获取其对应的类。获取jfieldID调用GetFieldID传入jclass、属性名和属性签名。读取/修改属性使用GetObjectField/SetObjectField等系列函数进行操作。代码实现cpp// 假设 Java 层有 native 方法: public native void modifyUser(User user); JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_modifyUser(JNIEnv *env, jobject thiz, jobject userObj) { // 1. 从对象获取类 jclass userClass env-GetObjectClass(userObj); if (userClass nullptr) { return; // 异常处理 } // 2. 获取 name 字段的 ID jfieldID nameFieldId env-GetFieldID(userClass, name, Ljava/lang/String;); if (nameFieldId nullptr) { env-DeleteLocalRef(userClass); return; // 异常处理 } // 3. 读取 name 字段的值 jstring jName (jstring) env-GetObjectField(userObj, nameFieldId); const char *cName env-GetStringUTFChars(jName, nullptr); if (cName ! nullptr) { // 对 cName 进行操作例如打印日志 env-ReleaseStringUTFChars(jName, cName); // 必须释放 } env-DeleteLocalRef(jName); // 4. 修改 name 字段的值 jstring newName env-NewStringUTF(New Name from JNI); if (newName ! nullptr) { env-SetObjectField(userObj, nameFieldId, newName); // 注意newName 作为局部引用在函数返回后会被自动释放或需手动 DeleteLocalRef env-DeleteLocalRef(newName); } env-DeleteLocalRef(userClass); }2.2 访问静态属性 (Static Field)静态属性属于类本身操作时不需要对象实例直接使用jclass即可。关键区别使用GetStaticFieldID获取 ID并使用GetStaticObjectField/SetStaticObjectField等函数进行操作。代码实现cpp// 假设 Java 层有 native 方法: public native void modifyStaticField(); JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_modifyStaticField(JNIEnv *env, jobject thiz) { // 1. 通过类名获取 jclass jclass userClass env-FindClass(com/example/jnidemo/User); if (userClass nullptr) { return; } // 2. 获取静态字段 totalCount 的 ID jfieldID totalCountFieldId env-GetStaticFieldID(userClass, totalCount, I); if (totalCountFieldId nullptr) { env-DeleteLocalRef(userClass); return; } // 3. 读取静态字段的值 jint count env-GetStaticIntField(userClass, totalCountFieldId); // 4. 修改静态字段的值 env-SetStaticIntField(userClass, totalCountFieldId, count 1); env-DeleteLocalRef(userClass); } 3. 调用 Java 方法 (Method)JNI 允许 Native 代码回调 Java 层的方法这常用于异步通知或事件处理。3.1 调用实例方法 (Instance Method)实例方法需要依附于具体的对象实例来调用。C 层操作步骤获取jclass通过FindClass或GetObjectClass获取。获取jmethodID调用GetMethodID传入jclass、方法名和方法签名。调用方法使用CallXXXMethod系列函数进行调用XXX代表返回类型。代码实现cpp// Java 层: public void printMessage(String msg) { Log.d(JNI, msg); } // 假设 Java 层有 native 方法: public native void callJavaMethod(User user); JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_callJavaMethod(JNIEnv *env, jobject thiz, jobject userObj) { // 1. 获取类 jclass userClass env-GetObjectClass(userObj); if (userClass nullptr) return; // 2. 获取 printMessage 方法的 ID jmethodID printMethodId env-GetMethodID(userClass, printMessage, (Ljava/lang/String;)V); if (printMethodId nullptr) { env-DeleteLocalRef(userClass); return; } // 3. 构造参数并调用方法 jstring msg env-NewStringUTF(Hello from JNI!); env-CallVoidMethod(userObj, printMethodId, msg); // 4. 释放资源 env-DeleteLocalRef(msg); env-DeleteLocalRef(userClass); }3.2 调用静态方法 (Static Method)静态方法属于类通过jclass即可调用。关键区别使用GetStaticMethodID获取 ID并使用CallStaticXXXMethod函数进行调用。代码实现cpp// Java 层: public static void staticMethod() { Log.d(JNI, Static method called); } JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_callStaticMethod(JNIEnv *env, jobject thiz) { // 1. 获取类 jclass userClass env-FindClass(com/example/jnidemo/User); if (userClass nullptr) return; // 2. 获取静态方法 ID jmethodID staticMethodId env-GetStaticMethodID(userClass, staticMethod, ()V); if (staticMethodId nullptr) { env-DeleteLocalRef(userClass); return; } // 3. 调用静态方法 env-CallStaticVoidMethod(userClass, staticMethodId); env-DeleteLocalRef(userClass); }⚡️ 4. 性能优化缓存jfieldID和jmethodID在系统级开发中FindClass、GetFieldID、GetMethodID等操作因为涉及字符串查找和类加载是相对耗时的。如果在高频场景如音视频处理循环中每次都重新查找会严重影响性能。最佳实践是进行缓存。jfieldID和jmethodID在 JVM 进程的生命周期内是不会改变的可以安全地缓存起来重复使用。4.1 使用时缓存 (Lazy Cache)在第一次使用时查找并保存在static局部变量中。优点实现简单。缺点每次调用仍需判空在多线程环境下可能存在线程安全问题。cppJNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_useCache(JNIEnv *env, jobject thiz, jobject obj) { static jfieldID fieldId nullptr; // 声明为 static 进行缓存[reference:30] if (fieldId nullptr) { jclass clazz env-GetObjectClass(obj); fieldId env-GetFieldID(clazz, name, Ljava/lang/String;); env-DeleteLocalRef(clazz); if (fieldId nullptr) return; } // 直接使用缓存的 fieldId... }4.2 初始化时缓存 (Initialize Cache)在 Java 类加载时如静态代码块中主动调用一个 native 方法完成所有 ID 的查找和缓存。优点线程安全无运行时判空开销性能最佳。缺点需要额外增加一个初始化方法。java// Java 层 public class User { static { initIDs(); // 类加载时调用缓存 ID } private static native void initIDs(); // ... 其他代码 }cpp// C 层全局变量缓存 ID jfieldID cached_nameFieldId nullptr; JNIEXPORT void JNICALL Java_com_example_jnidemo_User_initIDs(JNIEnv *env, jclass clazz) { cached_nameFieldId env-GetFieldID(clazz, name, Ljava/lang/String;); // 可以在此处缓存更多 ID } // 之后在任意 JNI 函数中直接使用 cached_nameFieldId无需判空 5. 系统开发者的进阶指南作为系统开发者以下内存管理知识是必须深入理解的。5.1 引用的生命周期管理JNI 中有三种引用类型管理不当是内存问题的主要来源。局部引用 (Local Reference)特点大多数 JNI 函数返回的默认引用类型。只在当前 Native 方法执行期间有效方法返回后自动释放。风险局部引用表大小有限Android 中通常为 512 个。在循环中创建大量局部引用而不释放会导致溢出崩溃。管理使用DeleteLocalRef及时手动释放不再需要的局部引用。全局引用 (Global Reference)特点通过NewGlobalRef创建显式调用DeleteGlobalRef之前一直有效可跨线程使用。用途非常适合缓存jclass对象因为FindClass返回的是局部引用不能跨方法使用。风险必须手动释放否则会导致内存泄漏。弱全局引用 (Weak Global Reference)特点通过NewWeakGlobalRef创建不阻止 GC 回收其引用的对象。用途缓存那些允许被 GC 回收的对象。风险使用前必须通过IsSameObject与NULL比较检查对象是否已被回收。5.2 异常处理JNI 函数如GetFieldID在失败时会返回NULL并在 JVM 中挂起一个异常。关键原则必须检查返回值。如果GetFieldID返回NULL不应继续执行而应立即返回让 Java 层通过try-catch处理异常或使用ExceptionClear清除异常。不检查的后果后续的 JNI 调用可能会导致程序直接崩溃。 6. 总结与对比为了方便对比和记忆这里总结了访问属性和方法的核心 API 差异操作类型获取 ID 的函数读取/调用函数 (示例)设置函数 (示例)实例属性GetFieldIDGetObjectFieldSetObjectField静态属性GetStaticFieldIDGetStaticObjectFieldSetStaticObjectField实例方法GetMethodIDCallVoidMethodN/A静态方法GetStaticMethodIDCallStaticVoidMethodN/A核心要点总结实例成员通过jobject对象实例操作静态成员通过jclass类操作。属性用Field方法用Method。Get/Set用于属性Call用于方法。包含Static的是静态成员相关 API。
返回列表