访问对象的成员属性和成员方法:

下面我们通过一个例子来介绍对象成员属性和成员方法的访问:

首先我们先定义一个测试类,在这篇博客中将对测试类的testString方法进行访问和设置,我们先看下这个测试类:

public class Testjni {

private String testString;

public String getTestString() {
return testString;
}

public void setTestString(String testString) {
this.testString = testString;
}
}
//通过调用Java层方法来实现某个功能
JNIEXPORT void JNICALL
Java_com_idealist_myapplication_MainActivity_setTestString(JNIEnv *env, jobject instance,
jobject obj, jstring str_) {
//获得某个对象的类类型
jclass testCls = (*env)->GetObjectClass(env,obj);
//获得对应的方法id
jmethodID methodId = (*env)->GetMethodID(env,testCls,"setTestString","(Ljava/lang/String;)V");
if(methodId == NULL) {
return;
}
//通过方法id 调用该方法
(*env)->CallVoidMethod(env,obj,methodId,str_);
}

//通过调用Java层的方法,来获取testString
JNIEXPORT jstring JNICALL
Java_com_idealist_myapplication_MainActivity_getTestString(JNIEnv *env, jobject instance,
jobject obj) {

jclass testCls = (*env)->GetObjectClass(env,obj);
jmethodID methodId = (*env)->GetMethodID(env,testCls,"getTestString","()Ljava/lang/String;");
if(methodId == NULL) {
return NULL;
}
jstring str = (*env)->CallObjectMethod(env, obj, methodId);
return str;
}

//通过直接访问属性域,来设置testString值。
JNIEXPORT void JNICALL
Java_com_idealist_myapplication_MainActivity_setTestStrByField(JNIEnv *env, jobject instance,
jobject obj, jstring str_) {

jclass testCls = (*env)->GetObjectClass(env, obj);
jfieldID fieldId = (*env)->GetFieldID(env,testCls,"testString","Ljava/lang/String;");
if(fieldId == NULL) {
return;
}
(*env)->SetObjectField(env, obj, fieldId, str_);
}
//通过直接访问属性域,来获取testString值。
JNIEXPORT jstring JNICALL
Java_com_idealist_myapplication_MainActivity_getTestStringByField(JNIEnv *env, jobject instance,
jobject obj) {
jclass testCls = (*env)->GetObjectClass(env,obj);
jfieldID fieldId = (*env)->GetFieldID(env,testCls,"testString","Ljava/lang/String;");
if(fieldId == NULL) {
return NULL;
}
jstring str = (*env)->GetObjectField(env, obj, fieldId);
return str;
}

这个是来自JNI Programmer’s Guide and Specification文档中的一个例子:

JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid;/* store the field ID */
jstring jstr;
const char *str;
/* 根据jobject对象实例创建指向它的类引用 */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* 根据上述找到的jclass类型以及类名和标识符寻找fieldID 最后一个参数可以通过javap -p -s InstanceFieldAccess 来查看*/
fid = (*env)->GetFieldID(env, cls, "s","Ljava/lang/String;");
if (fid == NULL) {
return; /* failed to find the field */
}
/* 读取对象的成员属性域*/
jstr = (*env)->GetObjectField(env, obj, fid);
/* 转化为C语言的字符串形式 */
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
/* 释放掉无用的资源 */
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* 创建一个新的字符串 */
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
/*设置对象属性值*/
(*env)->SetObjectField(env, obj, fid, jstr);
}

下面是访问静态成员变量的例子:

JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid;
/* store the field ID */
jint si;
/* Get a reference to obj’s class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the static field si in cls */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
if (fid == NULL) {
return; /* field not found */
}
/* Access the static field si */
si = (*env)->GetStaticIntField(env, cls, fid);
printf(" StaticFieldAccess.si = %d\n", si);
(*env)->SetStaticIntField(env, cls, fid, 200);
}

下面是访问静态成员方法的例子:

JNIEXPORT void JNICALL
Java_StaticMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid =(*env)->GetStaticMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallStaticVoidMethod(env, cls, mid);
}

上面我们介绍了通过GetObjectClass来获得某个jclass,下面将通过FindClass来获取:

jobject thd = ...; /* a java.lang.Thread instance */
jmethodID mid;
jclass runnableIntf = (*env)->FindClass(env, "java/lang/Runnable");
if (runnableIntf == NULL) {
... /* error handling */
}
mid = (*env)->GetMethodID(env, runnableIntf, "run", "()V");
if (mid == NULL) {
... /* error handling */
}
(*env)->CallVoidMethod(env, thd, mid);
... /* check for possible exceptions */

