链接:https://pan.baidu.com/s/1zB9r5WwuTMIPNMlYl5HlvA
提取码:5ndl
来模拟一个小功能,在JNI中,创建一个线程,用于下载文件,当文件下载成功之后,通知java层的方法进行更新。
开始实践吧
extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnistudy_MainActivity_downloadThread(JNIEnv *env, jobject thiz) {
}
在JNI的downloadThread中,创建一个JNI线程
pthread_t pid;
//启动线程 下载视频
pthread_create(&pid,NULL,threadTask,thiz);
在此说明下pthread_create的几个参数
pid,是线程的句柄,当线程创建好后,会将具体的句柄值传递过pid中,所谓的句柄,其实就是对象的一个标识符,通过这个标识符就能找到其他所在的内存地址。
第二个参数是一个结构体,该结构体中,你可以去定义一些线程的具体属性,此处直接传递NULL即可.
第三个参数是一个函数指针,有点类型java中的run方法,到时候,创建好的线程会回调这个函数,现在还没有创建threadTask函数,那么我们就立即创建一个吧
void* threadTask(void* args){
return 0;
}
第四个参数,其实就是第三个函数的参数,那么在此,我们可以直接将thiz传递过去,,thiz实际上就是MainActivity对象,到时我们想要调用MainActivity中的方法,就要用到这个参数。
那么好,现在线程我们已经创建好了,接下来就来完善threadTask中的逻辑
在JNI中想要获取java对象的Class,需要JNIEnv对象,在此有个误区,有的朋友可能会使用如下的JNIEnv
但我想告诉你,你使用上面的env对象绝对是错误的,因为这个env对象是在主线程中创建的,它不能被应用于子线程。那么问题来了,现在我在子线程中又非得要env对象,这个时候应该怎么办呢?答案就是利用java虚拟机,让它帮我们再创建一个适用于我们当前线程的env对象,做法如下。
在JNI_OnLoad中将JavaVM *vm 的vm作为全局变量,以便我们在子线程中使用
JavaVM它就代表的是java虚拟机,它能帮助我们创建一个env对象,创建env对象的代码如下
JNIEnv *env;
jint i = _vm->AttachCurrentThread(&env,0);
if (i != JNI_OK){
return 0;
}
那么好,现在env对象有了,接下来就可以获取MainActivity的class了,代码如下
jclass cls = env->GetObjectClass(static_cast(args));
有了class还不够,还要被调用方法的MethodId,MethodId获取如下
jmethodID updateUI = env->GetMethodID(cls,"updateUI","()V");
此时,MainActivity中,还没有updateUI这个方法,现在我们去创建一个
public void updateUI(){
if (Looper.myLooper() == Looper.getMainLooper()){
System.out.println("更新UI");
}else{
runOnUiThread(new Runnable() {
@Override
public void run() {
System.out.println("更新UI");
}
});
}
}
class有了,MethodId也有了,那么就可以调用了
env->CallVoidMethod(args,updateUI);
最后当线程执行完毕后,记得要让当前线程和java虚拟机分离,不要让两者之间有过多的牵扯
//分离
_vm->DetachCurrentThread();
到此,线程部分算是写完了,接下来,就来运行测试下吧
在MainActivity中调用downloadThread
运行的时候,程序直接奔溃了,报了如下一个错误。
JNI DETECTED ERROR IN APPLICATION: use of invalid jobject
该错误说我们用了一个无效的jobject,其实问题就出在这
因此,我们主要thiz变为全局变量就行了,改变方式如下,利用NewGlobalRef
另外,在线程结束后,记得要释放thiz这个全局变量,代码如下
完整代码如下
void* threadTask(void* args){
// native线程 附加 到 Java 虚拟机
JNIEnv *env;
jint i = _vm->AttachCurrentThread(&env,0);
if (i != JNI_OK){
return 0;
}
//假设,到此下载文件完成,该通知MainActivity了
//获得MainActivity的class对象
jclass cls = env->GetObjectClass(static_cast(args));
// 反射获得方法
jmethodID updateUI = env->GetMethodID(cls,"updateUI","()V");
env->CallVoidMethod(static_cast(args), updateUI);
env->DeleteGlobalRef(static_cast(args));
//分离
_vm->DetachCurrentThread();
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnistudy_MainActivity_downloadThread(JNIEnv *env, jobject thiz) {
pthread_t pid;
//启动线程 下载视频
jobject globalThiz = env->NewGlobalRef(thiz);
pthread_create(&pid,NULL,threadTask,globalThiz);
}