JNI之引用简介

NDK  2023年7月28日 am8:08发布1年前 (2023)更新 91es.com站长
57 0 0

前言

简单的记录一下JNI局部引用全局引用弱全局引用,这对于写程序还是很有帮助的。

正文

深入了解是,先看看JNI中引用的重点知识。

  1. JNI 支持三种引用:局部引用、全局引用、弱全局引用(简称:弱引用)。

  2. 局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。

  3. 局部引用或者全局引用会阻止 GC 回收它们所引用的对象,而弱引用则不会 。

  4. 不是所有的引用可以被用在所有的场合。例如,一个本地方法创建一个局部引用并返回后,再对这个局部引用进行访问是非法的。

局部引用

局部引用(Local Reference)最常见的引用类型。

释放一个局部引用有两种方式,一个是本地函数执行完后VM自动释放,另外一个是通过DeleteLocalRef手动释放。

既然局部引用自动释放,那为什么还需手动释放?

如果不调用DeleteLocalRef ,局部引用在函数返回后会被回收;如果调用DeleteLocalRef ,局部引用会立即被回收。两种方式最后都会释放,但立即释放(调用DeleteLocalRef )可一定的缓解内存少的问题。

这个感觉跟Java中的GC原理有点类似,对象不用了,不一定马上会被回收,需要等到一定的条件才会回收。(个人理解)

全局引用

全局引用(Global Reference)可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被 GC 回收。

全局引用需要通过NewGlobalRef()函数进行创建!

jstring MyNewString(JNIEnv *env, jchar *chars, jint len){
    static jclass stringClass = NULL;
    ...
    if (stringClass == NULL) {
        jclass localRefCls =(*env)->FindClass(env, "java/lang/String");
        if (localRefCls == NULL) {
            return NULL; 
        }
        //创建全局引用
        stringClass = (*env)->NewGlobalRef(env, localRefCls);
        //删除局本引用
        (*env)->DeleteLocalRef(env, localRefCls);
        //判读全局引用是否创建成功
        if (stringClass == NULL) {
            return NULL;
        }
    }
    ...
}

码中通过FindClass返回的局部引用localRefCls,然后在通过NewGlobalRef创建String类的全局引用。删除localRefCls后,再检查NewGlobalRef是否成功。

与局部引用不同,全局引用的创建不是由 JNI 自动创建的,全局引用需要调用 NewGlobalRef 函数,而释放它需要使用 ReleaseGlobalRef 函数。

下面是全局引用创建和删除函数,以及局部引用的删除函数。

#全局引用
jobject     (*NewGlobalRef)(JNIEnv*, jobject);
void        (*DeleteGlobalRef)(JNIEnv*, jobject);
#局部引用
void        (*DeleteLocalRef)(JNIEnv*, jobject);

弱全局引用

弱全局引用 (Weak Global Reference),简称弱引用,JDK 1.2 引入。弱引用使用 NewGlobalWeakRef 创建,使用 DeleteGlobalWeakRef 释放。

与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止 GC 回收它所指向的 VM 内部的对象。

由于弱引用会被回收,所以在使用前最好通过IsSameObject进行判断是否回收了。

缓存变量

既然介绍了,总得试试身手吧。在JNI中为了获取java属性和方法,会先获取jfieldID与jmethodID,然后进行查询属性值或调用java方法。如果将jfieldID与jmethodID缓存起来,调用时不必每次都查询。

一般缓存有如下两种方式:

  1. 使用时缓存

  2. 初始化时缓存

使用时缓存

使用时缓存,也即是第一次调用是就保存下来,等下次再次调用时直接使用缓存的值即可。

