Android JNI和NDK学习(四):JNI调用Java方法和变量

概述

今天继续学习JNI,前几篇学习了一些基本的内容,今天我们全部实践一下,这篇文章仅作为笔记,以防以后忘记

JNI访问Java对象的成员

先来看下需要用到的Api

获取jclass

jclass GetObjectClass(JNIEnv *env, jobject obj);
  • jobject:代表java的对象
  • 函数返回一个class对象

获取jfieldID

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
  • JNIEnv:JNIEnv指针
  • jclass:class对象,可以通过GetObjectClass或者FindClass获取
  • name:class对象中某个变量的名字
  • sig:变量的类型签名
  • 返回一个class对象的变量,类型为jfieldID

获取变量的值

根据变量的类型,会有不同的函数来获取变量的值,函数的基本形式如下:

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
函数名 返回值类型
GetBooleanField() jboolean
GetByteField() jbyte
GetCharField() jchar
GetShortField() jshort
GetIntField() jint
GetLongField() jlong
GetFloatField() jfloat
GetDoubleField() jdouble
GetObjectField() jobject

前八项是基本数据类型,第九项是获取所有引用数据类型

例如获取int变量,如下:

jint GetIntField(JNIEnv * env, jobject obj, jfieldID fieldID);
  • jobject:代表一个java对象
  • jfieldID:代表class的一个变量,通过GetFieldID获取
  • 返回一个jint

设置变量的值

根据变量的类型,有不同的函数设置变量的值

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
                    NativeType value);
函数名 参数类型
SetBooleanField() jboolean
SetByteField() jbyte
SetCharField() jchar
SetShortField() jshort
SetIntField() jint
SetLongField() jlong
SetFloatField() jfloat
SetDoubleField() jdouble
SetObjectField() jobject

前八项对应基本数据类型,第九项java所有引用类型

例如,int的函数原型

void setIntField(JNIEnv * env, jobject obj, jfieldID fieldID, jint value);
  • value:你要设置int的值,其他参数跟上方都一样,不在重复说

获取jmethodID

jmethodID GetMethodID(JNIEnv *env, jclass clazz,
                        const char *name, const char *sig);
  • name:获取class对象中方法的名字
  • sig:获取方法的签名
  • 返回值:如果存在这个方法返回jmethodID,如果不存在返回NULL
  • 如果获取java的构造方法,const char *name的值为,参数const char *sig的值为void(V)

调用对象的方法

根据java方法返回值得不同,jni有不同的函数来调用java对象的方法,基本的形式有三种

NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);

NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
jmethodID methodID, const jvalue *args);

NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
jmethodID methodID, va_list args);

这三个的方法的区别在与传递参数的不同,最常用的是第一个,我们只讲第一个

如果方法返回的是int,那么函数原型为

jint CallIntMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);

如果方法的返回值是引用数据类型

jobject CallObjectMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);

如果java的返回值类型为void

void CallVoidMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
  • jmethodID:对象的方法,可以通过GetMethodID获取
  • …:可变参数,你需要传递给java方法的参数

下面我们看下实现的完整代码

首先定义一个java类,里面有个native方法,当创建对象是会调用

public class Student {
     
    public String name = "小红";

    static {
     
        System.loadLibrary("studentlib");
    }

    native void native_init();

    public Student() {
     
        native_init();
    }


    public void print() {
     
        Log.d("mmm", name);
    }
}

在jni注册这个java的native方法,然后用C实现它

#include 
#include 
#include 

static void native_init(JNIEnv *env, jobject jobject1) {
     

    //1 获取java对象的变量的值,并重新为他设置新的值


    //获取class对象
    jclass jclass_student = env->GetObjectClass(jobject1);
    //从class中获取变量
    jfieldID jfieldId = env->GetFieldID(jclass_student, "name", "Ljava/lang/String;");
    //从java对象obj中获取name变量的值
    jstring name = static_cast<jstring>(env->GetObjectField(jobject1, jfieldId));
    const char *char_name = env->GetStringUTFChars(name, JNI_FALSE);
    //打印出原来的name变量的值
    __android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__,
                        char_name);
    const char native_name[] = "小明";
    jstring jstring_name = env->NewStringUTF(native_name);
    //通过jni重新设置变量name的值
    env->SetObjectField(jobject1, jfieldId, jstring_name);


    //2 调用java对象中的方法

    //获取class对象中的print方法
    jmethodID print = env->GetMethodID(jclass_student, "print", "()V");
    //调用java对象中的print方法
    env->CallVoidMethod(jobject1, print);

}

static const JNINativeMethod nativeMethod[] = {
     
        {
     "native_init", "()V", (void *) native_init},
};