访问属性和方法总结:

调用对象的静态方法的步骤

1. 使用FindClass或者GetObjectClass来获取jclass对象
2. 通过GetMethodID获取在某个类中的方法的id值。这一步需要步骤1中获取到的jclass对象,方法名,方法签名
3. 通过这个id值来调用通过CallStaticXXXMethod方法传入id,参数
4. 释放局部变量

调用对象的实例方法的步骤(没有传入jobject的情况)

1. 使用FindClass搜索某个类
2. 获取某个类的默认构造方法
3. 通过NewObject创建该类的实例
4. 通过GetMethodID查找要调用的实例对象的方法id
5. 通过CallXXXMethod调用实例的方法
6. 释放局部变量

调用对象的实例方法的步骤(有传入jobject的情况)

1. 如果有传入对象的话可以使用GetObjectClass获取jclass对象
2. 通过GetMethodID查找要调用的实例对象的方法id
3. 通过CallXXXMethod调用实例的方法
4. 释放局部变量

获取和设置实例对象的属性

1. 通过 GetObjectClass 获取类的jclass引用
2. 通过GetFieldID获取实例变量的属性id
3. 通过GetXXXField来获取某个属性变量的值
4. 通过setObjectField设置某个属性对象的值
5. 删除局部变量

获取和设置某个静态属性

1. 通过FindClass 获取类的jclass引用
2. 通过GetStaticFieldID获得静态属性id
3. 通过GetStaticXXXField来获取某个属性变量的值
4. 通过setStaticObjectField设置某个属性对象的值
5. 删除局部变量

访问父类方法和访问构造函数:

  1. 访问父类方法:

