Android NDK开发(十一):JNI函数接口详解—引用

1 JNI中的引用、引用类型及引用变量

        JNI中的引用与java中的类似,是用来在JNI中访问java对象的,那么JNI中谁来持有引用呢,那当然是引用变量,那么引用变量如何定义呢,那当然是通过JNI定义的引用类型。JNI中也定义了与java引用类型对应的引用类型(如:jobject、jstring、jclass、jarray、jintArray等),与java类似,JNI的这些引用类型定义的变量称为引用变量,用于持有相同类型的引用,进而访问Java对象。

2 JNI引用分类及对比

        JNI根据引用的生命周期和作用范围不同,将引用分为三种,分别是 局部引用、全局引用、弱全局引用,三者区别具体如下:

局部引用全局引用弱全局引用
作用域方法内部工程内部工程内部
生命周期

开始:手动创建 / JNI接口返回

结束:手动释放 / 方法结束

开始:手动创建

结束:手动释放

开始:手动创建

结束:手动释放 / 被GC回收

生命周期内是否会被GC回收××

(若JNI中java对象只有弱全局引用,则随时会被GC回收)

能否跨方法使用×
能否跨线程使用×
存储位置局部引用表全局引用表弱全局引用表

上限

跟Android版本有关,以android10 为例

一个应用中 JNI最多可创建8388608个局部引用

若超过则报错table overflow

一个应用中 JNI最多可创建51200个全局引用

若超过则报错table overflow

一个应用中 JNI最多可创建51200个弱全局引用

若超过则报错table overflow

是否有下限JNI每个线程中默认至少可创建16个局部引用

        注意:应用开始到结束过程中,JNI会将过程中所产生的局部引用、全局引用、弱全局引用分别存储在 局部引用表、全局引用表、弱全局引用表中,上面说的上限,是整个JNI层创建引用的上限,也是各个表的长度,如果应用执行的过程中,JNI创建的引用超过了表的长度,JNI就会报错 table overflow,所以我们应该及时释放用不到的局部、全局、弱全局引用。
        注意:
在 android 8.0 以下版本,局部引用上限为512。

3 JNI引用详解

(1)局部引用

         在JNI中,局部引用比较常见,返回值为对象引用的JNI接口,所返回的引用基本都是局部引用;局部引用会阻止GC回收该引用所引用的对象;局部引用不能跨方法、跨线程使用。

1)涉及到的JNI接口

功能接口
手动创建局部引用NewLocalRef
销毁局部引用DeleteLocalRef
申请当前线程至少可用的局部引用数量EnsureLocalCapacity
为函数创建一个指定数量的局部引用帧PushLocalFrame
释放局部引用帧中的引用PopLocalFrame

2)接口详解

/**
 * 作用:创建一个局部引用
 * @param ref 可以是局部引用、全局引用、弱全局引用。可以传null
 * @return 相应对象的局部引用。若 内存溢出 或 ref为null 或 ref是已经被回收的弱全局引用 则 返回null
 *
 * @exceptions 不报异常
 */
jobject NewLocalRef(jobject ref);

/**
 * 作用:释放一个局部引用
 * @param localRef 只能是局部引用 或 null。
 *
 * @exceptions 不报异常
 *
 * 注意:同一个非空局部引用不能删除两次
 */
void DeleteLocalRef(jobject localRef);

/**
 * 作用:申请当前线程至少可用的局部引用数量
 * @param capacity 当前线程 至少可用的局部引用数量
 * @return
 * 0:申请成功
 * 负数:申请失败并抛出OutOfMemoryError异常
 *
 * @exceptions
 * OutOfMemoryError: 若系统空间不足,则报该异常
 *
 * 注意:JNI中每个线程至少可创建16个局部引用
 */
jint EnsureLocalCapacity(jint capacity);

/**
 * 作用:为函数创建一个指定大小的局部引用帧(一段栈上的连续的存储空间),与PopLocalFrame成对使用,
 * 在二者之间创建的局部引用会被存储在其中
 * @param capacity 帧的大小
 * @return
 * 0:创建成功
 * 负数:创建失败,并抛出OutOfMemoryError异常
 *
 * @exceptions
 * OutOfMemoryError: 若系统空间不足,则报该异常
 *
 * 注意:创建的局部引用帧仅供当前函数使用(即调用这个接口的函数)
 */
jint PushLocalFrame(jint capacity);

