前言
在http://27house.cn/archives/1097中,我介绍了下了如何编写一个简单的Demo来让native输出hello world到java层,接下来本篇主要介绍Java层调用native方法,以及native层调用Java层的方法以及修改Java对象的属性。
Java层调用native层的方法
其实在上一篇中已经介绍了下如何调用native层方法,其实与调用普通的Java方法没有什么区别,只是方法由native层实现的而已。首先我们在Java类中编写native方法,然后使用上一篇博客中介绍的方法在native层实现之。
package org.ndk.ndkfirst;
 public class NDKTest {
     private static final String TAG = NDKTest;
     static {
         System.loadLibrary(ndk-test-lib);
     }
     private String mString = Ndk test string;
     /**
      * 在native中进行加法运算
      */
     public native int calcInNative(int num1, int num2);
 }
#include <jni.h>
 extern C
 {
    JNIEXPORT jint JNICALL
    Java_org_ndk_ndkfirst_NDKTest_calcInNative(JNIEnv *env, jobject obj, jint num1, jint num2) {
        return num1 + num2;
    }
 }
我们可以看到,只是将加法运算在native层实现而已,调用native方法与普通Java方法一样的,调用代码如下。
NDKTest nDKTest = new NDKTest();
nDKTest.calcInNative(1, 2);
native层调用Java方法
Java数据类型的对应
在native层调用Java方法,首先得知道Java类型与native的对应关系,以及Java类型以及方法在native中的签名格式。对于基础类型,对应关系如下。这些数据类型的定义全在jni.h中
 #define JNI_FALSE   0
 #define JNI_TRUE    1
 typedef unsigned char   jboolean;       /* unsigned 8 bits */
 typedef signed char     jbyte;          /* signed 8 bits */
 typedef unsigned short  jchar;          /* unsigned 16 bits */
 typedef short           jshort;         /* signed 16 bits */
 typedef int             jint;           /* signed 32 bits */
 typedef long long       jlong;          /* signed 64 bits */
 typedef float           jfloat;         /* 32-bit IEEE 754 */
 typedef double          jdouble;        /* 64-bit IEEE 754 */

对于非基础类型,比如String等,Array等统一对应jobject对象。
 class _jobject {};
 class _jclass : public _jobject {};
 class _jstring : public _jobject {};
 class _jarray : public _jobject {};
 class _jobjectArray : public _jarray {};
 class _jbooleanArray : public _jarray {};
 class _jbyteArray : public _jarray {};
 class _jcharArray : public _jarray {};
 class _jshortArray : public _jarray {};
 class _jintArray : public _jarray {};
 class _jlongArray : public _jarray {};
 class _jfloatArray : public _jarray {};
 class _jdoubleArray : public _jarray {};
 class _jthrowable : public _jobject {};
 typedef _jobject*       jobject;
 typedef _jclass*        jclass;
 typedef _jstring*       jstring;
 typedef _jarray*        jarray;
 typedef _jobjectArray*  jobjectArray;
 typedef _jbooleanArray* jbooleanArray;
 typedef _jbyteArray*    jbyteArray;
 typedef _jcharArray*    jcharArray;
 typedef _jshortArray*   jshortArray;
 typedef _jintArray*     jintArray;
 typedef _jlongArray*    jlongArray;
 typedef _jfloatArray*   jfloatArray;
 typedef _jdoubleArray*  jdoubleArray;
 typedef _jthrowable*    jthrowable;
 typedef _jobject*       jweak;

Java类型以及方法签名
在native中调用Java层的方法以及属性的时候需要使用相应的签名表示方法以及属性,下面就来介绍下基础类型与引用类型的签名表示方法。

从上面的表可以看到
- 
基础类型是使用一个大写字母表示,
 - 
