在Native code中访问java 对象
接下来我们可以看一下,在native code中,访问非Java Language内置的引用数据类型的方法。
访问非Java Language内置的引用数据类型
访问非Java language内置的引用数据类型,这个topic又可以分为量个小的topic:
- 访问类成员
- 调用类方法
我们会一个接着一个的看一下这些小topic。
访问类成员
Java编程语言支持两种类型的类成员。一个类的每一个实例都会有一份属于他们自己的copy的类的实例成员,以及为一个类所有实例所共享的类的静态成员。JNI有提供一些函数,以便于在native code中获取或者设置对象的实例成员和类的静态成员。首先,我们先来看一段code,这段code 演示了我们在native cod访问类成员的方法:
package com.example.hellojni;public class FieldAccess { private String str = ""; private static int staticInt; public native void accessField(); public void setString(String text) { str = text; } public String getString() { return str; } public native void accessStaticField(); public void setStaticInt(int intValue) { staticInt = intValue; } public int getStaticInt() { return staticInt; }}
FieldAccess 类定义了一个String类型的成员变量str,并定义了访问这个成员变量的方法,还有一个名为accessField()的native方法。FieldAccess也定义了一个int类型的静态成员,同样的,有定义访问这个静态成员的方法和native方法。
在java code中,调用对象的native方法来做操作:
public class HelloJni extends Activity{ private static final String TAG = "hello-jni"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText(stringFromJNI()); setContentView(tv); FieldAccess instanceField = new FieldAccess(); instanceField.setString("abc"); instanceField.accessField(); Log.d(TAG, "In java: " + " instancField.str = \"" + instanceField.getString() + "\""); instanceField.setStaticInt(100); instanceField.accessStaticField(); Log.d(TAG, "In java: " + " FieldAccess.staticInt = " + instanceField.getStaticInt()); }
在onCreate()方法中,创建了一个FieldAccess类的实例,为这个实例设置str成员变量的值,并调用这个实例的native方法instancField.accessField()。不久我们即会看到的,在native方法中,会首先输出实例变量str的当前值,随后会给这个成员设置一个新的值。此处,在native方法执行完成之后,会再次输出str成员变量的值。对于静态成员的操作,大体上也是一样的。Java code中设置静态成员的值->native code中读取设置的值,用log输出,然后修改静态成员的值->Java code中再次用log输出静态成员的值。
下面是FieldAccess的native 方法的实现:
static void FieldAccess_accessField(JNIEnv* env, jobject thiz) { jfieldID fid; /* Store the field ID */ jstring jstr; const char *str; /* Get a reference to obj's class */ jclass cls = env->GetObjectClass(thiz); JniDebug("In native code: "); /* Look for the instance field str in cls */ fid = env->GetFieldID(cls, "str", "Ljava/lang/String;"); if (fid == NULL) { JniDebug("fid is NULL."); return; /* Failed to find the field */ } /* Read the instance field str */ jstr = (jstring)env->GetObjectField(thiz, fid); str = env->GetStringUTFChars(jstr, NULL); if (str == NULL) { JniDebug("fid is NULL."); return; /* Out of memory */ } JniDebug(" instancField.str = \" %s \"", str); env->ReleaseStringUTFChars(jstr, str); /* Create a new string and overwrite the instance field */ jstr = env->NewStringUTF("123"); if (jstr == NULL) { return; /* Out of memory */ } env->SetObjectField(thiz, fid, jstr);}static void FieldAccess_accessStaticField(JNIEnv* env, jobject thiz) { jfieldID fid; /* Store the field ID */ jint si; /* Get a reference to obj's class */ jclass cls = env->GetObjectClass(thiz); JniDebug("In native code: "); /* Look for the instance field str in cls */ fid = env->GetStaticFieldID(cls, "staticInt", "I"); if (fid == NULL) { JniDebug("fid is NULL."); return; /* Failed to find the field */ } /* Access the static field staticInt */ si = env->GetStaticIntField(cls, fid); JniDebug(" FieldAccess.staticInt = %d", si); env->SetStaticIntField(cls, fid, 200);}static JNINativeMethod gFieldAccessMethods[] = { NATIVE_METHOD(FieldAccess, accessField, "()V"), NATIVE_METHOD(FieldAccess, accessStaticField, "()V"),};static JNINativeMethod gMethods[] = { NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"), NATIVE_METHOD(HelloJni, stringToJNI, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(HelloJni, sumIntWithNative, "([III)I"), NATIVE_METHOD(HelloJni, sumDoubleWithNative, "([DII)D"),};void register_com_example_hellojni(JNIEnv* env) { jniRegisterNativeMethods(env, "com/example/hellojni/HelloJni", gMethods, NELEM(gMethods)); jniRegisterNativeMethods(env, "com/example/hellojni/FieldAccess", gFieldAccessMethods, NELEM(gFieldAccessMethods));}
运行上面的那些code,我们将会看到如下的log输出:
04-30 19:51:57.542: D/hello-jni(1791): JNI_OnLoad in hello-jni04-30 19:51:57.702: D/hello-jni(1791): from HelloJni_stringFromJNI04-30 19:51:57.912: D/hello-jni(1791): In native code: 04-30 19:51:57.912: D/hello-jni(1791): instancField.str = " abc "04-30 19:51:57.912: D/hello-jni(1791): In java: instancField.str = "123"04-30 19:51:57.912: D/hello-jni(1791): In native code: 04-30 19:51:57.912: D/hello-jni(1791): FieldAccess.staticInt = 10004-30 19:51:57.912: D/hello-jni(1791): In java: FieldAccess.staticInt = 200
接下来我们会再对上面的那些code,做更为详细的说明。
访问类实例成员
在native code中,需要有两个步骤来访问类实例成员。第一,调用GetFieldID,由类引用、成员名称和field descriptor获取field ID,如:
fid = env->GetFieldID(cls, "str", "Ljava/lang/String;");
上面的那段示例代码通过对实例引用thiz调用GetObjectClass来获取类引用cls,而这个thiz则是作为第二个参数,被传递给native方法的实现的。一旦获取了field ID,我们就可以将其和对象的引用传递给适当的实例成员访问函数,以访问实例成员:
jstr = (jstring)env->GetObjectField(thiz, fid);
由于strings和arrays是特殊种类的对象,我们使用GetObjectField来访问实例成员。除了Get/SetObjectField之外,JNI也提供了诸如GetIntField和SetFloatField这样的一些函数,以访问原始数据类型的实例成员。这些函数大体上有如下的这些:
android frameworks JNI部分,也有一些code值得我们参考。如JellyBean/frameworks/base/core/jni/android/graphics/Paint.cpp这个文件中的如下的code:
struct JMetricsID { jfieldID top; jfieldID ascent; jfieldID descent; jfieldID bottom; jfieldID leading;};static jclass gFontMetrics_class;static JMetricsID gFontMetrics_fieldID;... static jfloat getFontMetrics(JNIEnv* env, jobject paint, jobject metricsObj) { NPE_CHECK_RETURN_ZERO(env, paint); SkPaint::FontMetrics metrics; SkScalar spacing = GraphicsJNI::getNativePaint(env, paint)->getFontMetrics(&metrics); if (metricsObj) { SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class)); env->SetFloatField(metricsObj, gFontMetrics_fieldID.top, SkScalarToFloat(metrics.fTop)); env->SetFloatField(metricsObj, gFontMetrics_fieldID.ascent, SkScalarToFloat(metrics.fAscent)); env->SetFloatField(metricsObj, gFontMetrics_fieldID.descent, SkScalarToFloat(metrics.fDescent)); env->SetFloatField(metricsObj, gFontMetrics_fieldID.bottom, SkScalarToFloat(metrics.fBottom)); env->SetFloatField(metricsObj, gFontMetrics_fieldID.leading, SkScalarToFloat(metrics.fLeading)); } return SkScalarToFloat(spacing); }...int register_android_graphics_Paint(JNIEnv* env) { gFontMetrics_class = env->FindClass("android/graphics/Paint$FontMetrics"); SkASSERT(gFontMetrics_class); gFontMetrics_class = (jclass)env->NewGlobalRef(gFontMetrics_class); gFontMetrics_fieldID.top = req_fieldID(env->GetFieldID(gFontMetrics_class, "top", "F")); gFontMetrics_fieldID.ascent = req_fieldID(env->GetFieldID(gFontMetrics_class, "ascent", "F")); gFontMetrics_fieldID.descent = req_fieldID(env->GetFieldID(gFontMetrics_class, "descent", "F")); gFontMetrics_fieldID.bottom = req_fieldID(env->GetFieldID(gFontMetrics_class, "bottom", "F")); gFontMetrics_fieldID.leading = req_fieldID(env->GetFieldID(gFontMetrics_class, "leading", "F")); gFontMetricsInt_class = env->FindClass("android/graphics/Paint$FontMetricsInt"); SkASSERT(gFontMetricsInt_class); gFontMetricsInt_class = (jclass)env->NewGlobalRef(gFontMetricsInt_class); gFontMetricsInt_fieldID.top = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "top", "I")); gFontMetricsInt_fieldID.ascent = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "ascent", "I")); gFontMetricsInt_fieldID.descent = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "descent", "I")); gFontMetricsInt_fieldID.bottom = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "bottom", "I")); gFontMetricsInt_fieldID.leading = req_fieldID(env->GetFieldID(gFontMetricsInt_class, "leading", "I")); int result = AndroidRuntime::registerNativeMethods(env, "android/graphics/Paint", methods, sizeof(methods) / sizeof(methods[0])); return result;}可以看到在android frameworks jni下的这个部分的code,对于android.graphics.Paint.FontMetrics的访问。它是在注册native方法是,先获取到类的各个成员的field ID并保存起来。在native方法的实现中,直接借助于前面保存的field ID,来完成对于对象成员的修改。另外值得我们注意的是,在此处,没有通过GetObjectClass来获取类的引用,而是通过对class descriptor调用FindClass,来获取到类的引用。
访问类静态成员
访问类静态成员与访问类实例成员很相似。由上面访问类static 成员的native code,我们可以看出来,有如下的两点,是访问类静态成员与访问类实例成员所不一样的地方:
- 访问static成员时,我们是通过调用GetStaticFieldID来获取到数据成员field ID的,而不是像访问实例成员时调用GetFieldID那样。而这两个函数的返回值类型则都是一样的,均为jfieldID。
- 获取到field ID之后,我们是传递类引用,而不是一个对象的引用给适当的JNI函数来访问static 成员的。
同样的,我们在此处也对JNI提供的用于访问static 数据成员的函数做一个总结:
最后我们再整体的对于访问类的数据成员的方法做一个总结。访问类的数据成员,大体上需要3个步骤:
- 获取到对于class对象的引用,可以通过FindClass,或者GetObjectClass等
- 通过GetFieldID获取到特定数据成员的field ID
- 通过JNI提供的适当的方法,来读取或者修改类的数据成员,如Get/SetIntField
对类的数据成员进行访问的方法,大体上就如上所述。
调用类方法
Java语言中很许多中的类方法。实例方法(Instance method)只能依托于一个特定的对象来调用,而对于static 方法(static methods)的调用则则独立于类的任何一个具体实例。我们将会在后面再来讨论构造函数。
JNI支持一个完整的系列的函数,以便于我们在native code中执行回调。下面的例子包含两个native方法,他们会分别在native code实现中调用Java语言编写的一个实例方法和一个类静态方法。
package com.example.hellojni;import android.util.Log;public class MethodCall { private static final String TAG = "hello-jni"; public native void nativeMethod(); private void callback() { Log.d(TAG, "In java"); } public native void nativeStaticMethod(); private static void callbackStatic() { Log.d(TAG, "In java staic"); }}下面的code是在java code中调用native 方法的部分:
public class HelloJni extends Activity{ private static final String TAG = "hello-jni"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText(stringFromJNI()); setContentView(tv); MethodCall methodCall = new MethodCall(); methodCall.nativeMethod(); methodCall.nativeStaticMethod(); }
下面的code,是前面的code中所声明的native方法的实现:
static void MethodCall_nativeMethod(JNIEnv* env, jobject thiz) { jclass cls = env->GetObjectClass(thiz); jmethodID mid = env->GetMethodID(cls, "callback", "()V"); if (mid == NULL) { JniDebug("mid is NULL, method not found."); return; } JniDebug("In native code"); env->CallVoidMethod(thiz, mid);}static void MethodCall_nativeStaticMethod(JNIEnv* env, jobject thiz) { jclass cls = env->GetObjectClass(thiz); jmethodID mid = env->GetStaticMethodID(cls, "callbackStatic", "()V"); if (mid == NULL) { JniDebug("mid is NULL, method not found."); return; } JniDebug("In native code static"); env->CallStaticVoidMethod(cls, mid);}static JNINativeMethod gMethodCallMethods[] = { NATIVE_METHOD(MethodCall, nativeMethod, "()V"), NATIVE_METHOD(MethodCall, nativeStaticMethod, "()V"),};
如下则为上面的那些code的log输出:
05-01 03:16:33.491: D/hello-jni(1774): JNI_OnLoad in hello-jni05-01 03:16:33.621: D/hello-jni(1774): from HelloJni_stringFromJNI05-01 03:16:33.901: D/hello-jni(1774): In native code05-01 03:16:33.931: D/hello-jni(1774): In java05-01 03:16:33.931: D/hello-jni(1774): In native code static05-01 03:16:33.931: D/hello-jni(1774): In java staic
可以看到,有按照我们的预期,在native code中正确地完成了对于java 语言实现的方法的调用。
调用实例方法
MethodCall_nativeMethod的实现,阐明了在native code中调用实例方法所需要的两个步骤:
- native方法首先需要调用JNI函数GetMethodID。GetMethodID会在给定类中查找特定的方法。可以看到,查找是基于方法名和方法的类型描述符的。如果方法不存在,GetMethodID返回NULL。此处,由native code直接返回会导致在调用MethodCall.nativeMethod()的地方有一个NoSuchMethodError 被抛出。
- 然后,native code调用CallVoidMethod。CallVoidMethod调用一个实例方法,该方法的返回类型为void。我们需要传递对象,method ID和实际的参数(尽管上面的code中,没有参数)给CallVoidMethod。
除了CallVoidMethod,JNI也支持调用具有其他返回值类型的方法。比如,如果我们需要调用一个返回值类型为int的callback,我们可以在native code中使用CallIntMethod。类似地,我们可以使用CallObjectMethod来调用返回对象的方法。
我们也可以使用Call<Type>Method族函数来调用interface方法。只是一定要有interface类型继承method ID。
调用GetMethodID所传递的第三个参数,方法的类型描述符,与我们前面在注册native方法时所遇到的method descriptor的写法是一样的。
可以看一下这族函数:
JNI还提供了其他的一些函数,以方便我们在native code中回调Java方法。更具体的内容可以参考《The Java ™ Native Interface -- Programmer’s Guide and Specification》。
调用类静态方法
与在native code中调用实例方法相似,在native code中调用类静态方法也需要两个步骤:
- 使用GetStaticMethodID,而不是GetMethodID来获取method ID。
- 传递class,method ID和参数给static 方法调用族函数中的一个:CallStaticVoidMethod,CallStaticBooleanMethod等。
允许我们调用实例方法的函数族和允许我们调用类static 方法的函数族之间,最为关键的区别在于,前者都是需要将一个对象的引用作为第二个参数的,而后者则需要将一个class引用作为第二个参数。
在Java语言层级,我们有两种方式可以调用一个类Cls的static方法f():通过Cls.f()或者通过obj.f(),其中obj是Cls的一个实例。(然而,后者是推荐的编程风格)。在JNI中,从native code调用static方法,我们则总是需要指明class引用。如前面的那段code所示的那样。
在native code中调用static方法的那一族函数,与在native code中调用实例方法的那一族相比,差别不是很大,我们此处就不再列出,具体可以参考《The Java ™ Native Interface -- Programmer’s Guide and Specification》。
Done.