JNIEXPORT jint JNICALL native_jni_computer(JNIEnv *env, jobject object, jstring type, jint x, jint y{
    int count = -1;
    jclass clazz = clazz = env->GetObjectClass(object);
    //根据type进行加减乘
    const char *charType = env->GetStringUTFChars(type, 0);
    if (!strcmp(charType, "-")) {
        //静态变量,只会初始化一次
        static jmethodID subMethodID = NULL;
        if(NULL == subMethodID){
            subMethodID = env->GetMethodID(clazz, "sub", "(II)I");
        }
        if(NULL != subMethodID){
            count = env->CallIntMethod(object, subMethodID, x, y);
        }
    } else if (!strcmp(charType, "+")) {
        //静态变量
        static jmethodID addMethodID = NULL;
        if(NULL == addMethodID ){
            addMethodID = env->GetMethodID(clazz, "add", "(II)I");
        }
        if(NULL != addMethodID){
            count = env->CallIntMethod(object, addMethodID, x, y);
        }
    } else if (!strcmp(charType, "*")) {
        //静态变量
        static jmethodID multiplyMethodId = NULL;
        if(NULL == multiplyMethodId ){
            multiplyMethodId = env->GetStaticMethodID(clazz, "multiply", "(II)I");
        }
        if(NULL != multiplyMethodId){
            count = env->CallStaticIntMethod(clazz, multiplyMethodId, x, y);
        }
    }
    env->ReleaseStringUTFChars(type, charType);
    return count;
}
初始化时缓存

Android Framework中就很多用这种方式,也就是提前init。

static jfieldID mBooleanFieldID;
static jfieldID mIntFieldID;
static jmethodID mSubMethodID;
static jmethodID mAddMethodID;

JNIEXPORT void JNICALL native_jni_init(JNIEnv *env, jobject obj){
    jclass clazz = env->FindClass(DYNAMIC_CLASS);
    mjobject = env->NewWeakGlobalRef(obj);
    mBooleanFieldID = env->GetFieldID(clazz, "mBooleanValue", "Z");
    mIntFieldID = env->GetFieldID(clazz, "mIntValue", "I");
    mSubMethodID = env->GetMethodID(clazz, "sub", "(II)I");
    mAddMethodID = env->GetMethodID(clazz, "add", "(II)I");
}

参考文章

  1. 《JNI编程指南中文版》

  2. 《JNI完全手册》

  3. JNI:全局引用&局部引用&弱全局引用

  4. 《深入理解Android卷1(邓凡平)》

 历史上的今天

  1. 2022: [代码片段]GradientTextView渐变的TextView(0条评论)
  2. 2020: 音视频学习:AudioRecord的简单使用(0条评论)
  3. 2019: 梁实秋:排队(0条评论)
版权声明 1、 本站名称: 91易搜
2、 本站网址: 91es.com3xcn.com
3、 本站内容: 部分来源于网络,仅供学习和参考,若侵权请留言
3、 本站申明: 个人流水账日记,内容并不保证有效

暂无评论

暂无评论...

随机推荐

史铁生:秋天的怀念

双腿瘫痪后,我的脾气变得暴怒无常。望着望着天上北归的雁阵,我会突然把面前的玻璃砸碎;听着听着收音机里甜美的歌声,我会猛地把手边的东西摔向四周的墙壁。这时,母亲就悄悄地躲出去,在我看不见的地方偷偷地注意着我的动静。当一切恢复沉寂,她又悄悄地进来,眼圈红红地看着我。“听说北海的花儿都开了,我推着你去走走...

[摘]Android ANR日志分析指南

当你的项目越做越复杂,或者你的用户达到某个数量级的时候,你的代码不小心出现细小的问题,你会收到各种各样的bug,其中ANR的问题你一定不会陌生。本文将详细讲解ANR的类型、出现的原因、ANR案例详细分析、经典的案例。定义ANR(Application Not Responding) 应用程序无...

Android的5个进程等级

 一、进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。进程是系统进行资源分配和调度的一个独立单位。可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体,是一个“执行中的程序”。不只是程序的代码,还包括当前的活动。二、线程:线程是进程的一个实体,是CPU调度和...

费孝通:文字不能使人聪明起来

乡下人在城里人眼睛里是“愚”的。我们当然记得不少提倡乡村工作的朋友们,把愚和病贫联结起来去作为中国乡村的症候。关于病和贫我们似乎还有客观的标准可说,但是说乡下人“愚”,却是凭什么呢?乡下人在马路上听见背后汽车连续的按喇叭,慌了手脚。东避也不是,西躲又不是,司机拉住闸车,在玻璃窗里,探出半个头,向着那...

adb shell控制多媒体

前言记录一下通过adb shell 命令进行控制多媒体。这一套都是Android提供的标准,只要多媒体实现了MediaSession.Callback的响应即可。正文mMediaSession = new MediaSession(MusicApp.getContext(), TAG);m...

苏童:我从来不敢夸耀童年的幸福

我从来不敢夸耀童年的幸福,事实上我的童年有点孤独,有点心事重重。我父母除了拥有四个孩子之外基本上一无所有。父亲在市里的一个机关上班,每天骑着一辆破旧的自行车来去匆匆;母亲在附近的水泥厂当工人,她年轻时曾经美丽的脸到了中年以后经常是浮肿着的,因为疲累过度,也因为身患多种疾病。多少年来,父母亲靠80多元...