【Android C++】JNI引用管理

     cls = env->FindClass("java/lang/String");  
     if (cls == NULL) {  
         return NULL;  
     }  
 } else {  
     LOGD("cls is not null but program will crash");  
 }  
 if (mid == NULL) {  
     mid = env->GetMethodID(cls, "<init>", "(\[C)V");  
     if (mid == NULL) {  
         return NULL;  
     }  
 }  
 jcharArray charEleArr = env->NewCharArray(10);  
 const jchar \*j\_char = env->GetStringChars(env->NewStringUTF("LocalReference"), NULL);  
 env->SetCharArrayRegion(charEleArr, 0, 10, j\_char);  
 jstring result = (jstring) env->NewObject(cls, mid, charEleArr);  
 env->DeleteLocalRef(charEleArr);  
 return result;  

}


#### 局部引用手动释放时机


局部引用除了自动释放外,还可以通过 DeleteLocalRef 函数手动释放,它一般存在于以下场景中:


* 当要创建大量的局部引用对象时,会造成 JNI 局部引用表的溢出。 假如有以下代码,则要特别注意,及时释放局部引用,防止溢出。



for (int i = 0; i < len; ++i) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
… /* process jstr */
(*env)->DeleteLocalRef(env, jstr);
}


* 编写工具类时,即时释放局部引用。 在编写工具类时,很难知道被调用者具体会是谁,考虑到通用性,完成工具类的任务之后,就要及时释放相应的局部引用,防止被占着内存空间。
* 不需要返回的 Native 方法,即时释放局部引用。 如果 Native 方法不会返回,那么自动释放局部引用就失效了,这时候就必须要手动释放。比如,在某个一直等待的循环中,如果不及时释放局部引用,很快就会溢出了。
* 局部引用使用完了就删除,不必要等到函数结尾。 比如,通过局部引用创建了一个大对象,然后这个对象在函数中间就完成了任务,那么就可以早早地通过手动释放了,而不是等到函数的结尾才释放。


#### 管理局部引用


Java 还提供了一些函数来管理局部引用的生命周期:


* EnsureLocalCapacity
* NewLocalRef
* PushLocalFrame
* PopLocalFrame


#### EnsureLocalCapacity 函数


JNI 的规范指出,JVM 要确保每个 Native 方法至少可以创建 16 个局部引用,经验表明,16 个局部引用已经足够平常的使用了。


但是,如果要与 JVM 的中对象进行复杂的交互计算,就需要创建更多的局部引用了,这时就需要使用 EnsureLocalCapacity 来确保可以创建指定数量的局部引用,如果创建成功返回 0 ,失败返回小于 0 ,如下代码示例:



// Use EnsureLocalCapacity
int len = 20;
if (env->EnsureLocalCapacity(len) < 0) {
// 创建失败,out of memory
}
for (int i = 0; i < len; ++i) {
jstring jstr = env->GetObjectArrayElement(arr,i);
// 处理 字符串
// 创建了足够多的局部引用,这里就不用删除了,显然占用更多的内存
}


引用确保可以创建了足够的局部引用数量,所以在循环处理局部引用时可以不进行删除了,但是显然会消耗更多的内存空间了。


#### PushLocalFrame 与 PopLocalFrame 函数对


PushLocalFrame 与 PopLocalFrame 是两个配套使用的函数对。


它们可以为局部引用创建一个指定数量内嵌的空间,在这个函数对之间的局部引用都会在这个空间内,直到释放后,所有的局部引用都会被释放掉,不用再担心每一个局部引用的释放问题了。


常见的使用场景就是在循环中:



// Use PushLocalFrame & PopLocalFrame
for (int i = 0; i < len; ++i) {
if (env->PushLocalFrame(len)) { // 创建指定数据的局部引用空间
//out ot memory
}
jstring jstr = env->GetObjectArrayElement(arr, i);
// 处理字符串
// 期间创建的局部引用,都会在 PushLocalFrame 创建的局部引用空间中
// 调用 PopLocalFrame 直接释放这个空间内的所有局部引用
env->PopLocalFrame(NULL);
}


使用 PushLocalFrame & PopLocalFrame 函数对,就可以在期间放心地处理局部引用,最后统一释放掉。


### 全局引用


全局引用和局部引用一样,也会阻止它所引用的对象被回收。但是它不会在方法返回时被自动释放,必须要通过手动释放才行,而且,全局引用可以跨方法、跨线程使用。


