JNI访问Java类函数

xiaoeryu Lv5

在之前demo的基础上添加几个方法,测试在JNI编程中怎么访问Java类中的函数

在JNI和Java中访问类属性有什么区别:

对以下这几种类型的函数进行访问

构造函数的访问是一个特例

在JNI中访问Java函数要加上函数描述符

调用构造函数

extern "C"
JNIEXPORT jobject JNICALL
Java_com_xiaoeryu_reflectiontest_MainActivity_callInit(JNIEnv *env, jobject thiz) {
    // TODO: implement callInit()
    // public Test(String arg, int arg2)
    jclass TestJclass = env->FindClass("com/xiaoeryu/reflectiontest/Test");
    jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;I)V");
    jstring arg0 = env->NewStringUTF("i am from callInit");
    jobject obj = env->NewObject(TestJclass, con_mid, arg0, 100);
    return obj;
}

构造函数调用的时候函数名使用代替

调用静态函数

extern "C"
JNIEXPORT void JNICALL
Java_com_xiaoeryu_reflectiontest_MainActivity_callStaticFunc(JNIEnv *env, jobject thiz) {
    // TODO: implement callStaticFunc()
    // public static void publicStaticFunc()
    jclass Testjclass = env->FindClass("com/xiaoeryu/reflectiontest/Test");
    jmethodID publicStaticFunc_mid = env->GetStaticMethodID(Testjclass, "publicStaticFunc", "()V");

    env->CallStaticVoidMethod(Testjclass, publicStaticFunc_mid);

    // private static void privateStaticFunc()
    jmethodID privateStaticFunc_mid = env->GetStaticMethodID(Testjclass, "privateStaticFunc",
                                                             "()V");
    env->CallStaticVoidMethod(Testjclass, privateStaticFunc_mid);

//    public static int publicStaticFunc_int(int a){
//        Log.i("xiaoeryu", "i am from publicStaticFunc_int");
//        return 111 + a;
//    }
    jmethodID publicStaticFunc_int_mid = env->GetStaticMethodID(Testjclass, "publicStaticFunc_int",
                                                                "(I)I");
    jint res_value = env->CallStaticIntMethod(Testjclass, publicStaticFunc_int_mid, 001);
    __android_log_print(4, "xiaoeryu->jni", "publicStaticFunc_int->%d", res_value);

//    public static String publicStaticFunc_string(String arg){
//        Log.i("xiaoeryu", "i am from publicStaticFunc_int");
//        return "publicStaticFunc_string->" + arg;
//    }
    jmethodID publicStaticFunc_str_mid = env->GetStaticMethodID(Testjclass,
                                                                "publicStaticFunc_string",
                                                                "(Ljava/lang/String;)Ljava/lang/String;");
    jstring arg_str = env->NewStringUTF("i am from jni");
    jstring ret_str = static_cast<jstring>(env->CallStaticObjectMethod(Testjclass,
                                                                       publicStaticFunc_str_mid,
                                                                       arg_str));
    const char *content = env->GetStringUTFChars(ret_str, nullptr);
    __android_log_print(4, "xiaoeryu->jni", "publicStaticFunc_string->%s", content);

}

JNI调用java函数不分public/private

call的类型也分为引用类型CallStaticObjectMethod和基础类型CallStaticVoidMethod、CallStaticIntMethod 等。。。

调用非静态函数

extern "C"
JNIEXPORT void JNICALL
Java_com_xiaoeryu_reflectiontest_MainActivity_callNonStaticFunc(JNIEnv *env, jobject thiz) {
    // TODO: implement callNonStaticFunc()
    jclass TestJclass = env->FindClass("com/xiaoeryu/reflectiontest/Test");
    jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;I)V");
    jstring arg = env->NewStringUTF("i am from callInit");
    jobject testObj = env->NewObject(TestJclass, con_mid, arg, 100);
    // public void publicFunc()
    jmethodID publicFunc_mid = env->GetMethodID(TestJclass, "publicFunc", "()V");
//    void CallVoidMethod(jobject obj, jmethodID methodID, ...)
    env->CallVoidMethod(testObj, publicFunc_mid);

    // private String  privateFunc_str(int a, String b)
    jmethodID privateFunc_str_mid = env->GetMethodID(TestJclass, "privateFunc_str", "(ILjava/lang/String;)Ljava/lang/String;");
    jstring arg1 = env->NewStringUTF("i am from jni");
    // 调用不同CallObjectMethod的区别
    // CallObjectMethod
    // CallObjectMethodA
    // CallObjectMethodV
//    jstring ret_str = static_cast<jstring>(env->CallObjectMethod(testObj, privateFunc_str_mid, 002,
//                                                                 arg1));
    jvalue args[2];
    args[0].i = 003;
    args[1].l = arg1;
    jstring ret_str = static_cast<jstring>(env->CallObjectMethodA(testObj, privateFunc_str_mid,
                                                                  args));
    const char* result_ptr = env->GetStringUTFChars(ret_str, nullptr);
    __android_log_print(4, "xiaoeryu->jni", "privateFunc_str->%s", result_ptr);

    // private int[] privateFunc_array(int a)
    jmethodID privateFunc_array_mid = env->GetMethodID(TestJclass, "privateFunc_array", "(I)[I");
    jintArray array_obj = static_cast<jintArray>(static_cast<jarray>(env->CallObjectMethod(testObj,
                                                                                           privateFunc_array_mid,
                                                                                           20)));
    jint* array_ptr = env->GetIntArrayElements(array_obj, nullptr);
    for (int i = 0; i < env->GetArrayLength(array_obj); ++i) {
        __android_log_print(4, "xiaoeryu->jni", "array[%d]->%d", i, array_ptr[i]);
    }
}

