博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在Native code中访问java 对象(II)
阅读量:5965 次
发布时间:2019-06-19

本文共 13263 字,大约阅读时间需要 44 分钟。

在Native code中访问java 对象

接下来我们可以看一下,在native code中,访问非Java Language内置的引用数据类型的方法。

访问非Java Language内置的引用数据类型

访问非Java language内置的引用数据类型,这个topic又可以分为量个小的topic:

  1. 访问类成员
  2. 调用类方法

我们会一个接着一个的看一下这些小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,我们可以看出来,有如下的两点,是访问类静态成员与访问类实例成员所不一样的地方:

  1. 访问static成员时,我们是通过调用GetStaticFieldID来获取到数据成员field ID的,而不是像访问实例成员时调用GetFieldID那样。而这两个函数的返回值类型则都是一样的,均为jfieldID。
  2. 获取到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中调用实例方法所需要的两个步骤:

  1. native方法首先需要调用JNI函数GetMethodID。GetMethodID会在给定类中查找特定的方法。可以看到,查找是基于方法名和方法的类型描述符的。如果方法不存在,GetMethodID返回NULL。此处,由native code直接返回会导致在调用MethodCall.nativeMethod()的地方有一个NoSuchMethodError 被抛出。
  2. 然后,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中调用类静态方法也需要两个步骤:

  1. 使用GetStaticMethodID,而不是GetMethodID来获取method ID。
  2. 传递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.

转载于:https://my.oschina.net/wolfcs/blog/126660

你可能感兴趣的文章
python 文件及文件夹操作
查看>>
Android自定义ListView的Item无法响应OnItemClick的解决办法
查看>>
Building Apps for Windows Phone 8.1教程下载地址整理
查看>>
移动Web—CSS为Retina屏幕替换更高质量的图片
查看>>
[Linux 性能检测工具]SAR
查看>>
JS 运行、复制、另存为 代码。
查看>>
一个经典编程面试题的“隐退”
查看>>
阿里公共DNS 正式发布了
查看>>
Java抓取网页数据(原网页+Javascript返回数据)
查看>>
EasyUI基础入门之Pagination(分页)
查看>>
ORACLE中CONSTRAINT的四对属性
查看>>
python 迭代器 生成器
查看>>
dorado基本事件样例
查看>>
Unity寻路的功能总结
查看>>
Python访问PostGIS(建表、空间索引、分区表)
查看>>
quick-cocos2d-x开发环境Lua for IntelliJ IDEA的安装
查看>>
Target-Action回调模式
查看>>
换个红圈1微信头像恶搞一下好友
查看>>
Socket网络编程--简单Web服务器(3)
查看>>
ylbtech_dbs_article_五大主流数据库模型
查看>>