Android NDK开发(第三天)

2018-07-22  本文已影响0人  arkliu

JNIEnv类型

JNIEnv类型实际上代表了java环境,通过JNIEnv* 指针就可以对java端的代码进行操作,例如,创建java类中的对象,调用java对象的方法,获取java对象中的属性等,常用的类型如下:

#创建java类中的对象
jobject NewObject(jclass clazz, jmethodID methodID, ...)

#创建java类中的string对象
jstring NewString(const jchar* unicodeChars, jsize len)

#创建特定类型的数组
jobjectArray NewObjectArray(jsize length, jclass elementClass,
        jobject initialElement)

#获取类型为type的字段
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)

更多的函数,可以查看jni.h中的函数名称

jclass类型

为了能够在C/C++中使用java类,jni.h头文件中专门定义了jclass类型来表示java中的class类

JNIEnv类中有如下几个简单的函数可以取得jclass:

通过对象实例获取jclass,类似于java中的getClass方法

native中访问java层方法

在native层访问java端的代码,最常见的就是获取类的属性,和调用类的方法。为了在c/c++中表示属性和方法,JNI在jni.h中定义了jfieldId,jmethodId类型来分别表示java端的属性和方法。

使用下面方法获取jfield和jmethod

jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)

参数说明:
# clazz: 这个方法依赖的类对象的class对象
# name 这个字段的名称
# sig这个字段的签名

可以通过javap查看勒种的字段和方法的签名如:
 

查看类中字段和方法的签名:

public class DataProvider {

    /**
     * 把java中的int传递给c语言,c语言处理完毕后,把结果相加返回给java
     * @param x
     * @param y
     * @return
     */
    public static native int add(int x, int y);

    /**
     * 把java中的string传递给c语言,c语言获取到java的string之后,在string后面添加一个hello字符串
     * @param s
     * @return
     */
    public static native String sayHelloInC(String s);

    static {
        System.loadLibrary("test-lib");
    }
    
}

使用javap -s -p ./app/build/intermediates/classes/debug/com/jni/test/DataProvider.class, 命令执行结果如下:


image.png

下面看个栗子:

新建Hello.java

import android.util.Log;

import java.util.Date;

/**
 * Created by liuhang on 18-7-20.
 */

public class Hello {

    public int property;
    public int function(int foo, Date date, int[]arr) {
        Log.e("liuhang", "function runs .....");
        return 0;
    }

    public native void test();

    static {
        System.loadLibrary("test-lib");
    }

}

Hello.java中属性和方法签名如下:

Compiled from "Hello.java"
public class com.jni.test.Hello {
  public int property;
    Signature: I
  public com.jni.test.Hello();
    Signature: ()V

  public int function(int, java.util.Date, int[]);
    Signature: (ILjava/util/Date;[I)I

  public native void test();
    Signature: ()V
}

首先使用javah生成hello.java对应的头文件

javah com.jni.test.Hello

编写hello.cpp文件,在native test方法中,调用java中的function方法

#include <jni.h>
#include <string>
#include <android/log.h>
#define  LOG_TAG    "liuhang"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C"

/*
 * Class:     com_jni_test_Hello
 * Method:    test
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_Hello_test
  (JNIEnv * env, jobject obj) {
  jclass hello_clazz = env->GetObjectClass(obj);
  jfieldID fieldId_prop = env->GetFieldID(hello_clazz, "property", "I");

  jmethodID methodId = env->GetMethodID(hello_clazz, "function", "(ILjava/util/Date;[I)I");

  env->CallIntMethod(obj, methodId, 0L, NULL, NULL);
}




此时执行test方法,就会在native中调用java中的function方法

new Hello().test();

结果如下:


image.png

native中更改java类属性值

创建ChangeProp.java

package com.jni.test;

/**
 * Created by liuhang on 18-7-20.
 */

public class ChangeProp {

    public String name = "zhangsan";
    public static int number = 9;

    public native void changePropFun();

