JNI编程

JNI是一种本地编程接口,也就是桥接语言,它能够使Java与其他编程语言(C、C++、汇编)进行交互。下面我先列出来Java的数据类型对应的JNI类型的表,这里可以暂时先不看,之后的例子中会有介绍,当做字典来用即可。

Java类型本地类型描述
booleanjbooleanC/C++8位整型
bytejbyteC/C++带符号的8位整型
charjcharC/C++无符号的16位整型
shortjshortC/C++带符号的16位整型
intjintC/C++带符号的32位整型
longjlongC/C++带符号的64位整型
floatjfloatC/C++32位浮点型
doublejdoubleC/C++64位浮点型
Objectjobject任何Java对象,或者没有对应java类型的对象
ClassjclassClass对象
Stringjstring字符串对象
Object[]jobjectArray任何对象的数组
boolean[]jbooleanArray布尔型数组

JNI编程

关于Android项目的创建过程我就不赘述了,可以自行百度,我这里直接进入正题

在MainActivity中会有这么一句代码,而这个代码的作用就是动态加载native-lib的库文件,这个库文件是根据src/main/cpp中的native-lib.cpp文件生成的,我们的JNI代码都是写在这个cpp文件中

static {
        System.loadLibrary("native-lib");
    }

1.基本数据类型及字符串类型

在MainActivity中创建一个native方法,有3个类型的参数:int,float,String,返回值为String类型

native String perInfo(int age,float height,String name);

此时我们需要在刚刚所说的native-lib.cpp文件中创建如下对应的方法。我做了一些说明

extern "C"
JNIEXPORT jstring JNICALL
Java_com_gzc_jnitest_MainActivity_perInfo(JNIEnv *env, jobject instance,
                                        jint jage, jfloat jheight,jstring jname){
    //将jstring类型转换为char*类型
    const char* str = env->GetStringUTFChars(jname,JNI_FALSE);
    char returnStr[100];
    sprintf(returnStr,"name:%s,age:%d,height:%.2f",str,jage,jheight);
    //创建一个新字符串
    return env->NewStringUTF(returnStr);
}

需要注意的地方:
(1)方法名中Java为固定的格式;com_gzc_jnitest是类名,只是把点改成_;MainActivity为native方法所在的类的名称;perInfo为native方法名
(2)jint,jfloat所对应的都是基本数据类型,可以直接使用;而jstring对应的是引用类型,需要进行转换才可以使用
(3)方法中除了native对应的三个参数外,还有两个参数:
    JNIEnv *env:可以把它看成JVM的引用对象,关于数据的一些处理都要用到;
    jobject instance:这个参数其实就是native方法所在类的对象,这里就是MainActivity
(4)GetStringUTFChars的第二个参数是一个boolean类型:
    true:表示将jstring的数据拷贝到新的内存中,之后使用的都是新内存的数据
    false:表示不进行拷贝,之后使用的都是原数据

2.基本数据类型及String数据的数组

同样的我们在MainActivity中写相应的native方法,在native-lib.cpp文件中写对应的JNI代码

native void printPeopleInfo(int[] ages,float[] heights,String[] names);
#include <android/log.h>
//  __VA_ARGS__ 代表... 可变参数
#define  LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"JNI",__VA_ARGS__);

extern "C"
JNIEXPORT void JNICALL
Java_com_gzc_jnitest_MainActivity_printPeopleInfo(JNIEnv *env,jobject instance,
                                              jintArray jages,jfloatArray jheights,jobjectArray jnames){
    //取出数组首元素的内存地址
    jint *ageFirst = env->GetIntArrayElements(jages,JNI_FALSE);
    jfloat *heightFirst = env->GetFloatArrayElements(jheights,JNI_FALSE);
    //获取数组的长度
    int32_t size = env->GetArrayLength(jages);
    for (int i = 0; i <size; ++i) {
        jstring jname = static_cast<jstring>(env->GetObjectArrayElement(jnames,i));
        const char* name = env->GetStringUTFChars(jname,JNI_FALSE);
        LOGE("第%d位的信息:姓名:%s,年龄:%d,身高:%.2f",i+1,name,*(ageFirst+i),*(heightFirst+i));
        env->ReleaseStringUTFChars(jname,name);
    }
    env->ReleaseIntArrayElements(jages,ageFirst,0);
    env->ReleaseFloatArrayElements(jheights,heightFirst,0);
}

需要注意的地方:
(1)数组其实就是内存中一段连续的地址,我们拿到首元素的地址,就可以获得每个元素的值。这里feageFirst和heightFirst分别就是int[]和float[]的首元素地址。
(2)获取字符串数组每个元素的值,首先先将每个元素转换成jstring类型,然后再转换成char*类型输出
(3)LOGE是在开头位置定义的宏,等同于
    __android_log_print(ANDROID_LOG_ERROR,"JNI","第%d位的信息:姓名:%s,年龄:%d,身高:%.2f",i+1,name,*(ageFirst+i),*(heightFirst+i));