我们可以调用在父类中已经定义但是已经被覆写的实例方法,JNI 中提供了一系列的CallNonvirtual 方法用来解决这个问题,为了能够调用父类的方法需要如下处理:

  • 使用GetMethodID或者GetStaticMethodID从父类引用中获得方法id
  • 传入对象,父类,方法id到上面提到的CallNonvirtual方法中。
  1. 访问构造函数:
    在JNI中可以通过和调用实例对象的其他方法一样调用构造方法,唯一的区别是需要传入”“作为方法名”V” 作为返回类型标识。然后通过调用 NewObject并传入方法id来创建出实例对象。
    jstring
    MyNewString(JNIEnv *env, jchar *chars, jint len){
    jclass stringClass;
    jmethodID cid;
    jcharArray elemArr;
    jstring result;
    stringClass = (*env)->FindClass(env, "java/lang/String");
    if (stringClass == NULL) {
    return NULL; /* exception thrown */
    }
    /* Get the method ID for the String(char[]) constructor */
    cid = (*env)->GetMethodID(env, stringClass,"<init>", "([C)V");
    if (cid == NULL) {
    return NULL; /* exception thrown */
    }
    /* Create a char[] that holds the string characters */
    elemArr = (*env)->NewCharArray(env, len);
    if (elemArr == NULL) {
    return NULL; /* exception thrown */
    }
    (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
    /* Construct a java.lang.String object */
    result = (*env)->NewObject(env, stringClass, cid, elemArr);
    /* Free local references */
    (*env)->DeleteLocalRef(env, elemArr);
    (*env)->DeleteLocalRef(env, stringClass);
    return result;
    }

下面通过一个例子来验证上述的知识点:

JNIEXPORT void JNICALL
Java_com_idealist_myapplication_MainActivity_testSayHello(JNIEnv *env, jobject instance,
jstring name_) {
//获取ChildClass类
jclass clzz = (*env)->FindClass(env, "com/idealist/myapplication/ChildClass");
//获取ChildClass的构造函数
jmethodID contructureid = (*env)->GetMethodID(env,clzz,"<init>","()V");
//使用构造函数创建一个对象
jobject childObj = (*env)->NewObject(env, clzz, contructureid);

//通过这个对象调用ChildClass sayHello的方法
jmethodID sayhelloid = (*env)->GetMethodID(env,clzz,"sayHello","(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, childObj, sayhelloid, name_);

//通过FindClass获取FatherClass类
jclass suppercls = (*env)->FindClass(env,"com/idealist/myapplication/FatherClass");
//获取FatherClass的sayHello的id
jmethodID sayhellofromSuper = (*env)->GetMethodID(env,suppercls,"sayHello","(Ljava/lang/String;)V");
//通过子类对象,父类jclass,父类方法id来调用父类的方法。
(*env)->CallNonvirtualVoidMethod(env, childObj, suppercls, sayhellofromSuper, name_);

//释放局部变量
(*env)->DeleteLocalRef(env, clzz);
(*env)->DeleteLocalRef(env, contructureid);
(*env)->DeleteLocalRef(env, childObj);
(*env)->DeleteLocalRef(env, sayhelloid);
(*env)->DeleteLocalRef(env, suppercls);
(*env)->DeleteLocalRef(env, sayhellofromSuper);
}

输出结果:

07-05 21:52:49.212 20472-20472/com.idealist.myapplication I/xiaohai.lin: Constructure of FatherClass
07-05 21:52:49.212 20472-20472/com.idealist.myapplication I/xiaohai.lin: Constructure of ChildClass
07-05 21:52:49.212 20472-20472/com.idealist.myapplication I/xiaohai.lin: From Child class Hello Jimmy
07-05 21:52:49.212 20472-20472/com.idealist.myapplication I/xiaohai.lin: From Super class Hello Jimmy

缓存FieldID 以及methodID

每次获取FieldID以及和methodID都需要对字段、方法的名字和描述符进行一费时的检索过程。为了减小检索过程的耗时行为可以将第一次获取到的字段id缓存起来,缓存方法有两种,一种是通过将id声明为static变量在使用时缓存,另一种实在类静态初始化的时候。

  • 使用时缓存:
    JNIEXPORT void JNICALL
    Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) {
    //使用静态变量缓存
    static jfieldID fid_s = NULL; /* cached field ID for s */
    jclass cls = (*env)->GetObjectClass(env, obj);
    jstring jstr;
    const char *str;
    //判断是否是第一次
    if ( fid_s == NULL) {
    fid_s = (*env)->GetFieldID(env, cls, "s","Ljava/lang/String;");
    if ( fid_s == NULL) {
    return; /* exception already thrown */
    }
    }
    printf("In C:\n");
    jstr = (*env)->GetObjectField(env, obj, fid_s );
    str = (*env)->GetStringUTFChars(env, jstr, NULL);
    if (str == NULL) {
    return; /* out of memory */
    }
    printf(" c.s = \"%s\"\n", str);
    (*env)->ReleaseStringUTFChars(env, jstr, str);
    jstr = (*env)->NewStringUTF(env, "123");
    if (jstr == NULL) {
    return; /* out of memory */
    }
    (*env)->SetObjectField(env, obj, fid_s , jstr);
    }
  • 类初始化时缓存:
    class InstanceMethodCall {
    private static native void initIDs();
    private native void nativeMethod();
    private void callback() {
    System.out.println("In Java");
    }
    public static void main(String args[]) {
    InstanceMethodCall c = new InstanceMethodCall();
    c.nativeMethod();
    }
    static {
    System.loadLibrary("InstanceMethodCall");
    initIDs();
    }
    }

    //在类初始化的时候就获取要使用方法id或者成员变量id,并缓存起来。
    jmethodID MID_InstanceMethodCall_callback;
    JNIEXPORT void JNICALL
    Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls)
    {
    MID_InstanceMethodCall_callback =
    (*env)->GetMethodID(env, cls, "callback", "()V");
    }

那么到底用哪种方式比较合适呢?这个必须依据实际情况:
如果我们不能修改方法或者字段所在的类的源码的话,这时候就不用说得用使用时缓存,但是如果可以修改的话尽量使用静态初始化时缓存,因为在使用时缓存需要每次使用的时候都需要判断一下才能使用。这样效率往往不高。

是否需要引入jni

在我们印象中引入jni会提高运行效率,事实并不是这样的java/native 比起 JVM 内部的 java/java 来说有一个调用转换过程,在把控制权和入口切换给本地方法之前,VM 必须做一些额外的操作来创建参数和栈帧。这个步骤会导致java/native 调用比java/java 要慢。我们再来看下native/java 调用和 java/native从技术角度来看, native/java 调用和 java/native 是相似的。但实际上 native/java调用很少见,因此VM 通常不会优化 native/java 这种回调方式。
这就导致了native/java 调用的消耗可以达到 java/java 调用的好几倍。并且引入jni会对调试引入极大的难度,所以不是万不得已最好不要引入Jni。

Contents
  1. 1. 访问对象的成员属性和成员方法:
  2. 2. 访问属性和方法总结:
  3. 3. 访问父类方法和访问构造函数:
  4. 4. 缓存FieldID 以及methodID
  5. 5. 是否需要引入jni