我们已经在《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 对象的属性
主要通过 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;
}
对应 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
- 《深入理解 Android 卷I》
- https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
- https://blog.csdn.net/qinjuning/article/details/7607214
- https://blog.csdn.net/shulianghan/article/details/38012515
转载:https://blog.csdn.net/johnny901114/article/details/101124117