前言
在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