Day08_JNI、Linux 常用命令
一、JNI_OnLoad
通过 System.loadLibrary() 去加载 NDK 编译生成的动态库时,内部就会先去找动态库中是否有 JNI_OnLoad 这个函数,找到了就会调用这个函数
JNI_OnLoad 函数会告诉 JVM,native 组件使用的 JNI 版本
- NDK 提供的 JNI 版本
查看 NDK 版本,如下图,当前 NDK 版本为 19
如下图所示,根据方法提示可知,当前版本 NDK 提供 JNI_VERSION_1_1、JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6,只能用 JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6,用 JNI_VERSION_1_1 报错
- JDK 提供的 JNI 版本
JDK 1.8 提供 JNI_VERSION_1_1、JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6、JNI_VERSION_1_8
JNI_OnLoad()
- 调用 System.loadLibrary() 时,内部会去查找动态库中的 JNI_OnLoad 函数,若存在则调用
- JNI_OnLoad 会返回 JNI 版本信息
// 参数一:JNIEnv,JVM 虚拟机
// 参数二:void*,系统传过来该值为 NULL,我们可以不用管第二个参数
// 返回值 jint:当前使用的 JNI 版本(Android 使用 SDK 提供的类,JNI 在 SDK 中,NDK 使用了 JNI)
jint JNI_OnLoad(JNIEnv *env, void* r) {
}
静态注册
在 .java 文件中声明 native 方法
public native String stringFromJNI();
在 .cpp 文件中实现 native 方法
extern "C" JNIEXPORT jstring JNICALL
Java_com_lql_dn_ndk_day07_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
动态注册
可以在 JNI_OnLoad 函数中进行动态注册
- native 方法声明
public native void dynamicJavaTest();
public native int dynamicJavaTest2(int i);
- native 方法实现
- 定义一个 JNINativeMethod 结构体数组
#include <android/log.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI_TAG", __VA_ARGS__);
// native 方法实现必须写在结构体数组前面,否则当结构体数组会报红,找不到 native 方法实现
void dynamicTest() {
}
// 注意:如果在 Java 代码中调用 dynamicJavaTest2(88),则 88 对应 native 方法 dynamicTest2() 的第三个参数,前两个参数有参数占位即可
jint dynamicTest2(jstring i1, jboolean i2, jint i) {
LOGE("dynamicTest2: %d", i);
return 0;
}
// JNINativeMethod 结构体数组
static const JNINativeMethod method[] = {
{"dynamicJavaTest", "()V", (void*)dynamicTest},
{"dynamicJavaTest2", "(I)I", (int*)dynamicTest2}
};
static const char *mClassName = "com/lql/dn/ndk/day07/MainActivity";
jint JNI_OnLoad(JavaVM *vm, void *r) {
LOGE("第一个调用的函数");
JNIEnv *env = nullptr;
int rc = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (rc != JNI_OK) {
return -1;
}
jclass clazz = (env)->FindClass(mClassName);
(env)->RegisterNatives(clazz, method, sizeof(method) / sizeof(JNINativeMethod));
return JNI_VERSION_1_6;
}
二、native 线程调用 Java
在 .java 文件中声明两个方法,一个是 native 方法,另一个是 java 方法(供 native 方法调用)
public native void nativeThread();
public void updateUI() {
Log.e("TAG", "updateUIupdateUIupdateUI");
if (Looper.myLooper() == Looper.getMainLooper()) {
Toast.makeText(this, "更新UI", Toast.LENGTH_SHORT).show();
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "子线程更新UI", Toast.LENGTH_SHORT).show();
}
});
}
}
在 .cpp 文件中实现 native 方法,开启 native 线程,调用 java 方法
#include <pthread.h>
// 借助 JavaVM 对象可以创建 JNIEnv 对象
JavaVM *_vm;
jint JNI_OnLoad(JavaVM *vm, void *r) {
_vm = vm;
LOGE("第一个调用的函数");
return JNI_VERSION_1_6;
}
// pthread 的执行函数
void* threadTask(void* args) {
JNIEnv *env;
// 关键二:当前是 native 线程,附加到 Java 虚拟机,获取当前线程的 JNIEnv
int rc = _vm->AttachCurrentThread(&env, 0); // 指针的指针可以修改指针的值(指向)。直接传指针不能修改,传参时调用了复制构造函数
if (rc != JNI_OK) {
return reinterpret_cast<void *>(-1);
}
TContext *tContext = static_cast<TContext *>(args);
jclass cls = env->GetObjectClass(tContext->instance); // 获取 MainActivity 的 Class
jmethodID jmid = env->GetMethodID(cls, "updateUI", "()V"); // 对应 Java 层的 upateUI() 方法
env->CallVoidMethod(tContext->instance, jmid); // 调用 Java 层的 upateUI() 方法
delete(tContext);
tContext = 0;
// 关键三:必须调用 DetachCurrentThread 方法,从 Java 虚拟机分离
_vm->DetachCurrentThread();
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_lql_dn_ndk_day07_MainActivity_nativeThread(JNIEnv *env, jobject instance) {
TContext *tContext = new TContext;
// 关键一:需要在外面使用,必须创建全局引用
tContext->instance = env->NewGlobalRef(instance);
// 创建线程
pthread_t pid;
// 启动线程
pthread_create(&pid, 0, threadTask, 0); // 第三个参数是函数指针,第四个参数会传给第三个参数
}
结构体和类的区别
在 C/C++ 中,struct 结构体(属性、继承)默认行为是 public,class 类(属性、继承)默认行为是 private