访问对象的成员属性和成员方法: 下面我们通过一个例子来介绍对象成员属性和成员方法的访问:
首先我们先定义一个测试类,在这篇博客中将对测试类的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) ->     // 获得对应的方法id 	jmethodID methodId = (*env) ->"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) ->     jmethodID methodId = (*env) ->"getTestString" ,"()Ljava/lang/String;" );     if (methodId == NULL) {         return  NULL;     }     jstring str = (*env) ->     return  str; } // 通过直接访问属性域,来设置testString值。JNIEXPORT void  JNICALL Java_com_idealist_myapplication_MainActivity_setTestStrByField(JNIEnv *env, jobject instance,                                                                jobject obj, jstring str_) {     jclass  testCls  = (*env) ->     jfieldID  fieldId = (*env) ->"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) ->     jfieldID fieldId = (*env) ->"testString" ,"Ljava/lang/String;" );     if (fieldId == NULL) {         return  NULL;     }     jstring str = (*env) ->     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方法中。  
 
访问构造函数:“作为方法名”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)->"com/idealist/myapplication/ChildClass" );      	jmethodID  contructureid = (*env)->"<init>" ,"()V" );      	jobject  childObj = (*env)-> 	     jmethodID  sayhelloid = (*env)->"sayHello" ,"(Ljava/lang/String;)V" );     (*env )-> 	     jclass  suppercls = (*env)->"com/idealist/myapplication/FatherClass" ); 	     jmethodID  sayhellofromSuper = (*env)->"sayHello" ,"(Ljava/lang/String;)V" ); 	     (*env )-> 	     (*env )->     (*env )->     (*env )->     (*env )->     (*env )->     (*env )-> } 
输出结果:
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 这种回调方式。