应用JNI实现数据的访问操作

69 阅读3分钟

在android中,可以通过JNI实现对应用层JAVA类中对象属性的修改操作。
1,在应用层JAVA类中定义本地函数和属性数据

public native void change_name();   
public static native void changeAge();  
private String name = "Jack";   
private static int age = 99;

2,在定义的本地函数声明中通过ALT键回车即可进入本地cpp文件对声明的本地函数进行实现。

extern "C"
JNIEXPORT void JNICALL
Java_com_example_ericnative_MainActivity_change_1name(JNIEnv *env, jobject thiz) {
    jclass jclass1 = env->GetObjectClass(thiz);
    jfieldID jfieldId = env->GetFieldID(jclass1, "name", "Ljava/lang/String;");
    jstring value = env->NewStringUTF("Jacky");
    env->SetObjectField(thiz, jfieldId, value);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_ericnative_MainActivity_changeAge(JNIEnv *env, jclass clazz) {
      jfieldID jfieldId1 = env->GetStaticFieldID(clazz, "age", "I");
      env->SetStaticIntField(clazz, jfieldId1, 101);
}

3,从上面的代码可以看出: 1)如果在声明本地函数时函数名带有下划线,则在本地函数的实现函数中会在定义的函数名称中的下划线后跟数字1,用以表示是函数名中的下划线。2)如果声明的本地方法是静态static修饰的函数,则在本地函数的实现函数中,第二个参数为jclass类型。如果不是静态static修饰的,则第二个参数为jobject类型。3)对于JAVA代码中的String类型,在GetFieldID时传入的签名是"Ljava/lang/String;",在设置值时调用的是SetObjectField函数。对于int型数据,传入的签名是"I"。4)对于静态static int型数据的Get和Set,要调用相应的GetStaticFieldID和SetStaticIntField函数。
4,对于JNIEnv的分析,在jni.h中

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
    ...
}

struct JNINativeInterface {
    ......
    jclass      (*FindClass)(JNIEnv*, const char*);
    ......

从以上代码可以看出,JNIEnv *env是一个指向JNIEnv结构的指针,在C++中,JNIEnv *env是一级指针,指向的是struct _JNIEnv,在C中,JNIEnv env是二级指针,指向的是JNINativeInterface,所以对于调用struct JNINativeInterface中的函数,在cpp文件中可以直接通过env->调用,而在c文件中则需要通过(*env)->进行调用。
5, JNI中的签名规则,在jni中需要对函数进行签名,签名的目的是为了解决Java函数重载的问题。
1)基本数据类型(如boolean、byte、char、short、int、long、float、double)在JNI中有直接对应的字符表示。在JNI中,boolean类型被映射为Z。2)对象类型:对象类型用 L 开头,后面跟着完全限定的类名(使用 / 替换 .),并以分号 ; 结尾。java.lang.String 在JNI中表示为 Ljava/lang/String;。3)数组类型通过在类型签名后加上方括号"["来表示。多维数组通过添加多个方括号来表示。int[] 表示为 [I,String[][] 表示为 [[Ljava/lang/String;。4)方法类型(即 java.lang.reflect.Method 类型的对象)在JNI中有特殊的表示方式,但它不常直接用于JNI方法签名中,而是用于某些JNI函数(如CallNonvirtualMethod)的参数中。方法类型的签名是通过将方法参数的类型签名和返回类型的签名组合起来,并在前面加上 ( 和 ),然后后面跟上方法的返回类型签名来表示的。一个返回 void 并接受两个 int 参数的Java方法的JNI类型签名是 (II)V。
对于如下的代码

public class Example {  
    public void doSomething(int a, String b) {  
        // 方法体  
    }  
}

上面方法的JNI类型签名是 (ILjava/lang/String;)V,其中:( 和 ) 表示这是一个方法签名。I 表示第一个参数是 int 类型。Ljava/lang/String; 表示第二个参数是 String 类型。V 表示方法返回 void。