小言_互联网的博客

Android NDK 开发之 Java 和 Native 交互

309人阅读  评论(0)

我们已经在《Android NDK 开发之 相关术语及第一个NDK程序分析》 介绍了 NDK 开发的基础知识,今天我们开始介绍下 JNI 的常用函数

什么是 JNIEnv

在我们的第一个 NDK 程序中的 stringFromJNI 方法的第一个参数就是 JNIEnv

extern "C" JNIEXPORT jstring JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

那么什么是 JNIEnv 呢?

JNIEnv 是一个与线程相关的,代表 JNI 环境的结构体。JNIEnv 里面封装了一些 JNI 系统函数,用于操作对象和调用函数。

JNIEnv 是线程相关的,所以不能跨线程使用,各自的线程只能使用各自的 JNIEnv。例如我们上面的 stringFromJNI 就可以直接使用它的第一个参数 JNIEnv,但有的时候我们不能直接拿到 JNIEnv,比如在 Native 层的后台线程收到了某个消息,需调用 Java 层的函数时,这个时候没有 JNIEnv,怎么办呢?我们可以使用 JavaVM,它是虚拟机在 JNI 层的代表,不管有多少个线程只有一个 JavaVM,通过 JavaVM 的 AttachCurrentThread 函数来获取当前线程的 JNIEnv 结构体,当后台线程退出时需要通过 JavaVM 的 DetachCurrentThread 函数来释放资源。

下面我们来看下 JNIEnv 常用的函数。

JNI 实例方法

还是以我们的第一个 NDK 程序中的 stringFromJNI 方法为例:

extern "C" JNIEXPORT jstring JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

该方法的第二个参数是 jobject,意思就是说该方法是一个实例方法,Java 层与之对应就是:

public native String stringFromJNI();

JNI 实例静态方法

上面我们提到第二个参数是 jobject ,表明它是实例方法,那么我们将第二个参数改成 jclass 那么该方法就是静态方法了:

// Native 返回一个字符串(静态方法)
extern "C" JNIEXPORT jstring JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_stringFromJNI2(
        JNIEnv *env,
        jclass) {
    std::string hello = "Hello from C++ static method...";
    return env->NewStringUTF(hello.c_str());
}

对应 Java 层方法:

public native static String stringFromJNI2();

Native 操作 Java 对象的属性

主要通过 SetIntFieldGetIntField 方法操作对象的属性

// Native 设置/获取 Java 对象的属性
extern "C" JNIEXPORT jint JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_updateObjProperty(
        JNIEnv *env,
        jobject obj) {
        
    // 获取对象的 class
    jclass jclazz = env->GetObjectClass(obj);
    
    // 获取字段id
    jfieldID jfid = env->GetFieldID(jclazz, "number", "I");

    // 设置属性的值
    env->SetIntField(obj, jfid, COUNT);

    // 获取属性的值
    jint value = env->GetIntField(obj, jfid);

    env->DeleteLocalRef(jclazz);

    return value;
}

对应 Java 层方法:

public native int updateObjProperty();

如果对象的属性为private,Native 层也能操作

Native 调用 Java 对象的方法

主要通过 Call[Type]Method 方法来调用 Java 对象实例方法,如果是静态方法则使用:CallStatic[Type]Method,其中的 type 就是方法的返回值类型:

// Native 调用 Java 对象的方法
extern "C" JNIEXPORT jstring JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_invokeObjMethod(
        JNIEnv *env,
        jobject obj) {
    jclass jclazz = env->GetObjectClass(obj);
    jmethodID jmid = env->GetMethodID(jclazz, "methodForJNI", "(I)Ljava/lang/String;");
    env->DeleteLocalRef(jclazz);
    return (jstring) env->CallObjectMethod(obj, jmid, COUNT);
}

对应的 Java 层代码:

public native String invokeObjMethod();

注意:调用方法的时候需要注意参数是基本数据类型还是引用类型,如果参数为 Integer,不能传递 jint,JNI 是不会自动装箱的

Native 创建 Java 对象并返回

创建对象主要是通过 NewObject 来实现:

// Native 创建 Java 对象并返回
extern "C" JNIEXPORT jobject JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_createObj(
        JNIEnv *env,
        jobject) {
    jclass jclazz = env->FindClass("com/chiclaim/androidnative/jni/User");
    jmethodID jmid = env->GetMethodID(jclazz, "<init>", "(Ljava/lang/String;)V");
    jstring username = env->NewStringUTF("Chiclaim");
    jobject user = env->NewObject(jclazz, jmid, username);
    env->DeleteLocalRef(jclazz);
    return user;
}

对应的 Java 层代码:

public native User createObj();

注意:构造方法的名字为 <init>

Native 返回一个int数组

主要是通过 NewIntArray 函数创建数组,SetIntArrayRegion 函数为数组设置值:

const jint COUNT = 10;
// Native 返回一个int数组
extern "C" JNIEXPORT jintArray JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_getIntArray(
        JNIEnv *env,
        jobject) {
    jintArray _intArray = env->NewIntArray(COUNT);
    jint tmpArray[COUNT];
    for (jint i = 0; i < COUNT; i++) {
        tmpArray[i] = i;
    }
    env->SetIntArrayRegion(_intArray, 0, COUNT, tmpArray);
    return _intArray;
}

对应的 Java 代码:

public native int[] getIntArray();

Native 修改 Java 传递进来的数组

通过 GetIntArrayElements 获取数组的值,然后修改响应的值,最后通过 SetIntArrayRegion 方法将元素重新设置到原来的数组中去:

const int VALUE = 100;
// Native 修改 Java 传递进来的数组
extern "C" JNIEXPORT void JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_updateIntArray(
        JNIEnv *env,
        jobject,
        jintArray intArray) {
    jboolean isCopy = static_cast<jboolean>(false);
    jint *arr = env->GetIntArrayElements(intArray, &isCopy);
    jint length = env->GetArrayLength(intArray);
    arr[0] = VALUE;

    env->SetIntArrayRegion(intArray, 0, length, arr);
    
    env->ReleaseIntArrayElements(intArray, arr, JNI_ABORT);
}

对应的 Java 代码:

public native void updateIntArray(int[] array);

判断两个对象的地址是否相同

判断两个对象的地址是否相同不能使用 ==,要是用 IsSameObject 函数:

extern "C" JNIEXPORT jboolean JNICALL
Java_com_chiclaim_androidnative_jni_JNIHolder_equals(
        JNIEnv *env, 
        jobject thiz, 
        jobject user1,
        jobject user2) {
    return env->IsSameObject(user1, user2);
}

对应的 Java 代码:

public native boolean equals(User user1,User user2);

小结

本文只介绍了最常用的一些 JNI 函数使用方法和注意事项,方便日后查阅和完善,更多的 JNI 函数可以查阅官方文档:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

Reference


转载:https://blog.csdn.net/johnny901114/article/details/101124117
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场