    static {
        System.loadLibrary("test-lib");
    }
}

对应的签名如下:

public class com.jni.test.ChangeProp {
  public java.lang.String name;
    Signature: Ljava/lang/String;
  public static int number;
    Signature: I
  public com.jni.test.ChangeProp();
    Signature: ()V

  public native void changePropFun();
    Signature: ()V

  static {};
    Signature: ()V
}

使用javah生成头文件

image.png

创建changeprop.cpp

#include <jni.h>
#include <string>
#include <android/log.h>
#include <malloc.h>
#define  LOG_TAG    "liuhang"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C"


char* Jstring2CStr(JNIEnv*   env,   jstring   jstr)
{
     char*   rtn   =   NULL;
     jclass   clsstring   =   (env)->FindClass("java/lang/String");
     jstring   strencode   =   (env)->NewStringUTF("GB2312");
     jmethodID   mid   =   (env)->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
     jbyteArray   barr=   (jbyteArray)(env)->CallObjectMethod(jstr,mid,strencode); // String .getByte("GB2312");
     jsize   alen   =   (env)->GetArrayLength(barr);
     jbyte*   ba   =   (env)->GetByteArrayElements(barr,JNI_FALSE);
     if(alen   >   0)
     {
      rtn   =   (char*)malloc(alen+1);         //"\0"
      memcpy(rtn,ba,alen);
      rtn[alen]=0;
     }
     (env)->ReleaseByteArrayElements(barr,ba,0);  //
     return rtn;
}


extern "C"
/*
 * Class:     com_jni_test_ChangeProp
 * Method:    changePropFun
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_ChangeProp_changePropFun
  (JNIEnv *env, jobject jobject) {
    jclass jclazz = env->GetObjectClass(jobject);

    // 分别获取两个属性的id,,Ljava/lang/String; 这里的签名一定要由分号
    jfieldID jfield_name = env->GetFieldID(jclazz, "name", "Ljava/lang/String;");
    jfieldID jfiled_number = env->GetStaticFieldID(jclazz, "number", "I");
    // 获取属性的值,需要注意string是object类型
    jstring before_name = (jstring)env->GetObjectField(jobject, jfield_name);
    jint before_number = env->GetStaticIntField(jclazz, jfiled_number);
    // 打印修改前的属性值
    LOGI("modify before name is %s ,, number is :%d", Jstring2CStr(env, before_name), before_number);

    // 设置新的属性值
    env->SetObjectField(jobject, jfield_name, env->NewStringUTF("lisi"));
    env->SetStaticIntField(jclazz, jfiled_number, 44);

}



java代码调用

ChangeProp prop = new ChangeProp();
Log.e("liuhang", "in activity modify before name is :"+prop.name+" number is :"+ChangeProp.number);
        prop.changePropFun();
Log.e("liuhang", "in activity modify after name is :"+prop.name+" number is :"+ChangeProp.number);

此时打印如下:


image.png
JNIEnv中,提供了众多的 Call<Type>Method和 CallStatic<Type>method,
还有CallNonvirtual<Type>Method函数,需要通过GetMethodID获取相
对应的方法的jmethodID来传入到上述函数参数中

调用示例方法的三种形式如下:

Call<Type>Method(jobject obj, jmethodID id, ....);
Call<Type>Method(jobject obj, jmethodID id, va_list lst);
Call<Type>Method(jobject obj, jmethodID id, jvalue * v);

java和c++中的多态机制

C++和java对于继承后执行的是父类还是子类的方法是由区别的,在java中,所有的方法都是虚拟的,所以总是调用子类的方法,因此CallNonVirtual<Type>Method方法就出来了,这个方法可以帮助调用java中父亲的方法。看下面demo

创建ExtendsDemo.java

package com.jni.test;

/**
 * Created by liuhang on 18-7-21.
 */

public class ExtendsDemo {

    public Father father = new Child();

    public native void testExtends();

    static {
        System.loadLibrary("test-lib");
    }

}

对应的方法签名如下:

public class com.jni.test.ExtendsDemo {
  public com.jni.test.Father father;
    Signature: Lcom/jni/test/Father;
  public com.jni.test.ExtendsDemo();
    Signature: ()V