/**
 * 作用:清空使用PushLocalFrame接口创建的局部引用帧中的所有局部引用或只保留一个,与PushLocalFrame成对使用
 * @param result 传入null,则释放帧中的全部局部引用;传入局部引用,则释放除result之外的其他局部引用
 * @return 需要保留的局部引用
 *
 * 注意:返回的局部引用并不是将帧中要保留的局部引用直接返回,而是将帧中的所有引用全部释放后,重新创建一个相同的引用
 */
jobject PopLocalFrame(jobject result);

3)接口使用

       主要对局部引用的创建和释放的方法进行示例说明。当JNI函数不会创建太多局部引用,且不会阻塞 或 耗时 时,不必手动处理局部引用,用方法1即可;当JNI函数需要创建较多引用,且函数短时间内不会结束时,应该用方法2;在方法2的情况下,如果嫌一个一个释放太累,可以使用方法3批量手动释放局部引用。
        为了养成用完就释放的好习惯,什么情况下都手动释放局部引用~

//局部引用的创建及释放
//方法1:自动创建与释放
void localRef1(JNIEnv * env){
    //1 创建引用变量
    jstring jstr;
    //2 通过JNI接口得到局部引用,并赋给引用变量
    jstr = env->NewStringUTF("uuuu");
    //3 方法结束自动释放局部引用
    return;
}

//方法2:用 NewLocalRef/DeleteLocalRef 手动创建与释放
void localRef2(JNIEnv * env){
    //1 创建引用变量
    jstring jstr;
    //2 手动创建局部引用,需要强制类型转换一下
    jstr =  (jstring)env->NewLocalRef(env->NewStringUTF("uuuu"));
    //3 手动释放局部引用
    env->DeleteLocalRef(jstr);
}

//方法3:用 PushLocalFrame/PopLocalFrame 手动创建与释放
//(1)局部引用帧无保留
void localRef3(JNIEnv * env){
    //1 创建局部引用帧
    env->PushLocalFrame(20);
    //2 jni接口返回局部引用
    jstring jstr1 = env->NewStringUTF("uuuu");
    jstring jstr2 = env->NewStringUTF("iiii");
    jstring jstr3 = env->NewStringUTF("tttt");
    //3 手动释放局部引用帧中的所有局部引用
    env->PopLocalFrame(nullptr);
}
//(2)局部引用帧有保留
jstring localRef4(JNIEnv * env){
    //1 创建局部引用帧
    env->PushLocalFrame(20);
    //2 jni接口返回局部引用
    jstring jstr1 = env->NewStringUTF("uuuu");
    jstring jstr2 = env->NewStringUTF("iiii");
    jstring jstr3 = env->NewStringUTF("tttt");
    //3 手动释放局部引用帧中除jstr3的局部引用
    return (jstring)env->PopLocalFrame(jstr3);
}

(2)全局引用

1)涉及到的JNI接口

功能接口
创建全局引用NewGlobalRef
销毁全局引用DeleteGlobalRef

2)接口详解

/**
 * 作用:创建全局引用
 * @param obj 可以是 局部引用、全局引用、弱全局引用、null
 * @return 返回 全局引用 或 null(传入null 或 已被回收的弱全局引用)
 *
 * @exceptions 不报异常
 */
jobject NewGlobalRef(jobject obj);

/**
 * 作用:释放全局引用
 * @param globalRef 全局引用 或 null
 * 
 *  @exceptions 不报异常
 */
void DeleteGlobalRef(jobject globalRef);

3)接口使用

//全局引用的创建及释放
//1 定义引用变量(在方法外定义,便于全局使用)
jstring jstr_global;
void GlobalRef(JNIEnv * env){
    //2 获取局部引用
    jstring jstr = env->NewStringUTF("uuuu");
    //3 创建全局引用
    jstr_global = (jstring)env->NewGlobalRef(jstr);
    //4 释放全局引用
    env->DeleteGlobalRef(jstr_global);
}

(3)弱全局引用

1)涉及到的JNI接口

功能接口
创建弱全局引用NewWeakGlobalRef
销毁弱全局引用DeleteWeakGlobalRef
判断两个对象是否一样,在这里用于判断弱全局引用是否为nullIsSameObject

2)接口详解

/**
 * 作用:创建弱全局引用
 * @param obj 局部引用、全局引用、弱全局引用
 * @return 若obj非null,则返回弱全局引用;若obj为null,则返回null
 *
 * @exceptions
 * OutOfMemoryError:内存不足时报该异常
 */
jweak NewWeakGlobalRef(jobject obj);

/**
 * 作用:释放弱全局引用
 * @param obj 必须是弱全局引用
 *
 * @exceptions 不报异常
 *
 * 注意:同一个弱全局引用不能删除两次
 */
