JNI 编程上手指南之 Native 访问 Java

本文接着介绍如何在 C/C++ 中访问 Java,主要从以下几个方面来讲述:

  • 访问 Java 的成员变量,包括了实例成员和静态成员
  • 访问 Java 的方法,包括了成员方法和静态方法

1. Native 访问 Java 成员变量

我们直接看 Demo:

Java 层:

//定义一个被访问的类
public class TestJavaClass {



    private String mString = "Hello JNI, this is normal string !";
    
    private static int mStaticInt = 0;
}



//定义两个 native 方法
public native void accessJavaFiled(TestJavaClass testJavaClass);
public native void accessStaticField(TestJavaClass testJavaClass);

c++ 层:

//访问成员变量
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_accessJavaFiled(JNIEnv *env, jobject thiz,
                                                          jobject test_java_class) {
    jclass clazz;
    jfieldID mString_fieldID;



    //获得 TestJavaClass 的 jclass 对象
    // jclass 类型是一个局部引用
    clazz = env->GetObjectClass(test_java_class);


    if (clazz == NULL) {
        return;
    }

    //获得 mString 的 fieldID
    mString_fieldID = env->GetFieldID(clazz, "mString", "Ljava/lang/String;");
    if (mString_fieldID == NULL) {
        return;
    }

    //获得 mString 的值
    jstring j_string = (jstring) env->GetObjectField(test_java_class, mString_fieldID);
    //GetStringUTFChars 分配了内存,需要使用 ReleaseStringUTFChars 释放
    const char *buf = env->GetStringUTFChars(j_string, NULL);

    //修改 mString 的值
    char *buf_out = "Hello Java, I am JNI!";
    jstring temp = env->NewStringUTF(buf_out);
    env->SetObjectField(test_java_class, mString_fieldID, temp);


    //jfieldID 不是 JNI 引用类型,不用 DeleteLocalRef
    // jfieldID 是一个指针类型,其内存的分配与回收由 JVM 负责,不需要我们去 free
    //free(mString_fieldID);

    //释放内存
    env->ReleaseStringUTFChars(j_string, buf);
    //释放局部引用表
    env->DeleteLocalRef(j_string);
    env->DeleteLocalRef(clazz);

}

//访问静态成员变量
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_accessStaticField(JNIEnv *env, jobject thiz,
                                                            jobject test_java_class) {
    jclass clazz;
    jfieldID mStaticIntFiledID;

    clazz = env->GetObjectClass(test_java_class);

    if (clazz == NULL) {
        return;
    }

    mStaticIntFiledID = env->GetStaticFieldID(clazz, "mStaticInt", "I");

    //获取静态成员
    jint mInt = env->GetStaticIntField(clazz, mStaticIntFiledID);
    //修改静态成员
    env->SetStaticIntField(clazz, mStaticIntFiledID, 10086);

    env->DeleteLocalRef(clazz);
    
}

访问一个类成员基本分为三部:

  • 获取到类对应的 jclass 对象(对应于 Java 层的 Class 对象),jclss 是一个局部引用,使用完后记得使用 DeleteLocalRef 以避免局部引用表溢出。
  • 获取到需要访问的类成员的 jfieldID,jfieldID 不是一个 JNI 引用类型,是一个普通指针,指针指向的内存又 JVM 管理,我们无需在使用完后执行 free 清理操作
  • 根据被访问对象的类型,使用 GetxxxField 和 SetxxxField 来获得/设置成员变量的值

2. Native 访问 Java 方法

我们直接看 Demo:

Java 层

//等待被 native 层访问的 java 类
public class TestJavaClass {



    //......
    private void myMethod() {
        Log.i("JNI", "this is java myMethod");
    }



    private static void myStaticMethod() {
        Log.d("JNI", "this is Java myStaticMethod");
    }


}

//本地方法
public native void accessJavaMethod();


public native void accessStaticMethod();

C++ 层:

extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_accessJavaMethod(JNIEnv *env, jobject thiz) {

    //获取 TestJavaClass 对应的 jclass
    jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass");
    if (clazz == NULL) {
        return;
    }

    //构造函数 id
    jmethodID java_construct_method_id = env->GetMethodID(clazz, "<init>", "()V");

    if (java_construct_method_id == NULL) {
        return;
    }


    //创建一个对象
    jobject object_test = env->NewObject(clazz, java_construct_method_id);
    if (object_test == NULL) {
        return;
    }

    //获得 methodid
    jmethodID java_method_id = env->GetMethodID(clazz, "myMethod", "()V");
    if (java_method_id == NULL) {
        return;
    }

    //调用 myMethod 方法
    env->CallVoidMethod(object_test,java_method_id);


    //清理临时引用吧  
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(object_test);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_accessStaticMethod(JNIEnv *env, jobject thiz) {

    jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass");
    if (clazz == NULL) {
        return;
    }

    jmethodID static_method_id = env->GetStaticMethodID(clazz, "myStaticMethod", "()V");
    if(NULL == static_method_id)
    {
        return;
    }

    env->CallStaticVoidMethod(clazz, static_method_id);

    env->DeleteLocalRef(clazz);

}

Native 访问一个 Java 方法基本分为三部:

  • 获取到类对应的 jclass 对象(对应于 Java 层的 Class 对象),jclss 是一个局部引用,使用完后记得使用 DeleteLocalRef 以避免局部引用表溢出。
  • 获取到需要访问的方法的 jmethodID,jmethodID 不是一个 JNI 引用类型,是一个普通指针,指针指向的内存由 JVM 管理,我们无需在使用完后执行 free 清理操作
  • 接着就可以调用 CallxxxMethod/CallStaticxxxMethod 来调用对于的方法,xxx 是方法的返回类型。

参考资料

关于

我叫阿豪,2015 年本科毕业于国防科技大学指挥自动化专业,毕业后,从事信息化装备的研发工作。主要研究方向为 Android Framework 与 Linux Kernel,2023年春节后开始做 Android Framework 相关的技术分享。

如果你对 Framework 感兴趣或者正在学习 Framework,可以参考我总结的Android Framework 学习路线指南,也可关注我的微信公众号,我会在公众号上持续分享我的经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYzzVU7d' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片