  public native void testExtends();
    Signature: ()V

  static {};
    Signature: ()V
}

创建父类Father.java和子类Child.java

package com.jni.test;

import android.util.Log;

/**
 * Created by liuhang on 18-7-21.
 */

public class Father {

    public void function() {
        Log.e("liuhang", "father function runs.....");
    }
}



package com.jni.test;

import android.util.Log;

/**
 * Created by liuhang on 18-7-21.
 */

public class Child extends Father {

    @Override
    public void function() {
        Log.e("liuhang", "child function runs....");
    }
}


为ExtendsDemo生成头文件

liuhang@android:~/code/MyApplication/app/src/main/java$ javah com.jni.test.ExtendsDemo

创建Extends.cpp

#include <jni.h>
#include <string>
#include <android/log.h>
#define  LOG_TAG    "liuhang"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C"
/*
 * Class:     com_jni_test_ExtendsDemo
 * Method:    testExtends
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_ExtendsDemo_testExtends
  (JNIEnv * env, jobject obj) {

    LOGI("liuhang jni testExtends runs...");
    // 获取ExtendsEemo类对应的class对象
    jclass clazz = env->GetObjectClass(obj);
    // 获取father属性对应的filedid
    jfieldID id_father = env->GetFieldID(clazz, "father", "Lcom/jni/test/Father;");
    // 获取father属性对象
    jobject father = env->GetObjectField(obj, id_father);


    // 获取father类
    jclass jfather_clazz = env->FindClass("com/jni/test/Father");
    // 获取Father类中的function方法
    jmethodID jfatherFun_id = env->GetMethodID(jfather_clazz, "function", "()V");
    // 调用子类的方法
    env->CallVoidMethod(father, jfatherFun_id);
    // 调用父类的方法
    env->CallNonvirtualVoidMethod(father, jfather_clazz, jfatherFun_id);

}


java中调用native方法

new ExtendsDemo().testExtends();

此时执行结果如下:
[图片上传失败...(image-23a951-1532242821008)]

native中创建java对象

在JNIEnv中有两种方法创建java对象,下面逐个介绍

通过NewObject创建java对象

jobject NewObject(jclass clazz, jmethodID methodID, ...)

#clazz是需要构建的java对象的class类对象
#methodID是传递一个方法ID,就是构造方法

#因为构造方法没有返回值,所以认为类的默认构造方法的返回值类型的签名始终是"()V",(因为默认的构造方法是没有参数的),方法的名称始终为"<init>"

在native中创建Date对象,打印getTime返回值

创建ConstructJavaObj.java
package com.jni.test;


/**
 * Created by liuhang on 18-7-21.
 */

public class ConstructJavaObj {

    public native void firstConstruct();
    public native void secondConstruct();

