接下来我们可以看一下,在native code中,访问非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-jni 04-30 19:51:57.702: D/hello-jni(1791): from HelloJni_stringFromJNI 04-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 = 100 04-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,我们可以看出来,有如下的两点,是访问类静态成员与访问类实例成员所不一样的地方:
同样的,我们在此处也对JNI提供的用于访问static 数据成员的函数做一个总结:
最后我们再整体的对于访问类的数据成员的方法做一个总结。访问类的数据成员,大体上需要3个步骤:
对类的数据成员进行访问的方法,大体上就如上所述。
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-jni 05-01 03:16:33.621: D/hello-jni(1774): from HelloJni_stringFromJNI 05-01 03:16:33.901: D/hello-jni(1774): In native code 05-01 03:16:33.931: D/hello-jni(1774): In java 05-01 03:16:33.931: D/hello-jni(1774): In native code static 05-01 03:16:33.931: D/hello-jni(1774): In java staic
可以看到,有按照我们的预期,在native code中正确地完成了对于java 语言实现的方法的调用。
MethodCall_nativeMethod的实现,阐明了在native code中调用实例方法所需要的两个步骤:
除了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中调用类静态方法也需要两个步骤:
允许我们调用实例方法的函数族和允许我们调用类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.