- 学习了JNI中涉及到引用的API主要分为局部引用和全局引用两类,以及弱全局引用的特殊情况。
- 学习了如何使用JNI函数来创建、删除、检查和比较不同类型的引用。
- 学习了如何使用
PushLocalFrame()
和PopLocalFrame()
来管理局部引用的生命周期,并可选地返回一个全局引用。
JNI引用API的使用
学习了JNI中涉及到引用的API主要分为局部引用和全局引用两类,以及弱全局引用的特殊情况。局部引用是在函数内部创建和使用的,它们会随着函数返回而自动销毁。全局引用是在函数外部创建和使用的,它们需要手动删除才能释放内存。弱全局引用是一种特殊的全局引用,它不会阻止垃圾回收器回收对象,因此需要在使用前检查其有效性。
JNI中的全局引用、局部引用以及弱全局引用的主要作用是允许native代码访问和操作Java对象,同时管理它们的生命周期。在实际应用中,这些引用类型的使用取决于具体的需求和场景。
全局引用(Global References):
全局引用在native代码中创建,可以在任何线程中使用,不受局部引用帧的限制,只有在显式调用DeleteGlobalRef时才会被释放。这使得可以长时间或跨线程持有Java对象的引用。
举例来说,假设的应用在native代码中使用了一个长时间运行的线程,这个线程需要定期回调Java方法进行状态更新。在这种情况下,可能需要创建一个全局引用来持有Java回调对象,以确保它在整个线程的生命周期内都不会被垃圾收集器回收。
局部引用(Local References):
局部引用是在native方法调用期间创建的引用,它们只在创建它们的线程中有效,并且在native方法返回时会自动被删除。这可以让在临时操作Java对象时不需要手动管理内存。
例如,假设在一个native方法中访问了Java对象的一个字段。可以创建一个局部引用来持有这个字段的值,在方法返回时,局部引用会自动被删除,不需要手动删除它。
弱全局引用(Weak Global References):
弱全局引用是一种特殊的引用,它不会阻止其引用的对象被垃圾回收。它们在native代码中创建,可以在任何线程中使用,但是可能会被垃圾回收。
比如说,的应用可能会缓存一些Java对象以便快速访问。为了防止这些对象占用过多内存,可以使用弱全局引用来引用它们。这样,当内存不足时,垃圾收集器可以回收这些对象,从而避免内存泄漏。
引用类型 |
生命周期 |
作用范围 |
是否需要显式删除 |
特殊属性 |
全局引用 |
长(需要显式删除) |
在任何线程中使用 |
是 |
N/A |
局部引用 |
短(native方法返回时自动删除) |
只能在创建它的线程中使用 |
否 |
N/A |
弱全局引用 |
长(需要显式删除) |
在任何线程中使用 |
是 |
不会阻止其引用的对象被垃圾回收 |
函数 |
作用 |
|
创建局部引用帧,为即将创建的局部引用预留空间 |
|
删除局部引用帧,一次性删除帧中的所有局部引用,不需要逐个删除 |
以下是一些重要的引用API:
NewGlobalRef(JNIEnv *env, jobject obj)
: 创建一个全局引用。DeleteGlobalRef(JNIEnv *env, jobject globalRef)
: 删除一个全局引用。NewLocalRef(JNIEnv *env, jobject ref)
: 创建一个局部引用。DeleteLocalRef(JNIEnv *env, jobject localRef)
: 删除一个局部引用。PushLocalFrame(JNIEnv *env, jint capacity)
: 为即将创建的局部引用预留空间。PopLocalFrame(JNIEnv *env, jobject result)
: 销毁创建的局部引用,并可选地返回一个全局引用。NewWeakGlobalRef(JNIEnv *env, jobject obj)
: 创建一个弱全局引用。DeleteWeakGlobalRef(JNIEnv *env, jweak ref)
: 删除一个弱全局引用。IsSameObject(JNIEnv *env, jobject ref1, jobject ref2)
: 检查两个引用是否指向相同的对象。
完整实例
下面是C++中实现的一个测试函数,用于演示JNI引用API的使用。首先创建了一个全局引用和一个弱全局引用,并在每次调用时检查它们是否有效。然后创建了一些局部引用,并使用PushLocalFrame()
和PopLocalFrame()
来管理它们。最后使用IsSameObject()
来比较不同的引用是否指向同一个对象,并返回一个全局引用作为结果。
#include <jni.h> #include <string> #include <android/log.h> #define LOG_TAG "Chapter05-JNI" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) using namespace chapter05; // 使用chapter05命名空间 // 定义一些常量,表示类名和方法签名 const char* STRING_CLASS = "java/lang/String"; const char* OBJECT_CLASS = "java/lang/Object"; // 定义一些全局变量,表示全局引用和弱全局引用 jobject globalRef = NULL; // 全局引用 jweak weakGlobalRef = NULL; // 弱全局引用 // 定义一个辅助函数,用于检查并打印JNI异常 void checkAndPrintException(JNIEnv *env) { jthrowable exception = env->ExceptionOccurred(); // 检查是否发生了Java异常 if (exception != NULL) { env->ExceptionDescribe(); // 打印Java异常的堆栈跟踪信息 } } // 定义一个测试函数,用于演示JNI引用API的使用 jobject testReferenceAPI(JNIEnv *env) { // 创建一个字符串对象 jstring str = env->NewStringUTF("测试对象引用-obj"); checkAndPrintException(env); // 如果全局引用为空,就创建一个全局引用 if (globalRef == NULL) { globalRef = env->NewGlobalRef(str); checkAndPrintException(env); LOGD("创建了一个全局引用"); } else { LOGD("全局引用已存在"); } // 如果弱全局引用为空,就创建一个弱全局引用 if (weakGlobalRef == NULL) { weakGlobalRef = env->NewWeakGlobalRef(str); checkAndPrintException(env); LOGD("创建了一个弱全局引用"); } else { LOGD("弱全局引用已存在"); } // 检查全局引用是否有效 jboolean isGlobalRefValid = env->IsSameObject(globalRef, NULL); if (isGlobalRefValid == JNI_FALSE) { LOGD("全局引用有效"); } else { LOGD("全局引用无效"); } // 检查弱全局引用是否有效 jboolean isWeakGlobalRefValid = env->IsSameObject(weakGlobalRef, NULL); if (isWeakGlobalRefValid == JNI_FALSE) { LOGD("弱全局引用有效"); } else { LOGD("弱全局引用无效"); } // 为即将创建的局部引用预留空间 env->PushLocalFrame(16); checkAndPrintException(env); // 创建一个对象数组,用于存放局部引用 jobjectArray localRefs = env->NewObjectArray(10, env->FindClass(OBJECT_CLASS), NULL); checkAndPrintException(env); // 创建一些对象,并将它们作为局部引用添加到数组中 for (int i = 0; i < 10; i++) { jobject obj = env->NewStringUTF("Object"); checkAndPrintException(env); env->SetObjectArrayElement(localRefs, i, obj); checkAndPrintException(env); env->DeleteLocalRef(obj); // 删除不再使用的局部引用 checkAndPrintException(env); } // 获取数组中的第一个元素,并创建一个新的局部引用 jobject firstElement = env->GetObjectArrayElement(localRefs, 0); checkAndPrintException(env); jobject newLocalRef = env->NewLocalRef(firstElement); checkAndPrintException(env); // 比较两个局部引用是否指向同一个对象 jboolean isSameObject = env->IsSameObject(firstElement, newLocalRef); if (isSameObject == JNI_TRUE) { LOGD("两个局部引用指向同一个对象"); } else { LOGD("两个局部引用指向不同对象"); } // 销毁创建的局部引用,并返回一个全局引用作为结果 return env->PopLocalFrame(globalRef); } extern "C" JNIEXPORT jstring JNICALL Java_com_ln28_jnidemo_Chapter05_stringFromJNI( // 修改函数名前缀,与Chapter05.kt中对应 JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); } extern "C" JNIEXPORT jobject JNICALL Java_com_ln28_jnidemo_Chapter05_testReferenceAPI( // 修改函数名前缀,与Chapter05.kt中对应 JNIEnv* env, jobject /* this */) { return testReferenceAPI(env); }
在Kotlin中,需要定义和调用原生方法,并使用System.loadLibrary()
来加载本地库。下面是Kotlin中实现的一些原生方法和测试代码:
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.widget.TextView import com.ln28.jnidemo.databinding.ActivityMainBinding import com.ln28.jnidemo.databinding.Chapter05Binding class Chapter05 : AppCompatActivity() { // 定义一个原生方法,用于调用测试函数 external fun testReferenceAPI(): Any // 原生方法:测试引用类型的API调用 var TAG = "Chapter05"; // 定义TAG变量用于日志输出 private lateinit var binding: Chapter05Binding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) this.setTitle("Chapter05") binding = Chapter05Binding.inflate(layoutInflater) setContentView(binding.root) // Example of a call to a native method binding.sampleText.text = stringFromJNI() // 调用原生方法,返回字符串,并显示在UI中 // 调用测试函数并打印结果 val result = testReferenceAPI() // 调用原生方法获取引用类型的API调用结果 printObject(result) // 调用普通方法打印对象信息 } // 定义一个普通方法,用于打印对象信息 fun printObject(obj: Any) { Log.d(TAG, "对象为:$obj") Log.d(TAG, "对象的类为:${obj.javaClass}") Log.d(TAG, "对象的哈希码为:${obj.hashCode()}") } /** * 一个由本应用程序打包的 'jnidemo' native库实现的原生方法。 */ external fun stringFromJNI(): String // 原生方法:获取一个字符串 companion object { // 用于在应用程序启动时加载 'jnidemo' 库。 init { System.loadLibrary("jnidemo") } } }
疑问和解答
- 为什么我需要全局引用,我不能只使用局部引用吗?
全局引用和局部引用具有不同的生命周期和作用范围。全局引用可以在任何线程中使用,并且需要显式删除以防止内存泄漏,而局部引用只能在创建它的线程中使用,并且在native方法返回时自动删除。因此,如果需要在多个native方法或者线程之间共享一个Java对象,或者需要持有一个Java对象的引用超出一个native方法的生命周期,就需要使用全局引用。 - 创建全局引用有什么开销吗?我可以创建多少个全局引用?
创建全局引用会占用一些内存和CPU时间,但通常这个开销是很小的。然而,如果创建了很多全局引用而没有适时地删除,可能会导致内存泄漏。理论上,可以创建任意数量的全局引用,只要系统的内存允许。 - 如果我忘记删除全局引用,会发生什么?这会导致内存泄漏吗?
是的,如果创建了全局引用但忘记删除,这将导致内存泄漏,因为全局引用会阻止垃圾收集器回收其引用的对象,即使这个对象在Java代码中已经没有其他引用。 - 我如何知道何时需要创建全局引用,而何时只需要局部引用?
需要创建全局引用的情况通常包括:需要在native代码的不同部分共享Java对象,需要在不同的线程中访问Java对象,或者需要持有一个Java对象的引用超过一个native方法的生命周期。如果只是临时访问一个Java对象,并且在方法返回时不再需要这个引用,那么使用局部引用就足够了。 - 局部引用的生命周期是怎样的?如果我在一个native方法中创建了一个局部引用,然后在另一个方法中使用它,会发生什么?
局部引用的生命周期仅限于创建它的native方法。一旦这个native方法返回,所有在这个方法中创建的局部引用都会自动被删除。因此,如果在一个方法中创建了一个局部引用,然后在另一个方法中使用它,可能会得到一个无效的引用,这会导致未定义的行为,如程序崩溃。 - 如果我在多线程环境中使用局部引用会有问题吗?
是的,局部引用只能在创建它的线程中使用。如果尝试在其他线程中使用局部引用,可能会导致未定义的行为。在多线程环境中,应该使用全局引用来共享Java对象。 - 弱全局引用和全局引用有什么区别?我何时需要使用弱全局引用?
弱全局引用是一种特殊的引用,它不会阻止其引用的对象被垃圾回收。也就是说,即使持有一个弱全局引用,垃圾收集器仍然可以在内存不足时回收这个对象。可能需要使用弱全局引用的情况包括:需要缓存一些Java对象,但不希望这些对象占用过多内存。 - 如果我持有一个弱全局引用,然后该对象被垃圾回收,我会得到一个无效引用吗?我应该如何处理这种情况?
是的,如果持有一个弱全局引用,然后该对象被垃圾回收,会得到一个无效的引用。在使用弱全局引用前,应该总是检查这个引用是否仍然有效。可以使用IsSameObject
函数来检查一个引用是否有效。 - 如何正确地创建和删除全局引用、局部引用和弱全局引用?
可以使用NewGlobalRef
、NewLocalRef
和NewWeakGlobalRef
函数来创建全局引用、局部引用和弱全局引用。使用完一个全局引用或弱全局引用后,应该使用DeleteGlobalRef
或DeleteWeakGlobalRef
函数来删除它。局部引用在native方法返回时会自动被删除,不需要手动删除。 PushLocalFrame
和PopLocalFrame
函数是做什么的?我何时需要使用它们?
PushLocalFrame
和PopLocalFrame
函数用于管理局部引用。PushLocalFrame
函数会创建一个局部引用帧,然后可以在这个帧中创建多个局部引用。使用PopLocalFrame
函数时,这个局部引用帧中的所有局部引用都会一次性被删除,这样可以避免手动管理每个局部引用的生命周期。如果在一个native方法中创建了很多局部引用,或者在一个循环中反复创建局部引用,使用这两个函数可以帮助更有效地管理内存。
假设有这样一个例子:在一个native方法中访问了一个大型Java数组,可能会这样做:
jobjectArray array = /* a large Java array */; int len = (*env)->GetArrayLength(env, array); for (int i = 0; i < len; i++) { (*env)->PushLocalFrame(env, 1); jobject element = (*env)->GetObjectArrayElement(env, array, i); // do something with element (*env)->PopLocalFrame(env, NULL); }
这样,每次循环都会创建一个新的局部引用帧,循环结束时删除该帧及其所有局部引用。这样可以避免在循环期间积累大量的局部引用,防止内存泄漏。