首页 > 代码库 > Android中关于JNI 的学习(三)在JNI层访问Java端对象
Android中关于JNI 的学习(三)在JNI层访问Java端对象
前面两篇文章简单介绍了JNI层跟Java层的一些对应关系,包括方法名,数据类型和方法名称等,相信在理论层面,能够很好地帮助我们去了解JNI在Native本地开发中的作用,对JNI的一些概念也有了一个初步的认识,由于表达能力或者理解还是有限,有些地方讲得不是很清楚,如果各位朋友有觉得云里雾里,欢迎大家留言一起学习。
概念上的理解有助于我们更好地认识JNI,而一些实际点的例子则能够更好地帮我们从代码上去掌握并应用JNI。
在第一篇文章,我们是从一个小例子来入门学习的,在其中,我们通过JNI层函数返回了一字符串,如下:
JNIEXPORT jstring JNICALL Java_com_lms_jni_HwDemo_printHello (JNIEnv *e, jobject j) { return (**e).NewStringUTF(e,"Hello from T"); }
这是一种最简单的情况,但更多时候,我们需要在JNI层获得Java对象,对其进行操作,最后将结果返回到Java端,所以这个时候我们就要利用到JNI函数定义的第二个参数 jobject了。
上一篇文章,我们说过,JNIEnv *和jobejct参数都是JNI层方法添加的参数,关于JNIEnv*我们已经在前面的文章简单介绍过,而jobject参数呢,则我们这一篇文章要操作到的参数了。
对于本地方法,即在Java中定义的native方法,有静态(static)和非静态的方法,而我们知道,静态方法它是属于这个类的方法,对象不能操作它,而非静态方法则刚好相反,所以在JNI层的方法参数中:
1)对于静态(static)方法,jobject参数表示的是对应Java类的引用。
2)对于非静态方法,jobject表示的是对应Java对象的引用。
这一点,应该不难理解。
接下来,我们通过一个小Demo来学习怎么在JNI层操作Java端的对象,并且改变其中的值。
首先,我们在Java类中定外一个static的变量testval,还有一个方法changeTestVal(),用来改变testval的值,如下:
public class ParamTransferTest { public static int testval = 1; public native void changeTestVal(); ... }
当然,首先,第一步,我们要在C中实现其对应的函数了,如下:
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal (JNIEnv * env, jobject obj){ jclass clazz = (*env)->GetObjectClass(env,obj); jint val = (*env)->GetStaticIntField(env, clazz, (*env)->GetStaticFieldID(env, clazz,"testval","I")); LOGI("before change testval = %d", val); val = val + 1; LOGI("after change testval = %d", val); (*env)->SetStaticIntField(env, clazz,(*env)->GetStaticFieldID(env, clazz,"testval","I"),val); }
我们在对应的c文件中来实现这个native方法,因为实现的是非静态方法,所以jobject传过来的就是对该对象的引用,所以我们需要通过GetObjectClass方法来获得该对象对应的类。
一般在JNI中,我们会利用FindClass和GetObjectClass两个方法来获得对应的类,并放到jclass类型的变量中去,不过在这里注意一点,用C实现和用C++实现的代码对于JNI的调用方法是不一样的。
在前面文章中说过,C++对JNINativeInterface定义的方法进行了一层包装,所以其参数不再需要传递env进去,而C则是需要的,比如上面*env调用的方法,如果是用C++实现的话,那么是不再需要传递env参数进去的,即 GetObjectClass(jobject)就可以了。
1)利用GetObjectClass方法获得jclass。
2)调用GetStaticIntFieldID获得对应class对应的变量,即jclass中的类型为I(即int)的静态(static)变量 testval。
3)调用GetStaticIntField获得对应变量的值 val。
4)改变val 的值,在这里,我们进行加1操作。
5)调用SetStaticIntField来设置对应变量的值。
所以,在这里我们发现,Env其实提供了很多的方法,对于访问对象变量值的,分为静态非静态的,基本上就是Get<Type>Field和GetStatic<Type>Field,
而相应的,也有Set<Type>Field和SetStatic<Type>Field方法。
而如果调用方法呢,就是利用Call<Type>Method和CallStatic<Type>Method方法了,这些大家可以自己去jni.h文件中自己看一下,就大概知道怎么做了。
JNI层这边实现好了之后,我们利用ndk-build工具重新生成一个so库,加载到Android中,在Activity中直接调用方法,如下:
TextView tvChangeTestVal = (TextView)findViewById(R.id.tvChangeTestVal); ParamTransferTest ptt = new ParamTransferTest(); ptt.changeTestVal(); tvChangeTestVal.setText("" + ptt.testval);
我们调用方法之后,在屏幕上将调用方法后的值,显示出来,结果应该是1+1=2,对吧,看下面结果:
的确如我们所想像的,它的值已经变化成2了,对吧,说明我们的确是通过native方法在JNI层改变了其值。
我们刚才也在JNI中添加了log,来展示其改变前后的值,如下:
通过这样一个简单的小例子,相信大家应该就知道了怎么样在JNI层来操作Java端的数据了,对吧。
结束!