本文只针对对jni有了初步了解的同学,帮助其快速掌握jni的核心用法
本文涉及到的源码:github.com/bytebitx/ND…
JNI中重要的是三个表
jni也有基本的数据类型和引用类型,和java的基本数据类型及引用对应如下:
基本数据类型映射表
java类型 | jni类型 | 描述 |
---|---|---|
boolean | jboolean | 无符号8位 |
byte | jbyte | 有符号8位 |
char | jchar | 无符号16位 |
short | jshort | 有符号16位 |
int | jint | 有符号32位 |
long | jlong | 有符号64位 |
float | jfloat | 32位 |
double | jdouble | 64位 |
引用类型映射表
java引用类型 | jni类型 |
---|---|
All objects | jobject |
java.lang.Class | jclass |
java.lang.String | jstring |
java.lang.Throwable | jthrowable |
Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
方法签名
java类型 | 字符 |
---|---|
void | V |
boolean | Z |
int | I |
long | J |
float | F |
double | D |
byte | B |
char | C |
short | S |
int[] | [I (数组以‘[’开始) |
String | Ljava/lang/String; (记得要加分号) |
Object[] | [java/lang/object; |
JNI方法注册方式
jni分为静态注册和动态注册,Android Studio默认使用的是静态注册。
查找静态注册的jni方法,按照jni规范的命名规则进行查找,格式为Java_类路径_方法名
动态注册:
在Android中,当程序在Java层运行System.loadLibrary
这行代码后,程序会去载入so文件。于此同时,产生一个Load事件,这个事件触发后,程序默认会在载入的.so文件的函数列表中查找JNI_OnLoad
函数并执行。因此开发者经常会在JNI_OnLoad
中做一些初始化操作,动态注册就是在这里进行的,使用env->RegisterNatives(clazz, gMethods, numMethods)。
- 参数1:Java对应的类。
- 参数2:
JNINativeMethod
数组。 - 参数3:
JNINativeMethod
数组的长度,也就是要注册的方法的个数。
JNINativeMethod
是jni中定义的一个结构体
typedef struct {
const char* name; // 函数名称
const char* signature; // 函数签名
void* fnPtr; // 函数具体实现的指针
} JNINativeMethod;
案例
MainActivity中有三个方法,如下:
external fun accessBasicVar(l: Long, b: Boolean, s: String): ArrayList<Any>
external fun accessReferenceVar(testCallBack: TestCallBack)
external fun accessMainInstance(mainActivity: MainActivity)
现在需要动态注册accessBasicVar,accessReferenceVar,accessMainInstance这三个函数,应该怎么写呢?
JNINativeMethod jniNativeMethod[] = {
{"accessBasicVar", "(JZLjava/lang/String;)Ljava/util/ArrayList;", (void*) accessBasicVar}, // 这里的名称和第一个参数的名称可以不一致
{"accessReferenceVar", "(Lcom/bytebitx/ndkdemo/MainActivity$TestCallBack;)V", (void*) accessReferenceVar},
{"accessMainInstance", "(Lcom/bytebitx/ndkdemo/MainActivity;)V", (void*) accessMainInstance},
};
if (env->RegisterNatives(cls, jniNativeMethod, 3) < JNI_OK) {
return JNI_FALSE;
}
JNINativeMethod结构体中第三个参数的名称可以和java中对应的方法不一致,这里为了简便,所以写成了一样
accessBasicVar这个函数表示jni访问java的基本数据类型
JNIEXPORT JNICALL jobject accessBasicVar(JNIEnv *env, jobject thiz, jlong l, jboolean b, jstring s) {
LOGD("dynamic register accessBasicVar");
// 根据对象获取对应的class
jclass cls = env->GetObjectClass(thiz);
// 获取基本数据类型的fieldID
jfieldID lField = env->GetFieldID(cls, "basicL", "J");
jfieldID bField = env->GetFieldID(cls, "basicB", "Z");
jfieldID sField = env->GetFieldID(cls, "basicS", "Ljava/lang/String;");
jlong basicL = 2 + l;
jboolean basicB = !b;
jstring basicS = env->NewStringUTF("this is basic jni string");
// 重新赋值基本数据类型
env->SetLongField(thiz, lField, basicL);
env->SetBooleanField(thiz, bField, basicB);
env->SetObjectField(thiz, sField, basicS);
// 获取静态的基本数据类型变量的fieldID
jfieldID sLField = env->GetStaticFieldID(cls, "sBasicL", "J");
jfieldID sBField = env->GetStaticFieldID(cls, "sBasicB", "Z");
jfieldID sSField = env->GetStaticFieldID(cls, "sBasicS", "Ljava/lang/String;");
jlong sLong = 1 + l;
jboolean sBoolean = b;
jstring sString = env->NewStringUTF("this is new jni string");
// 重新给静态的基本数据类型变量赋值
env->SetStaticLongField(cls, sLField, sLong);
env->SetStaticBooleanField(cls, sBField, sBoolean);
env->SetStaticObjectField(cls, sSField, sString);
// 实例化ArrayList
jclass listCls = env->FindClass("java/util/ArrayList");
jmethodID cmid = env->GetMethodID(listCls, "<init>", "()V");
jobject arrayList = env->NewObject(listCls, cmid);
jmethodID addMid = env->GetMethodID(listCls, "add", "(Ljava/lang/Object;)Z");
// 由于创建ArrayList对象的时候,泛型是Object,是属于引用类型,所以需要创建基本数据类型
// 对应的引用类型,才能往ArrayList中添加
jclass longCls = env->FindClass("java/lang/Long");
jmethodID longConstructorMid = env->GetMethodID(longCls, "<init>", "(J)V");
jobject longObj = env->NewObject(longCls, longConstructorMid, basicL);
jobject sLongObj = env->NewObject(longCls, longConstructorMid, sLong);
// 调用ArrayList对象的add方法
env->CallBooleanMethod(arrayList, addMid, longObj);
env->CallBooleanMethod(arrayList, addMid, sLongObj);
jclass booleanCls = env->FindClass("java/lang/Boolean");
jmethodID boolConstructorMid = env->GetMethodID(booleanCls, "<init>", "(Z)V");
jobject boolObj = env->NewObject(booleanCls, boolConstructorMid, basicB);
jobject sBoolObj = env->NewObject(booleanCls, boolConstructorMid, sBoolean);
env->CallBooleanMethod(arrayList, addMid, boolObj);
env->CallBooleanMethod(arrayList, addMid, sBoolObj);
env->CallBooleanMethod(arrayList, addMid, basicS);
env->CallBooleanMethod(arrayList, addMid, sString);
return arrayList;
}
accessReferenceVar这个函数表示jni访问java的引用数据类型,例如这里需要访问MainActivity中的成员变量testCallBack,该变量的类型是TestCallBack
JNIEXPORT void JNICALL accessReferenceVar(JNIEnv *env, jobject thiz,
jobject test_call_back) {
// 获取对象对应的class对象
jclass testCls = env->GetObjectClass(test_call_back);
// 获取类中的方法id
jmethodID frameMid = env->GetMethodID(testCls, "onVideoFrame", "(IJ)V");
jlong bitrate = 1;
// 调用test_call_back对象的onVideoFrame方法
env->CallVoidMethod(test_call_back, frameMid, 1, bitrate);
jmethodID successMid = env->GetMethodID(testCls, "onConnectSuccess", "(II)V");
env->CallVoidMethod(test_call_back, successMid, 0, 0);
jmethodID errMid = env->GetMethodID(testCls, "onConnectError", "()V");
env->CallVoidMethod(test_call_back, errMid);
}
accessMainInstance这个函数其实也是jni访问java的引用数据类型,只是这个引用是MainActivity对象
//为什么 thiz 和 instance 不是同一个内存地址?
//JNI 是基于句柄的:jobject 是一个指向 JVM 内部数据结构的句柄,而不是直接的内存指针。不同的 jobject 可以引用相同的 Java 对象。
//不同的调用上下文:在 JNI 方法中,thiz 代表的是调用该方法的对象实例,而 instance 是作为参数传递的对象引用。
// 尽管它们引用相同的 Java 对象,但它们在 JNI 中被视为不同的句柄。
JNIEXPORT JNICALL void accessMainInstance(JNIEnv *env, jobject thiz, jobject main_activity) {
LOGD("dynamic register nativeMainActivity");
if (env->IsSameObject(thiz, main_activity)) { // true
LOGD("same object");
} else {
LOGD("not same object");
}
LOGD("Java object address: %p\n", thiz);
LOGD("Java object address: %p\n", main_activity);
// 打印的地址不一样
jclass cls = env->GetObjectClass(thiz); // 使用thiz和main_activity是相同效果
jmethodID resumeMid = env->GetMethodID(cls, "onResume", "()V");
env->CallVoidMethod(thiz, resumeMid);
}
如果想动态注册staticAccessNormalL函数呢?
//-------------------------------------注册kotlin的静态方法---------------------------------------
jclass clsCompanion = env->FindClass("com/bytebitx/ndkdemo/MainActivity$Companion");
if (clsCompanion == nullptr) {
return JNI_FALSE;
}
JNINativeMethod jniNativeCompanionMethod[] = {
{"staticAccessNormalL", "(J)J", (void*) staticAccessL},
};
if (env->RegisterNatives(clsCompanion, jniNativeCompanionMethod, 1) < JNI_OK) {
return JNI_FALSE;
}
staticAccessL实现方式如下:
JNIEXPORT JNICALL jlong staticAccessL(JNIEnv *env, jobject thiz, jlong l) {
LOGD("dynamic register accessStaticL");
// 根据对象获取对应的class
jclass cls = env->GetObjectClass(thiz);
jmethodID setLMid = env->GetMethodID(cls, "setSBasicL", "(J)V");
jlong sLong = 11 + l;
env->CallVoidMethod(thiz, setLMid, sLong);
return sLong;
}
在jni中要想实例化一个类,并调用其方法,那么实现方式如下:
//-------------------------------------实例化java类并调用java类的方法---------------------------------------
// 这个字符串表示需要实例化的java类的路径
jclass realBackCls = env->FindClass("com/bytebitx/ndkdemo/MainActivity$RealPlayCallBack");
// 获取构造方法的methodID
jmethodID cmid = env->GetMethodID(realBackCls, "<init>", "()V");
// 实例化MainActivity$RealPlayCallBack类,相当于java的new关键字
jobject realObj = env->NewObject(realBackCls, cmid);
// 获取类中对应的方法
// 参数1:对象
// 参数2:方法名称
// 参数3:方法签名(写法参见上面的签名表)
jmethodID framesMid = env->GetMethodID(realBackCls, "onVideoFrame", "(IJ)V");
jlong bitrate = 1;
env->CallVoidMethod(realObj, framesMid, 0, bitrate);
jmethodID connectSucMid = env->GetMethodID(realBackCls, "onConnectSuccess", "(II)V");
env->CallVoidMethod(realObj, connectSucMid, 1, 1);
jmethodID connectErrMid = env->GetMethodID(realBackCls, "onConnectError", "()V");
env->CallVoidMethod(realObj, connectErrMid);
以上都是jni在主线程访问java变量或者方法,那如果想在子线程访问变量和方法,又该怎么办呢? 例如在MainActivity中有一个这样的native方法:
// 子线程回调callback方法
external fun accessCallBackByChildThread(testCallBack: TestCallBack)
在jni中动态注册该native方法,方式和上面的一致,在JNINativeMethod
中添加函数名,函数签名及函数具体的实现即可。
jobject testObj = nullptr;
jmethodID fMid = nullptr;
// 具体的实现方式,注意AttachCurrentThread方法必现和DetachCurrentThread方法成对使用
void *testCallBack(void *) {
JNIEnv *env;
jint result = getJvm()->AttachCurrentThread(&env, nullptr);
if (result == 0) { // 获取env实例成功
jlong bitrate = 50;
env->CallVoidMethod(testObj, fMid, 50, bitrate);
env->DeleteGlobalRef(testObj);
}
getJvm()->DetachCurrentThread();
pthread_exit(0);
}
JNIEXPORT void JNICALL accessCallBackByChildThread(JNIEnv *env, jobject thiz, jobject test_call_back) {
jclass testCls = env->GetObjectClass(test_call_back);
testObj = env->NewGlobalRef(test_call_back);
fMid = env->GetMethodID(testCls, "onVideoFrame", "(IJ)V");
// jni中创建线程(其实就是c/c++创建线程的方式)
pthread_t pthreadHandle;
pthread_create(&pthreadHandle, nullptr, testCallBack, nullptr);
}
以上基本上就是jni的核心用法了,掌握了以上的方法,jni基本就算掌握了,剩下的就是c/c++语法的学习了。