访问对象的成员属性和成员方法: 下面我们通过一个例子来介绍对象成员属性和成员方法的访问:
首先我们先定义一个测试类,在这篇博客中将对测试类的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层的方法,来获取testStringJNIEXPORT 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; jstring jstr; const char *str; jclass cls = (*env)->GetObjectClass(env , obj ) ; printf("In C:\n" ); fid = (*env)->GetFieldID(env , cls , "s" ,"Ljava/lang/String;" ) ; if (fid == NULL) { return; } jstr = (*env)->GetObjectField(env , obj , fid ) ; str = (*env)->GetStringUTFChars(env , jstr , NULL) ; if (str == NULL) { return; } printf(" c.s = \"%s\"\n" , str); (*env)->ReleaseStringUTFChars(env , jstr , str ) ; jstr = (*env)->NewStringUTF(env , "123" ) ; if (jstr == NULL) { return; } (*env)->SetObjectField(env , obj , fid , jstr ) ; }
下面是访问静态成员变量的例子:
JNIEXPORT void JNICALL Java_StaticFieldAccess_accessField(JNIEnv * env , jobject obj ) { jfieldID fid; jint si; jclass cls = (*env)->GetObjectClass(env , obj ) ; printf("In C:\n" ); fid = (*env)->GetStaticFieldID(env , cls , "si" , "I" ) ; if (fid == NULL) { return; } 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; } 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查找要调用的实例对象的方法id5. 通过CallXXXMethod调用实例的方法6. 释放局部变量
调用对象的实例方法的步骤(有传入jobject的情况)
1. 如果有传入对象的话可以使用GetObjectClass获取jclass对象2. 通过GetMethodID查找要调用的实例对象的方法id3. 通过CallXXXMethod调用实例的方法4. 释放局部变量
获取和设置实例对象的属性
1. 通过 GetObjectClass 获取类的jclass引用2. 通过GetFieldID获取实例变量的属性id3. 通过GetXXXField来获取某个属性变量的值4. 通过setObjectField设置某个属性对象的值5. 删除局部变量
获取和设置某个静态属性
1. 通过FindClass 获取类的jclass引用2. 通过GetStaticFieldID获得静态属性id3. 通过GetStaticXXXField来获取某个属性变量的值4. 通过setStaticObjectField设置某个属性对象的值5. 删除局部变量
访问父类方法和访问构造函数:
访问父类方法:
我们可以调用在父类中已经定义但是已经被覆写的实例方法,JNI 中提供了一系列的CallNonvirtual 方法用来解决这个问题,为了能够调用父类的方法需要如下处理:
使用GetMethodID或者GetStaticMethodID从父类引用中获得方法id
传入对象,父类,方法id到上面提到的CallNonvirtual方法中。
访问构造函数: 在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; } cid = (*env)->GetMethodID(env , stringClass ,"<init>" , "([C)V" ) ; if (cid == NULL) { return NULL; } elemArr = (*env)->NewCharArray(env , len ) ; if (elemArr == NULL) { return NULL; } (*env)->SetCharArrayRegion(env , elemArr , 0, len , chars ) ; result = (*env)->NewObject(env , stringClass , cid , elemArr ) ; (*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_) { jclass clzz = (*env)-> FindClass(env, "com/idealist/myapplication/ChildClass" ); jmethodID contructureid = (*env)-> GetMethodID(env,clzz,"<init>" ,"()V" ); jobject childObj = (*env)-> NewObject(env, clzz, contructureid); jmethodID sayhelloid = (*env)-> GetMethodID(env,clzz,"sayHello" ,"(Ljava/lang/String;)V" ); (*env )-> CallVoidMethod(env, childObj, sayhelloid, name_); jclass suppercls = (*env)-> FindClass(env,"com/idealist/myapplication/FatherClass" ); jmethodID sayhellofromSuper = (*env)-> GetMethodID(env,suppercls,"sayHello" ,"(Ljava/lang/String;)V" ); (*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 FatherClass07 -05 21 :52 :49 .212 20472 -20472 /com.idealist.myapplication I/xiaohai.lin: Constructure of ChildClass07 -05 21 :52 :49 .212 20472 -20472 /com.idealist.myapplication I/xiaohai.lin: From Child class Hello Jimmy07 -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; 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; } } printf("In C:\n" ); jstr = (*env)->GetObjectField(env , obj , fid_s ) ; str = (*env)->GetStringUTFChars(env , jstr , NULL) ; if (str == NULL) { return; } printf(" c.s = \"%s\"\n" , str); (*env)->ReleaseStringUTFChars(env , jstr , str ) ; jstr = (*env)->NewStringUTF(env , "123" ) ; if (jstr == NULL) { return; } (*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(); } } 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。