转载

Android NDK 开发之 Java 和 Native 交互

Android NDK 开发之 Java 和 Native 交互

一、什么是 JNIEnv

1.在我们的第一个 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());
}
复制代码

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

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

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

二、JNI 实例方法

1.还是以我们的第一个 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());
}复制代码

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

public native String stringFromJNI();复制代码

三、JNI 实例静态方法

1.上面我们提到第二个参数是 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());
}复制代码

2.对应 Java 层方法:

public native static String stringFromJNI2();复制代码

四、Native 操作 Java 对象的属性

1.主要通过 SetIntField 和 GetIntField 方法操作对象的属性

// 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;
}
复制代码

2.对应 Java 层方法:

public native int updateObjProperty();复制代码

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

五、Native 调用 Java 对象的方法

1.主要通过 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);
}复制代码

2.对应的 Java 层代码:

public native String invokeObjMethod();复制代码

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

六、Native 创建 Java 对象并返回

1.创建对象主要是通过 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;
}复制代码

2.对应的 Java 层代码:

public native User createObj();复制代码

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

七、Native 返回一个int数组

1.主要是通过 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;
}复制代码

2.对应的 Java 代码:

public native int[] getIntArray();复制代码

八、Native 修改 Java 传递进来的数组

1.通过 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);
}
复制代码

2.对应的 Java 代码:

public native void updateIntArray(int[] array);复制代码

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

1.判断两个对象的地址是否相同不能使用 ==,要是用 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);
}
复制代码

2.对应的 Java 代码:

public native boolean equals(User user1,User user2);复制代码

十、总结

本文只介绍了最常用的一些 JNI 函数使用方法和注意事项,方便日后查阅和完善,更多的 JNI 函数可以查阅官方文档:

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。喜欢的小伙伴可以关注一下哦。谢谢!

Android NDK 开发之 Java 和 Native 交互

原文  https://juejin.im/post/5d888429e51d453c135c5bb8
正文到此结束
Loading...