    static {
        System.loadLibrary("test-lib");
    }

}

使用javah生成头文件
liuhang@android:~/code/MyApplication/app/src/main/java$ javah com.jni.test.ConstructJavaObj
创建constructobj.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define  LOG_TAG    "liuhang"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C"

/*
 * Class:     com_jni_test_ConstructJavaObj
 * Method:    firstConstruct
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_ConstructJavaObj_firstConstruct
  (JNIEnv *env, jobject obj) {

    jclass date_clazz = env->FindClass("java/util/Date");

    jmethodID date_construct = env->GetMethodID(date_clazz, "<init>", "()V");

    jobject date_obj = env->NewObject(date_clazz, date_construct);

    jmethodID method_id = env->GetMethodID(date_clazz, "getTime", "()J");

    jlong time_current = env->CallLongMethod(date_obj, method_id);

    LOGI("current time is :%lld",time_current);
}
java中调用native方法
new ConstructJavaObj().firstConstruct();
Log.e("liuhang", "mainactivity tine is :"+new Date().getTime());
image.png

<font color="red">最后别忘了,在CMakeLists.txt中添加对应的cpp文件引用</font>

#生成so动态库
ADD_LIBRARY(test-lib SHARED test-lib.cpp hello.cpp changeprop.cpp extends.cpp constructobj.cpp)

target_link_libraries(test-lib log)

通过AllocObject(jclass clazz)创建java对象

用AllocObject函数创建一个java对象,可以根据传入的jclass创建一个java对象,但是状态是非初始化的,在这个对象使用之前,需要用CallNonvirtualVoidMethod来调用该jclass的构造函数,这样做可以延迟构造函数的调用。还是实现上面的功能,打印date.getTime()返回值

实现secondConstruct本地方法

/*
 * Class:     com_jni_test_ConstructJavaObj
 * Method:    secondConstruct
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_ConstructJavaObj_secondConstruct
  (JNIEnv *env, jobject obj) {

  jclass date_clazz = env->FindClass("java/util/Date");
  jmethodID date_construct = env->GetMethodID(date_clazz, "<init>", "()V");

  jobject date_obj = env->AllocObject(date_clazz);

  env->CallNonvirtualVoidMethod(date_obj, date_clazz, date_construct);

  jmethodID method_id = env->GetMethodID(date_clazz, "getTime", "()J");

  jlong time_current = env->CallLongMethod(date_obj, method_id);

  LOGI("int secondConstruct current time is :%lld",time_current);

}

image.png

native中操作java字符串

# 获取字符串长度
jsize GetStringLength(jstring string)

# 将jstring对象拷贝到const jchar* 指针字符串
void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf)

#生成一个jstring对象
jstring NewString(const jchar* unicodeChars, jsize len)

# 将string对象转换成const jcahr* 字符串指针
const jchar* GetStringChars(jstring string, jboolean* isCopy)

在java中定义一个字符串属性,在C++中将该字符串进行倒序,在从Java中输出

package com.jni.test;

/**
 * Created by liuhang on 18-7-21.
 */

public class StringJNI {

    public String name = "zhangsan";

    public native void convertStr();

    static {
        System.loadLibrary("test-lib");
    }

}

编写convertstr.cpp类

#include <jni.h>
#include <string>
#include <android/log.h>
#include <algorithm>
#define  LOG_TAG    "liuhang"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
using namespace std;

extern "C"

/*
 * Class:     com_jni_test_StringJNI
 * Method:    convertStr
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_StringJNI_convertStr
  (JNIEnv * env, jobject jobject) {
    jclass jclazz = env->GetObjectClass(jobject);

    jfieldID j_name_id = env->GetFieldID(jclazz, "name", "Ljava/lang/String;");
    jstring j_namestr = (jstring)env->GetObjectField(jobject, j_name_id);

    const jchar * jstr = env->GetStringChars(j_namestr, NULL);
    wstring wstr((const wchar_t*)jstr);

    //释放指针
    env->ReleaseStringChars(j_namestr, jstr);

    reverse(wstr.begin(), wstr.end());

    jstring j_newstr = env->NewString((const jchar*)wstr.c_str() , (jint)wstr.size());
    env->SetObjectField(jobject, j_name_id, j_newstr);

}


native操作java中的数组

操作基本类型数组

Get<Type>ArrayElements方法

上述函数可以吧java基本类型的数组转换到c/c++中的数组,有两种处理方式:一种是拷贝一份传回本地代码,另一种是把指向java数组的指针直接传回到本地代码中,处理完本地化的数组后,通过Release<Type>ArrayElements来释放数组。

Release<Type>ArrayElements

用上述方法可以选择将如何处理java和c++的数组,是提交还是撤销等,内存释放还是不释放等。

void* GetPrimitiveArrayCritical(jarray array, jboolean* isCopy)
void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode)
get<Type>ArrayRegion
#在c/c++中预先开辟一段内存,然后把java基本类型的数组拷贝到这段内存中

set<Type>ArrayRegion
#把java基本类型数组中的指定范围的元素用c/c++数组中的元素来赋值

<Type>ArrayNew方法
指定一个长度然后返回相应的java基本类型的数组

操作对象类型数组

JNI没有提供把java对象类型数组(Object[])直接转换到c++中的Object[]数组的函数,而是通过Get/SetObjectArrayElement这样的函数来对java的Object[]数组进行操作,由于对象数组没有进行拷贝,所以不需要释放任何资源,NewObjectArray可以通过制定长度和初始值来创建某个类的数组

创建ArrayTest.java

package com.jni.test;

/**
 * Created by liuhang on 18-7-21.
 */

public class ArrayTest {

