前言
在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,其他的类似。
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、调用相应函数以及获取/修改属性值
//调用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