- 如果你想了解JNI在如何在多线程下使用
- 如果你在子线程使用JNI时遇到findClass不能找到目标Class,而在主线程下却能找到该Class的问题。或是GetEnv返回NULL的问题
- 如果你想多学点编程技术的话
那么,这篇文章就是为你而写的, :)
最近工作中遇到这么个问题:c++代码需要调用Android的API来做一个比较耗时的任务,因为有点耗时,希望能有个进度条显示给用户,很自然地,我创建了一个子线程用来执行这个耗时的任务,按照平时写法,结果一运行,GetEnv获取失败了。网上查找一番,官方说明有这么句话:
If the current thread is not attached to the VM, sets *env
to NULL
,
and returns JNI_EDETACHED
. If the specified
version is not supported, sets *env
to NULL
,
and returns JNI_EVERSION
. Otherwise,
sets *env
to the appropriate interface,
and returns JNI_OK
.
调试后找到了原因,the current thread is not attached。 原来子线程函数里需要使用AttachCurrentThread()和DetachCurrentThread()这两个函数。没错,你需要gJvm->AttachCurrentThread(&env,
NULL);来获取env,这样写之后,以为万事大吉了,结果findClass出错了,没有找到目标类,可是我千真万确地记得在主线程里这么写是没有问题的。env没有问题了,这回又哪里出错了呢?上网google一番,噢,不对,google被墙了,是用bing查找一番后,总算有些眉目了。
首先确保你的class name写对了,以包名开头,并用反斜杠隔开。如果class name没有错,那么应该是class loader的问题了。解决方法是你先在主线程中获取该class,并且将其保存为全局变量,以便其他线程使用。
jclass tmp = env->FindClass("com/example/company/MyClass");
myClass = (jclass)env->NewGlobalRef(tmp);
在子线程中,
mid = env->GetStaticMethodID(cls, "fromJNI", "(I)V");
if (mid != NULL)
{
env->CallStaticVoidMethod(env, cls, mid, i);
}
当然,也有其他解决方法,至少我使用这种方法成功了。而接下来在java中调用c++的代码就比较顺利了,木有碰到问题了。
总结:
1.在JNI_OnLoad中,保存JavaVM*,这是跨线程的,持久有效的,而JNIEnv*则是当前线程有效的。一旦启动线程,用AttachCurrentThread方法获得env。
2.通过JavaVM*和JNIEnv可以查找到jclass。
3.把jclass转成全局引用,使其跨线程。
4.然后就可以正常地调用你想调用的方法了。
5.用完后,别忘了delete掉创建的全局引用和调用DetachCurrentThread方法。