非静态函数不能直接调用,需要先使用NewObject()实例化之后才能调用

每个Call函数有三种实现:这三种接口都可以完成函数的调用,只是参数会有区别

​ CallXXXMethod:一般方式:处理变长参数时可能不够灵活

​ CallXXXMethodA:使用jvalue数组传递参数,可以更方便的处理变长参数

​ CallXXXMethodV:使用va_list传递参数,也是为了处理变长参数。形式更加灵活,但是需要注意正确处理参数的类型和顺序

调用父类函数

例如我们如果要把这个父类的onCreate()以及它里面的一些调用方法进行本地化写成JNI函数应该怎么做呢

因为它是一个入口函数所以有很多壳会这么做,接下来看一下它是怎么实现的

PS:新版的布局函数调用出错了,换了老版的布局函数。以后再解决

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

换了老版的

setContentView(R.layout.activity_main);

TextView tv = findViewById(R.id.sample_text);
extern "C"
JNIEXPORT void JNICALL
Java_com_xiaoeryu_reflectiontest_MainActivity_onCreate(JNIEnv *env, jobject thiz,
                                                       jobject saved_instance_state) {
//        super.onCreate(savedInstanceState);

    // TODO: implement onCreate()
    // 有三种方法都可以获取到当前类引用
    jclass AppCompatActivity_jclass = env->FindClass("androidx/appcompat/app/AppCompatActivity");
    jclass MainActivity_jclass1 = env->FindClass("com/xiaoeryu/reflectiontest/MainActivity");
    jclass MainActivity_jclass2 = env->GetObjectClass(thiz);
    // 获取父类
    jclass AppCompatActivity_jclass2 = env->GetSuperclass(MainActivity_jclass2);
    // protected native void onCreate(Bundle savedInstanceState);
    // super.onCreate(savedInstanceState);
    jmethodID onCreate_mid = env->GetMethodID(AppCompatActivity_jclass, "onCreate", "(Landroid/os/Bundle;)V");
//    void CallNonvirtualVoidMethod(jobject obj, jclass clazz,
//                                  jmethodID methodID, ...)
    // 当前类对象是MainActivity类对象
    env->CallNonvirtualVoidMethod(thiz, AppCompatActivity_jclass, onCreate_mid,saved_instance_state);

    // 然后就可以调用Java中的方法了
    jstring arg1 = env->NewStringUTF("xiaoeryu");
    jstring arg2 = env->NewStringUTF("onCreate is Called!");
    // 使用Log也需要先找到它所在的类名创建类引用
    jclass LogJclass = env->FindClass("android/util/Log");
    // .i是一个静态函数可以在JNI中直接调用,所以就直接获取MethodID
    // public static int i(String tag, String msg)
    jmethodID Log_i_mid = env->GetStaticMethodID(LogJclass, "i", "(Ljava/lang/String;Ljava/lang/String;)I");

    jint result = env->CallStaticIntMethod(LogJclass, Log_i_mid, arg1, arg2);

    __android_log_print(4, "xiaoeryu->jni", "Log_i_mid->%d", result);

/* setContentView(R.layout.activity_main);
 * TextView tv = findViewById(R.id.sample_text);
 * Test testobj = (Test) callInit();
 * Log.i("xiaoeryu", testobj.flag);
*/
    jmethodID setContentView_mid = env->GetMethodID(MainActivity_jclass2, "setContentView", "(I)V");

    jclass R_layoutjclass = env->FindClass("com/xiaoeryu/reflectiontest/R$layout");
    jfieldID activity_main_fieldid = env->GetStaticFieldID(R_layoutjclass, "activity_main", "I");
    jint activity_main_value = env->GetStaticIntField(R_layoutjclass, activity_main_fieldid);

    env->CallVoidMethod(thiz, setContentView_mid, activity_main_value);

//    TextView tv = findViewById(R.id.sample_text);
    jmethodID findViewById_mid = env->GetMethodID(MainActivity_jclass2, "findViewById", "(I)Landroid/view/View;");

    jclass R_idjclass = env->FindClass("com/xiaoeryu/reflectiontest/R$id");
    jfieldID sample_text_fieldid = env->GetStaticFieldID(R_idjclass, "sample_text", "I");
    jint sample_text_value = env->GetStaticIntField(R_idjclass, sample_text_fieldid);

    env->CallObjectMethod(thiz, findViewById_mid, sample_text_value);

    /*
     * Test testobj = (Test) callInit();
     * Log.i("xiaoeryu", testobj.flag);
     * */
    jmethodID callInit_mid = env->GetMethodID(MainActivity_jclass2, "callInit", "()Ljava/lang/Object;");
    jobject testobj = env->CallObjectMethod(thiz,callInit_mid);

    jclass testjcalss = env->FindClass("com/xiaoeryu/reflectiontest/Test");
    jfieldID flagjfield = env->GetFieldID(testjcalss, "flag", "Ljava/lang/String;");
    jstring flagvalue = static_cast<jstring>(env->GetObjectField(testobj, flagjfield));

    jint result_flag = env->CallStaticIntMethod(LogJclass, Log_i_mid, arg1, flagvalue);

    __android_log_print(4, "xiaoeryu->jni", "flag->%d", result_flag);
}

通过对Oncreate以及它里面调用的实现可以发现,任意一个Java实现的函数都可以换成JNI来实现

代码地址

  • 标题: JNI访问Java类函数
  • 作者: xiaoeryu
  • 创建于 : 2023-10-14 21:38:59
  • 更新于 : 2023-10-19 11:19:34
  • 链接: https://github.com/xiaoeryu/2023/10/14/JNI访问Java类函数/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论