对于数组则是使用[数组类型签名,比如int[]数组的签名为[I,一维数组是一个[,多维数组就是多个[[[
 - 
对于Java中的对象则是L类全路径;,不要忘记结尾的;号
 - 
对于对于函数方法则是(参数一签名参数二签名...)返回值签名,中间没有空格。比如上面的 public native int calcInNative(int num1, int num2);方法的前面为(II)I,其他的类似。构造函数名为
,返回值为V  - 
native调用Java中的方法
首先我们得知道,任何jni的方法都需要通过JNIEnv *env参数去调用
//C++形式 env->方法名 //C形式 (*env)->方法名native层调用Java层方法以及属性的时候类似于使用反射去调用Java的方法,基本分为如下几步。1、获取jclass对象(类似于Java中的Class对象)
//方法一,通过jobject对象直接获取 jclass jclass1 = env->GetObjectClass(jobject); //方法二,通过类描述符获取 //匿名类使用$号隔开,比如Build中定义的VERSION->android/os/Build$VERSION env->FindClass(类描述符); //这里不是Ljava/lang/String;的形式,只有在参数列表中需要这么写 jclass cls = env->FindClass(java/lang/String);2、获取jmethodID或者jfieldID(类似于Java中的Method或者Field)
//获取非静态方法 //参数const char *name 方法名 //参数const char *sig 方法签名 jmethodID env->GetMethodID(jclass clazz, const char *name, const char *sig) //获取静态方法 jmethodID env->GetStaticMethodID(jclass clazz, const char *name, const char *sig) //获取非静态的成员变量 jfieldID env->GetFieldID(jclass clazz, const char *name, const char *sig) //获取静态的成员变量 jfieldID env->GetStaticFieldID(jclass clazz, const char *name, const char *sig) //获取指定类型的成员变量,如下面一个 jboolean env->Get[Type]Field(jobject obj, jfieldID fieldID) jboolean env->GetBooleanField(jobject obj, jfieldID fieldID) //获取指定类型的静态成员变量,如下面一个 jboolean env->GetStatic[Type]Field(jclass clazz, jfieldID fieldID) jboolean env->GetStaticBooleanField(jclass clazz, jfieldID fieldID)3、调用相应函数以及获取/修改属性值c
//调用Java方法 env->Call[Type]Method(...) jobject env->CallBooleanMethod(jobject obj, jmethodID methodID, ...) // 调用Java静态方法 env->CallStatic[Type]Method(...) jboolean env->CallStaticBooleanMethod(jclass clazz, jmethodID methodID, ...) //获取Java属性值 env->Get[Type]Field() jint env->GetIntField(jobject obj, jfieldID fieldID) //获取Java静态属性值 env->GetStatic[Type]Field() jboolean env->GetStaticBooleanField(jclass clazz, jfieldID fieldID) //设置Java属性值 env->Set[Type]Field() void env->SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) //设置Java静态属性值 env->SetStatic[Type]Field() void env->SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)native调用举例
由于本篇博客篇幅已经较长,所以这里只给出部分关键代码。下面为Java层代码。
package org.ndk.ndkfirst; import android.util.Log; public class NDKTest { private static final String TAG = NDKTest; static { System.loadLibrary(ndk-test-lib); } private String mString = Ndk test string; /** * 在native中调用java方法 */ public native void callJavaMetood(); /** * 在native中修改对象的字段 */ public native void modifyFiled(); /** * 输出成员变量的值 */ public void outputString() { Log.i(TAG, mString: + mString); } /** * 在native中调用此方法 */ @Override public String toString() { Log.i(TAG, toString: ); return mString; } }对应的native层实现。
#include <jni.h> extern C { JNIEXPORT void JNICALL Java_org_ndk_ndkfirst_NDKTest_callJavaMetood(JNIEnv *env, jobject obj) { jclass jclass1 = env->GetObjectClass(obj); jmethodID methodID = env->GetMethodID(jclass1, toString, ()Ljava/lang/String;); env->CallObjectMethod(obj, methodID); } JNIEXPORT void JNICALL Java_org_ndk_ndkfirst_NDKTest_modifyFiled(JNIEnv *env, jobject obj) { jclass jclass1 = env->GetObjectClass(obj); jfieldID fieldID1 = env->GetFieldID(jclass1,mString,Ljava/lang/String;); env->SetObjectField(obj,fieldID1,env->NewStringUTF(native modify it)); } }Demo地址:[github](https://github.com/CB2Git/BlogDemoRepository/tree/master/NDKFirst