static int registNativeMethod(JNIEnv *env) {
     
    int result = -1;

    jclass class_text = env->FindClass("com.taobao.alinnkit.ndk1.Student");
    if (env->RegisterNatives(class_text, nativeMethod,
                             sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
     
        result = 0;
    }
    return result;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     
    JNIEnv *env = NULL;
    int result = -1;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
     
        if (registNativeMethod(env) == JNI_OK) {
     
            result = JNI_VERSION_1_6;
        }
        return result;
    }
}

动态注册上篇文章讲了,这里就不多说了,主要的实现逻辑就在native_init的实现里,大概逻辑就是,获取Student对象中的变量name的值,并打印出来,然后重新设置name变量的值,最后调用Student对象的print方法,打印出name变量的值

最后就是调用,直接new就可以触发

  protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Student student = new Student();
    }

看下打印结果

/mmm: method = native_init, msg = 小红
/mmm: 小明

JNI访问java的静态成员

先看下用带的Api

FindClass

获取class对象

jclass FindClass(JNIEnv *env, const char *name);
  • const char *name:全限定的class名

GetStaticFieldID

获取class对象的静态字段

jfieldID GetStaticFieldID (JNIEnv *env, 
                            jclass clazz, 
                            const char *name, 
                            const char *sig);

  • name:静态字段的名字
  • sig:静态字段的类型签名

GetStaticField

获取静态字段的值,根据静态字段的类型不同,jni有不同的方法获取静态字段的值

方法名 返回值
GetStaticBooleanField jboolean
GetStaticByteField jbyte
GetStaticCharField jchar
GetStaticShortField jshort
GetStaticIntField jint
GetStaticLongField jlong
GetStaticFloatField jfloat
GetStaticDoubleField jdouble
GetStaticObjectField jobject

设置静态变量的值

void SetStatic<type>Field(JNIEnv *env, 
                        jclass clazz,
                        jfieldID fieldID, 
                        NativeType value);

  • 最后一个参数就是你需要设置的值
方法名 NativeType
SetStaticBooleanField jboolean
SetStaticByteField jbyte
SetStaticCharField jchar
SetStaticShortField jshort
SetStaticIntField jint
SetStaticLongField jlong
SetStaticFloatField jfloat
SetStaticDoubleField jdouble
SetStaticObjectField jobject

调用静态方法

jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)

void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)

这俩个跟之前调用普通方法用法一样就是换了个函数名

完整代码

java代码

public class Student {
     

    public static String text = "没改过";

    static {
     
        System.loadLibrary("studentlib");
    }


    native void native_Text();

    public static void printText() {
     
        Log.d("mmm", text);
    }

}

c代码

static void native_text(JNIEnv *env, jobject jobject1) {
     

    //获取class
    jclass jclass_student = env->FindClass("com.taobao.alinnkit.ndk1.Student");
    //获取静态变量text
    jfieldID jfieldId_text = env->GetStaticFieldID(jclass_student, "text", "Ljava/lang/String;");
    //获取静态变量text的值
    jstring text = (jstring) env->GetStaticObjectField(jclass_student, jfieldId_text);
    const char *char_name = env->GetStringUTFChars(text, JNI_FALSE);
    __android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__,
                        char_name);
    jstring jstring1 = env->NewStringUTF("改过了");
    //修改静态变量text的值
    env->SetStaticObjectField(jclass_student, jfieldId_text, jstring1);
    //获取静态方法printText
    jmethodID jmethodId = env->GetStaticMethodID(jclass_student, "printText", "()V");
    //调用静态方法printText
    env->CallStaticVoidMethod(jclass_student, jmethodId);
}

static const JNINativeMethod nativeMethod[] = {
     
        {
     "native_Text", "()V", (void *) native_text}

};

static int registNativeMethod(JNIEnv *env) {
     
    int result = -1;

    jclass class_text = env->FindClass("com.taobao.alinnkit.ndk1.Student");
    if (env->RegisterNatives(class_text, nativeMethod,
                             sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
     
        result = 0;
    }
    return result;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     
    JNIEnv *env = NULL;
    int result = -1;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
     
        if (registNativeMethod(env) == JNI_OK) {
     
            result = JNI_VERSION_1_6;
        }
        return result;
    }
}

主要的实现就是native_text方法,主要做了获取静态变量text的值,修改静态变量text的值,调用静态方法printText

调用

 protected void onCreate(Bundle savedInstanceState) {
     
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Student student = new Student();
        student.native_Text();
    }

打印数据

/mmm: method = native_text, msg = 没改过
/mmm: 改过了

参考

https://juejin.im/post/5d23fb066fb9a07eb15d7b29

https://blog.csdn.net/afei__/article/details/81016413

https://www.jianshu.com/p/67081d9b0a9c

你可能感兴趣的:(jni,jni)