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 常用的函数。
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();复制代码
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();复制代码
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 层也能操作
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 是不会自动装箱的
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>
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();复制代码
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 函数可以查阅官方文档:
如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。喜欢的小伙伴可以关注一下哦。谢谢!