目录
前言
JNI是学习NDK的必经之路。
一、JNI是什么?
详情摘录于:JNI笔记 : 数据类型、JNI函数与签名_jni 类型_pecuyu的博客-CSDN博客
jni是Java native interface 的简称,是伴随着Java诞生而出现的,目的就是Java语言和C语言、C++沟通的桥梁,实现了能与底层的交互。
1.表1 基本类型对照表
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位 |
在jin.h头文件中有如下定义:
# include <inttypes.h> /* C99 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
2.表2 引用类型对照表
Java引用类型 | JNI类型 |
---|---|
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
All objects | jobject |
java.lang.Class | jclass |
java.lang.String | jstring |
Object[] | jobjectArray |
java.lang.Throwable | jthrowable |
如下图展示了引用类型以及关系:
二、基本使用流程
1.在Java中创建native方法
例如:
public native void addTest01(int number, String text, int[] intArray, String[] array);
public native void putStudent(Student student);
2.C++创建对应的方法
c++方法的解释
extern "C" // 支持C语言的代码
JNIEXPORT // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表我要暴露出去的标准形式定义
// 例如:在Windows中,对外暴露的标准就规定了,所以函数必须是Windows系统规则定义的
void JNICALL // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表 当前函数 压栈规则(行参规则)
// 例如:Windows中:代表函数压栈 从右 到 左边
C++创建对应的方法及基本数据类型api的调用。
extern "C" JNIEXPORT void JNICALL Java_com_kevin_ndk08_1as_1code_MainActivity_addTest01(
JNIEnv *env, // Java虚拟机 自动携带过来的,就是为了 让我们可以使用JNI的API
jobject thiz, // java中的 MainActivity 这个实例
jint number,
jstring text,
jintArray int_array,
jobjectArray string_array) {
// C领域中 JNI领域中 Java领域中
// int jint int
// const char * jstring String
int my_number = number;
LOGD("my_number: %d\n", my_number);
// 参数二:第一重意思:是否在内部完成Copy操作,NULL==0 false,
//第二重意思:要给他一个值,让内部可以转起来,这个值,随意
const char * my_text = env->GetStringUTFChars(text, NULL);
LOGD("my_text: %s\n", my_text);
// 回收 GetStringUTFChars
env->ReleaseStringUTFChars(text, my_text);
// 打印Int数组
// jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
jint * my_int_array = env->GetIntArrayElements(int_array, NULL);
// jsize GetArrayLength(jarray array)
jsize intsize = env->GetArrayLength(int_array);
for (int i = 0; i < intsize; ++i) {
int result = *(my_int_array+i);
*(my_int_array+i)+=1000;
LOGD("遍历IntArray里面的值:%d\n",result);
}
// 回收
env->ReleaseIntArrayElements(int_array, my_int_array, 0); // 0代表要刷新
// 打印String数组
jsize jsize1 = env->GetArrayLength(string_array);
for (int i = 0; i < jsize1; i++) {
jobject jobject1 = env->GetObjectArrayElement(string_array, i);
jstring jstring1 = static_cast<jstring>(jobject1);
const char * itemStr = env->GetStringUTFChars(jstring1, NULL);
LOGD("遍历String Array 里面的值:%s\n", itemStr);
// 回收
env->ReleaseStringUTFChars(jstring1, itemStr);
}
}
对象的调用
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk08_1as_1code_MainActivity_putStudent(JNIEnv *env,
jobject thiz,
jobject student) {
// C领域中 JNI领域中 Java领域中
// jclass class
// jmethodID Method
// 1.获取字节码
const char * student_clss_str = "com/kevin/ndk08_as_code/Student";
jclass student_class = env->FindClass(student_clss_str);
// 2.拿到方法对象
const char * sig = "(Ljava/lang/String;)V"; // 方法签名 javap -s 全类名 必须在.class下
jmethodID setName = env->GetMethodID(student_class, "setName", sig);
sig = "(I)V";
jmethodID setAge = env->GetMethodID(student_class, "setAge", sig);
sig = "()V";
jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "myStaticMethod", sig);
// 3.调用对象
const char * str = "AAAAAAAA";
jstring str2 = env->NewStringUTF(str);
env->CallVoidMethod(student, setName, str2);
env->CallVoidMethod(student, setAge, 888);
env->CallStaticVoidMethod(student_class, myStaticMethod);
env->DeleteLocalRef(student_class); // 回收
env->DeleteLocalRef(student); // 回收
}
3.常用api使用
- 获取字节码的方法
①.通过全类名获取字节码
//通过全类名获取字节码 const char * dog_cls_str = "com/kevin/ndk09_code/Dog"; jclass dogcls = env->FindClass(dog_cls_str);
②.通过对象来拿
jclass dogcls = env->GetObjectClass(jobject);
- 获取对象
①通过AllocObject,不经过对象的构造方法jobject jdog = env->AllocObject(dogcls);
②通过NewObject,通过java对象中定义的构造方法来创建对象。需要指定构造方法的jmethodID。
jobject NewObject(jclass clazz, jmethodID methodID, ...)
- 获取对象的jmethodID
①普通函数的获取
②static函数的获取sig = "(Ljava/lang/String;)V"; jmethodID setName = env->GetMethodID(student_class, "setName", sig);
sig = "()V"; jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "staticMethodName", sig);
③构造函数的获取,第二个参数固定写法 "<init>"
// Java构造方法的实例化 const char * sig = "()V"; const char * method = "<init>"; // Java构造方法的标识 jmethodID init = env->GetMethodID(dogClass, method, sig);
4.引用类型
如何创建全局引用,static_cast<jclass>(env->NewGlobalRef(temp));
jclass dogClass;
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_testDog(JNIEnv *env, jobject thiz) {
// 局部引用:如果在函数里面,是在栈区,不用回收,函数结束,会自动回收 ,为了专业性,最好要写回收
if (dogClass == NULL) { // 第一次满足, 第二次不满足了
// 局部引用的方式
/*const char * dog_class_str = "com/kevin/ndk09_code/Dog";
dogClass = env->FindClass(dog_class_str);*/
// 解决各个局部引用带来的问题,全局引用(自己来提升)
const char * dog_class_str = "com/kevin/ndk09_code/Dog";
jclass temp = env->FindClass(dog_class_str);
dogClass = static_cast<jclass>(env->NewGlobalRef(temp));
// 手动释放全局引用之后,再次点击,没有进来
__android_log_print(ANDROID_LOG_DEBUG, "Kevin", "dogClass == NULL");
}
// Java构造方法的实例化
const char * sig = "()V";
const char * method = "<init>"; // Java构造方法的标识
jmethodID init = env->GetMethodID(dogClass, method, sig);
env->NewObject(dogClass, init); // 由于dogClass 是悬空的,直接报错
// 会隐士释放 dogClass , dogClass不为NULL, 悬空 同时手动全局释放一致
}
手动释放全局
// 此函数就是为了 手动释放全局
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_testUnDog(JNIEnv *env, jobject thiz) {
// TODO: implement testUnDog()
if(dogClass != nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, "Kevin", "全局应用被释放了,上面的按钮不能点击了,否则报错");
env->DeleteGlobalRef(dogClass);
dogClass = nullptr;
}
// Studnet * student = new Student; // 堆 必须释放
}
5.动态注册
动态注册流程
- 编写Java端的相关native方法
- 编写C/C++代码, 实现JNI_Onload()方法
- 将Java 方法和 C/C++方法通过签名信息一一对应起来
- 通过JavaVM获取JNIEnv, JNIEnv主要用于获取Java类和调用一些JNI提供的方法
- 使用类名和对应起来的方法作为参数, 调用JNI提供的函数RegisterNatives()注册方法
重写函数JNI_OnLoad,此函数会在 System.loadLibrary("native-lib"); 时加载。
JNI_OnLoad即可获取javaVM虚拟机实例,jvm可以跨线程。
// 下面是动态注册
JavaVM * jvm;
/*需要动态注册的方法结构体
* typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
*/
static const JNINativeMethod jniNativeMethod[] = {
{"registerJava01", "(Ljava/lang/String;)V", (void *)(register01)},
{"registerJava02", "(Ljava/lang/String;)V", (int *)(register02)}
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * javaVm, void * pVoid){
jvm = javaVm;
// 通过虚拟机 创建全新的 evn
JNIEnv * env = nullptr;
jint result = javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6); // 参数2:是JNI的版本 NDK 1.6 JavaJni 1.8
if (result != JNI_OK) {
return -1; // 主动报错
}
const char * mainActivityClassStr = "com/kevin/ndk09_code/MainActivity";
jclass mainActivityClass = jniEnv->FindClass(mainActivityClassStr);
jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod, sizeof(jniNativeMethod) / sizeof(JNINativeMethod)); // 参数三:到底要动态注册几个
return JNI_VERSION_1_6;
}
6.jni线程操作
jobject instance;//调用函数的实例,要提升为全局变量,并记得释放。
//线程的异步方法
void * customThread(void * pVoid) {
// jobject instance = static_cast<jobject>(pVoid);
// 如果instance不提升为全局,悬空 == instance
// 调用的话,一定需要JNIEnv *env
// JNIEnv *env 无法跨越线程,只有JavaVM才能跨越线程
JNIEnv * env = nullptr; // 全新的env
// 把native的线程,附加到JVM,并实例化env
int result = jvm->AttachCurrentThread(&env, 0);
if (result != 0) {//实例化结果
return 0;
}
// MainActivity main = AllocaObject(); // C++实例化MainActivity,无法拿到MainActivity上下文
// 包名 + 类名 同样不能拿到上下文
/*const char * mainActivityStr = "com/kevin/ndk09_code/MainActivity";
jclass mainActivityClass = env->FindClass(mainActivityStr);*/
//通过对象拿到字节码才可以
jclass mainActivityClass = env->GetObjectClass(instance);
// 拿到MainActivity的updateUI
const char * sig = "()V";
jmethodID updateUI = env->GetMethodID(mainActivityClass, "updateUI", sig);
env->CallVoidMethod(instance, updateUI);
// 解除 附加 到 JVM 的native线程,必须要
jvm->DetachCurrentThread();
return 0;
}
//native 方法实现
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_testThread(JNIEnv *env, jobject thiz) {
instance = env->NewGlobalRef(thiz); // 全局的,就不会被释放,所以可以在线程里面用
// 如果是非全局的,函数一结束,就被释放了
pthread_t pthreadID;
pthread_create(&pthreadID, 0, customThread, instance);
pthread_join(pthreadID, 0);
}
主动释放被提升为全局变量的 instance。可以在activity中onDestroy中调用。
// 释放全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_unThread(JNIEnv *env, jobject thiz) {
if (NULL != instance) {
env->DeleteGlobalRef(instance);
instance = NULL;
}
}