(4)注意要释放字符串:env->ReleaseStringUTFChars(jname,name)
(5)释放数组比释放字符串多了一个参数:
    0:刷新java数组 并 释放c/c++数组。比如在native-lib.cpp中改变了数组的值,在java代码中的数组的值也就改变了
    1(JNI_COMMIT):只刷新java数组。同上
    2(JNI_ABORT):只释放c/c++数组。比如改变了native-lib.cpp中数组的值,java代码中的数组不改变

3.JavaBean类型的获取及创建

创建一个名为PerInfoBean的类

public class PerInfoBean {
    private int age;
    private float height;
    private String name;


    public PerInfoBean(int age, float height, String name) {
        this.age = age;
        this.height = height;
        this.name = name;
    }

    public static void printInfo(String info){
        Log.e("JNI",info);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在MainActivity中写native方法

native void perInfoBeanOption(PerInfoBean bean);

native-lib中的JNI方法,我做了详细的介绍,结尾处是注意事项

extern "C"
JNIEXPORT void JNICALL
Java_com_gzc_jnitest_MainActivity_perInfoBeanOption(JNIEnv *env,jobject instance,jobject jobj){
    //1.获取java对应的class方法
    jclass jPerInfoBean = env->GetObjectClass(jobj);
    //2.找到要调用的方法。第二个参数:方法名  第三个参数:参数和返回值对应的签名
    jmethodID jsetAge = env->GetMethodID(jPerInfoBean,"setAge","(I)V");
    //3.调用方法
    env->CallVoidMethod(jobj,jsetAge,100);

    //我们再通过上述的方法调用getAge方法,看看值是否改变了
    jmethodID jgetAge = env->GetMethodID(jPerInfoBean,"getAge","()I");
    jint jage = env->CallIntMethod(jobj,jgetAge);
    LOGE("修改后的age:%d",jage);

    //static方法的调用,和普通方法步骤基本相同,只是在相应的JNI方法中带有static
    //接受的参数是String类型,根据下面贴出的签名对照表可知是这样的书写方式
    jmethodID jprintInfo = env->GetStaticMethodID(jPerInfoBean,"printInfo","(Ljava/lang/String;)V");
    jstring jprintstr = env->NewStringUTF("static方法被调用");
    //这里需要注意第一个参数和普通方法的不同
    env->CallStaticVoidMethod(jPerInfoBean,jprintInfo,jprintstr);
    //释放局部引用
    env->DeleteLocalRef(jprintstr);

    //创建JavaBean对象
    //1.获取jclass
    jclass jNewPerInfoBean = env->FindClass("com/gzc/jnitest/PerInfoBean");
    //2.获取构造器方法
    jmethodID jconstruct = env->GetMethodID(jNewPerInfoBean,"<init>","(IFLjava/lang/String;)V");
    //3.创建对象
    jstring jnewname = env->NewStringUTF("小海");
    jobject jnewobj = env->NewObject(jNewPerInfoBean,jconstruct,24,173.0f,jnewname);
    //若不再使用则删除
    env->DeleteLocalRef(jnewobj);
    env->DeleteLocalRef(jnewname);
    env->DeleteLocalRef(jNewPerInfoBean);

    //修改属性值
    jfieldID jfieldage = env->GetFieldID(jPerInfoBean,"age","I");
    env->SetIntField(jobj,jfieldage,30);

}

注意:
(1)获取JavaBean对象的相应方法时并不受修饰符的影响,无论是public还是private都能获得到
(2)注意无用对象的释放
(3)使用javap来获取指定方法的签名
    第一步:cd到app\build\intermediates\classes\debug目录下
    第二步:javap -s 包名.类名     注意:类名无后缀

基本数据类型的签名采用一系列大写字母来表示, 如下表所示:

Java类型签名
booleanZ
shortS
floatF
byteB
intI
doubleD
charC
longJ
voidV
引用类型L + 全限定名 + ;
数组[+类型签名

 

全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效 。由 NewGlobalRef 函数创建

extern "C"
JNIEXPORT jstring JNICALL
Java_com_dongnao_jnitest_MainActivity_test1(JNIEnv *env, jobject instance) {
	//正确
    static jstring globalStr;
    if(globalStr == NULL){
        jstring str = env->NewStringUTF("C++字符串");
        //删除全局引用调用  DeleteGlobalRef
        globalStr = static_cast<jstring>(env->NewGlobalRef(str));
        //可以释放,因为有了一个全局引用使用str,局部str也不会使用了
        env->DeleteLocalRef(str);
    }
    return globalStr;
}

弱引用

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

在对Class进行弱引用是非常合适(FindClass),因为Class一般直到程序进程结束才会卸载。

在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象

extern "C"
JNIEXPORT jclass JNICALL
Java_com_dongnao_jnitest_MainActivity_test1(JNIEnv *env, jobject instance) {
    static jclass globalClazz = NULL;
    //对于弱引用 如果引用的对象被回收返回 true,否则为false
    //对于局部和全局引用则判断是否引用java的null对象
    jboolean isEqual = env->IsSameObject(globalClazz, NULL);
    if (globalClazz == NULL || isEqual) {
        jclass clazz = env->GetObjectClass(instance);
        //删除使用 DeleteWeakGlobalRef
        globalClazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));
        env->DeleteLocalRef(clazz);
    }
    return globalClazz;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值