全局引用只能通过 NewGlobalRef函数来创建,然后通过 DeleteGlobalRef 函数来手动释放。  
 还是上面提到的缓存字段的例子,现在就可以使用全局引用来缓存了。



extern “C”
JNIEXPORT jstring JNICALL
Java_com_qingkouwei_Demo_cacheWithGlobalReference(JNIEnv *env, jobject instance) {
static jclass stringClass = NULL;
if (stringClass == NULL) {
jclass localRefs = env->FindClass(“java/lang/String”);
if (localRefs == NULL) {
return NULL;
}
stringClass = (jclass) env->NewGlobalRef(localRefs);
env->DeleteLocalRef(localRefs);
if (stringClass == NULL) {
return NULL;
}
} else {
LOGD(“use stringClass cached”);
}
static jmethodID stringMid = NULL;
if (stringMid == NULL) {
stringMid = env->GetMethodID(stringClass, “”, “(Ljava/lang/String;)V”);
if (stringMid == NULL) {
return NULL;
}
} else {
LOGD(“use method cached”);
}
jstring str = env->NewStringUTF(“string”);
return (jstring) env->NewObject(stringClass, stringMid, str);
}


### 弱全局引用


弱全局引用有点类似于 Java 中的弱引用,它所引用的对象可以被 GC 回收,并且它也可以跨方法、跨线程使用。  
 弱引用使用 NewWeakGlobalRef 方法创建,使用 DeleteWeakGlobalRef 方法释放。



extern “C”
JNIEXPORT void JNICALL
Java_com_qingkouwei_Demo_useWeakGlobalReference(JNIEnv *env, jobject instance) {
static jclass stringClass = NULL;
if (stringClass == NULL) {
jclass localRefs = env->FindClass(“java/lang/String”);
if (localRefs == NULL) {
return;
}
stringClass = (jclass) env->NewWeakGlobalRef(localRefs);
if (stringClass == NULL) {
return;
}
}
static jmethodID stringMid = NULL;
if (stringMid == NULL) {
stringMid = env->GetMethodID(stringClass, “”, “(Ljava/lang/String;)V”);
if (stringMid == NULL) {
return;
}
}
jboolean isGC = env->IsSameObject(stringClass, NULL);
if (isGC) {
LOGD(“weak reference has been gc”);
} else {
jstring str = (jstring) env->NewObject(stringClass, stringMid,
env->NewStringUTF(“jstring”));
LOGD(“str is %s”, env->GetStringUTFChars(str, NULL));
}
}


在使用弱引用时,要先检查弱引用所指的类对象是否被 GC 给回收了。通过 isSameObject 方法进行检查。  
 isSameObject 方法可以用来比较两个引用类型是否相同,也可以用来比较引用是否为 NULL。同时,还可以用 isSameObject 来比较弱全局引用所引用的对象是否被 GC 了,返回 JNI\_TRUE 则表示回收了,JNI\_FALSE 则表示未被回收。



env->IsSameObject(obj1, obj2) // 比较局部引用 和 全局引用是否相同
env->IsSameObject(obj, NULL) // 比较局部引用或者全局引用是否为 NULL
env->IsSameObject(wobj, NULL) // 比较弱全局引用所引用对象是否被 GC 回收


### 合理管理引用


总结一些关于引用管理方面的知识点,可以减少内存的使用和避免因为对象被引用不能释放而造成的内存浪费。  
 通常来说,Native 代码大体有两种情况:


* 直接实现 Java 层声明的 Native 函数的代码
* 用在任何场景下的工具函数




# 尾声

开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。


*   **330页 PDF Android核心笔记**

![](https://img-blog.csdnimg.cn/img_convert/abf35dd8581ad571d9a5879d96c077f0.webp?x-oss-process=image/format,png)

*   几十套**阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题**

![](https://img-blog.csdnimg.cn/img_convert/928d8c03cd7184691c860cfbadb3360d.webp?x-oss-process=image/format,png)

![](https://img-blog.csdnimg.cn/img_convert/ae4481deae49fc2f6cf9a0f1ed1b2a7f.webp?x-oss-process=image/format,png)

*   PDF和思维脑图,包含**知识脉络 + 诸多细节**

![](https://img-blog.csdnimg.cn/img_convert/7d5f823ed1c7d446f13e5103edde2a45.webp?x-oss-process=image/format,png)

*   **Android进阶系统学习视频**

![](https://img-blog.csdnimg.cn/img_convert/73071b5f8457517aa49ee519732034e8.webp?x-oss-process=image/format,png)



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

4529580952)]



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值