void DeleteWeakGlobalRef(jweak obj);

3)接口使用

//弱全局引用的创建、使用及释放
//1 定义引用变量(在方法外定义,便于全局使用)
jstring jstr_weak_global;
void WeakGlobalRef(JNIEnv * env){
    //2 获取局部引用
    jstring jstr = env->NewStringUTF("uuuu");
    //3 创建弱全局引用
    jstr_weak_global = (jstring)env->NewWeakGlobalRef(jstr);
    //4 使用弱全局引用之前判断一下 是否已经被回收
    if(!env->IsSameObject(jstr_weak_global, nullptr)){
        //TODO:你的处理逻辑
    }
    //5 释放弱全局引用
    env->DeleteWeakGlobalRef(jstr_global);
}

注意:由于只有弱全局引用的java对象随时会被回收,所以使用弱全局引用时,要用IsSameObject判断一下,弱全局引用是否为null。

(4)其他相关接口

1)涉及到的JNI接口

功能接口
获取引用类型GetObjectRefType

2)接口详解

/**
 * 作用:获取引用类型
 * @param obj 引用
 * @return 返回枚举值,代表相应的引用类型
 * 
 * 注意:该接口在JNI_1.6版本中添加,低版本并没有
 */
jobjectRefType GetObjectRefType(jobject obj);

        其中返回值  jobjectRefType 是定义在jni.h中的枚举:

typedef enum jobjectRefType {
    JNIInvalidRefType = 0,
    JNILocalRefType = 1,
    JNIGlobalRefType = 2,
    JNIWeakGlobalRefType = 3
} jobjectRefType;

 3)接口使用

//例子:释放引用
void releaseRef(JNIEnv * env,jobject jobj){
    //获取引用类型
    jobjectRefType type = env->GetObjectRefType(jobj);
    if(type == JNILocalRefType){
        env->DeleteLocalRef(jobj);
    }else if(type == JNIGlobalRefType){
        env->DeleteGlobalRef(jobj);
    }else if(type == JNIWeakGlobalRefType){
        env->DeleteWeakGlobalRef(jobj);
    }
}

(5)小结

        ** 所有引用使用完,最好都手动释放调。对于尤其是循环中使用的局部引用。
        ** 释放引用  仅仅是使引用不可达JNI中创建的java对象,并清理该引用,并不是释放java对象,java对象的回收释放需要GC才行,把对象的引用释放掉,只是让对象可以被回收,对象具体什么时候被回收还要看GC,这点跟java是相同的。
        ** 局部引用不能跨线程、跨方法,不要将局部引用赋给全局引用变量。
        ** JNI层的引用都是有上限的,局部引用Android8.0以上上限8388608个,Android8.0以下,上限512个;全局引用android8.0以上,上限51200个;弱全局引用android8.0以上,上限51200个;若JNI层创建的引用超过上限,则报错,程序退出。
        
** 使用弱全局引用时,先使用IsSameObject接口判断是否为null。GetObjectRefType。
        ** GetObjectRefType是JNI_1.6版本开始添加的,低版本使用不了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android NDK开发是指利用NDK(Native Development Kit)将C/C++开发的代码编译成so库,然后通过JNI(Java Native Interface)让Java程序调用。在Android开发中,默认使用的是Android SDK进行Java语言的开发,而对于一些需要使用C/C++的高性能计算、底层操作或跨平台需求的场景,可以使用NDK进行开发。 在Android Studio中进行NDK开发相对于Eclipse来说更加方便,特别是在Android Studio 3.0及以上版本中,配置更加简化,并引入了CMake等工具,使得开发更加便捷。首先要进行NDK开发,需要配置环境,包括导入NDK、LLDB和CMake等工具。可以通过打开Android Studio的SDK Manager,选择SDK Tools,在其中选中相应的工具进行导入。 在项目的build.gradle文件中,可以配置一些NDK相关的参数,例如编译版本、ABI过滤器等。其中,可以通过externalNativeBuild配置CMake的相关设置,包括CMakeLists.txt文件的路径和版本号。此外,在sourceSets.main中还可以设置jniLibs.srcDirs,指定so库的位置。 在进行NDK开发时,可以在jni文件夹中编写C/C++代码,并通过JNI调用相关的函数。通过JNI接口,可以实现Java与C/C++之间的相互调用,从而实现跨语言的开发。 综上所述,Android NDK开发是指利用NDK将C/C++开发的代码编译成so库,并通过JNI实现与Java的相互调用。在Android Studio中进行NDK开发相对方便,可以通过配置环境和相应的参数来进行开发。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值