    int []arrays = {3, 4, 13, 56, 34, 22,88,21};
    Father[] fathers = {new Father(), new Father(), new Father()};

    public native void testArray();

    static{
        System.loadLibrary("test-lib");
    }
}

签名如下:

public class com.jni.test.ArrayTest {
  int[] arrays;
    Signature: [I
  com.jni.test.Father[] fathers;
    Signature: [Lcom/jni/test/Father;
  public com.jni.test.ArrayTest();
    Signature: ()V

  public native void testArray();
    Signature: ()V

  static {};
    Signature: ()V
}

创建arraytest.cpp

#include <jni.h>
#include <string>
#include <android/log.h>
#define  LOG_TAG    "liuhang"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

extern "C"

/*
 * Class:     com_jni_test_ArrayTest
 * Method:    testArray
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_test_ArrayTest_testArray
  (JNIEnv * env, jobject jobject) {

    jclass jclazz = env->GetObjectClass(jobject);

    jfieldID arrayid = env->GetFieldID(jclazz, "arrays", "[I");

    jintArray arrayobj = (jintArray)env->GetObjectField(jobject, arrayid);

    jint* int_array = env->GetIntArrayElements(arrayobj, NULL);

    jsize length = env->GetArrayLength(arrayobj);

    LOGI("int array length is :%d", length);
    for(int i = 0; i < length; i++) {
        LOGI("int array %d place is :%d",i, int_array[i]);
    }
}

此时执行程序,运行结果如下:


image.png

<font color="red">将上述数组中下表偶数位,保存到新的数组里</font>

// 将上述数组中下表偶数位,保存到新的数组里
    jintArray jsave_array = env->NewIntArray(length);
    jint*  save_array = env->GetIntArrayElements(jsave_array, NULL);
    jint count = 0;
    for(int i = 0; i < length; i++) {
        if (i % 2 == 0) {
            save_array[count++] = int_array[i];
        }
    }

    //打印新的数组内容
    for(int j = 0; j < count; j++) {
        LOGI("save_array %d place is :%d",j, save_array[j]);
    }
image.png

<font color="red">将上述数组中0到3位,拷贝到数组,并且打印出来</font>

// 将上述数组中0到3位,拷贝到数组,并且打印出来
    jint * jmemory_array = new jint[length];
    env->GetIntArrayRegion(arrayobj, 0, 3, jmemory_array);

    for(int j = 0; j < count; j++) {
        LOGI("jmemory_array %d place is :%d",j, save_array[j]);
    }
image.png

<font color='red'>把上述数组中的3-7位,赋新的值</font>

 // 把上述数组中的3-7位,赋新的值
    jint * replace_array = new jint[4];
    for (int k = 0; k < 4; k++) {
        replace_array[k] = k+1;
    }
    env->SetIntArrayRegion(arrayobj, 3, 4, replace_array);
    // 重新获取数组指针
    int_array = env->GetIntArrayElements(arrayobj, NULL);
    for(int i = 0; i < length; i++) {
       LOGI("int array after replace ... %d place is :%d",i, int_array[i]);
    }
image.png

<font color='red'>调用sort函数对上述数组进行排序</font>

#include <algorithm>
using namespace std;

std :: sort(int_array, int_array+length);
for(int i = 0; i < length; i++) {
    LOGI("int array after sort ... %d place is :%d",i, int_array[i]);
}
image.png

JNIEnv操作引用类型的数组

<font color='red'>获取上述fathers数组中的第一个Father类对象,并且执行其function方法</font>

// 获取fathers属性字段id
    jfieldID j_father_id = env->GetFieldID(jclazz, "fathers", "[Lcom/jni/test/Father;");
    // 获取fathers属性
    jobjectArray  j_father_obj = (jobjectArray)env->GetObjectField(jobject, j_father_id);
    // 获取Father类class
    jclass j_father_clazz = env->GetObjectClass(env->GetObjectArrayElement(j_father_obj, 1));
    // 根据Father类class 获取function方法id
    jmethodID j_father_method = env->GetMethodID(j_father_clazz, "function", "()V");
    // 执行function方法
    env->CallVoidMethod(env->GetObjectArrayElement(j_father_obj, 1), j_father_method);

此时打印如下:

07-21 00:51:36.828  3710  3710 E liuhang : father function runs..... name is :lisi

<font color='red'>创建一个大小为5的Father类型数组,并且值为fathers数组中的第三个元素,执行其function方法</font>

 // 创建一个大小为5的Father类型数组,并且值为fathers数组中的第三个元素
     jobjectArray j_new_obj = env->NewObjectArray(5, j_father_clazz, env->GetObjectArrayElement(j_father_obj, 2));
     // 获取j_new_obj数组中的第四个对象,执行其function方法
     env->CallVoidMethod(env->GetObjectArrayElement(j_new_obj, 3), j_father_method);

此时打印如下:

07-21 00:51:36.828  3710  3710 E liuhang : father function runs..... name is :wangwu

c/c++中的引用类型

局部引用

局部引用是最常见的引用类型,基本上通过JNI返回来的引用都是局部引用,例如:使用NewObject就会返回创建出来的实力的局部引用,局部引用只在该native函数中有效,所有在该函数中产生的局部引用,都会在该函数返回的时候,自动释放,也可以使用DeleteLocalRef函数手动释放该引用。

全局引用

全局引用可以跨越当前线程,在多个native函数中有效,不过需要开发人员手动释放该引用,全局引用存在期间会防止在java的垃圾回收器的回收。

与局部引用不同,全局引用的创建不是由JNI自动创建的,全局引用是需要调用NewGlobalRef函数,而释放它需要使用ReleaseGlobalRef函数

弱全局引用

弱全局引用是java 1.2新出来的功能,与全局引用相似,创建和删除都需要开发人员进行,这种引用与全局引用一样,可以在多个本地代码中有效,也跨越多线程有效,不同的是,这种引用将不会阻止垃圾回收器回收这个引用所指向的对象,使用NewWeakGLobalRef和ReleaseWeakGlobalRef来产生和解除引用。

ID缓存

缓存jfieldID/jmethodID,取得jfieldID和jmethodID时候,会通过该属性/方法名称加上签名来查询相应的jfieldID/jmethodID。这种查询相对来说开销大,我们可以将这些jfieldID/jmethodID缓存起来,这样需要查询一次,以后就使用缓存起来的jfieldID/jmethodID

在使用的时候缓存

在native代码中使用static局部变量保存已经查询过的id,这样就不会在每次函数调用时候查询,而只要第一次查询成功后就保存起来。

static jfieldID fieldID_str = NULL;
     jclass clazz = env->GetObjectClass(obj);
     if (fieldID_str == NULL) {
        fieldID_str = env->GetFieldID(clazz, "string", "Ljava/lang/String;");
     }

在java类初始化时缓存

更好的一个方式是在任何native函数调用前把ID全部存起来,可以让java在第一次加载这个类的时候,首先调用本地代码初始化所有的jfieldID/jmethodID

jfieldID g_propInt_id = 0;
jfieldID g_propStr_id = 0;

JNIEXPORT void JNICALL Java_com_jni_test_Hello_test
  (JNIEnv * env, jobject obj) {
    g_propInt_id = env->GetFieldID(hello_clazz, "propertyInt", "I");
        g_propStr_id = env->GetFieldID(hello_clazz, "propertyStr", "Ljava/lang/String;");
}
上一篇 下一篇

猜你喜欢

热点阅读