NDK学习( 三),多线程与异步回调

来源:互联网 发布:知乎图片右显示一半 编辑:程序博客网 时间:2024/05/16 05:57
场景:在Java中传递任意类型参数,在JNI中新开子线程,将传入的参数处理后回调Java中的方法
技术点:
1、在JNI中新开子线程并传递参数
2、在子线程中回调Java方法(误区:JNIEnv指针可以共享)

对于技术点1,可参考一般的C++开发教程,一般创建子线程的方法有:
  • 通过pthread_create
  • C++11中thread的用法
参考:http://stackoverflow.com/questions/23872663/how-to-start-a-new-thread-from-jni

另外要注意的是传递的参数应当是堆内存中的指针,或者是全局变量,对于局部变量,可能在子线程中调用时已经失效。

对于技术点2,需要注意的是JNIEnv *这类指针是无法在线程间共享的,参考JNI文档。
参考:http://stackoverflow.com/questions/13383823/native-multithreading-and-jni
步骤:
  • 在JNI_OnLoad或者Java调用的native方法中缓存JavaVM*,JavaVM*指针是唯一在线程间可以共享的。
  • 在子线程的native方法中调用AttachCurrentThread ,获取JNIEnv*指针。
  • 通过JavaVM*和JNIEnv*找到你需要的jclasses,jobjects,jmethodIDs,它们不能在线程间共享。
  • 如果需要,将jclasses,jobjects转换为全局变量,jmethodIDs不需要转换,因为它不是对象。
  • 在JNI_OnUnlaod时删除全局变量这类引用,当你不再需要时。


理解JNI_OnLoad、JNI_OnUnload
参考:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnLoad

当native lib被加载(如System.loadLibrary()执行后),虚拟机会执行JNI_OnLoad,该方法必须返回当前JNI版本号。如果native lib不exportJNI_OnLoad方法,虚拟机认为lib只需要JNI_VERSION_1_1.如果返回的版本号不能被识别,该lib将不能被加载。

当包含native lib的类加载器被垃圾回收器回收,JNI_OnUnload会被调用。该方法可用来清理不再需要的指针。


Java代码:
public native void asyncCallBack(String str);//执行后向native lib传入字符串

native代码:
extern "C" {
JavaVM* javaVM; //定义要全局共享的虚拟机指针
jobject jinstance; //定义要共享的Java对象

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* jvm, void* reserved){
javaVM = jvm; //将全局虚拟机指针赋值
JNIEnv* env = NULL;
jint result = -1;

//获取JNI版本

if (jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
{
LOGE("GetEnv failed!");
return result;
}
LOGE("JNI_VERSION:%d",JNI_VERSION_1_4);
return JNI_VERSION_1_4;
}

//线程函数
void* runAsync(void* args) {
const char* chars = (const char*) args;

JNIEnv *env = NULL;
//Attach主线程
if (javaVM->AttachCurrentThread(&env, NULL) != JNI_OK) {
LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);
return NULL;
}
if (env != NULL) {
LOGE("jnienv* obtained");
}else {
return NULL;
}
LOGE("runAsync chars:%s", chars);
jclass jclass1 = env->GetObjectClass(jinstance);
if(jclass1==NULL){
LOGE("sub thread : failed get class");
return NULL;
}else{
LOGE("sub thread : find class");
}
jmethodID jmethodID =env->GetMethodID(jclass1,"callback","(Ljava/lang/String;)V");
if(jmethodID==NULL){
LOGE("sub thread : failed get jmethodID");
return NULL;
}else{
LOGE("sub thread : find jmethodID");
}
string str =chars;
string str2 = " from native";
str.append(str2);
jstring jstr=env->NewStringUTF(str.c_str());


env->CallVoidMethod(jinstance,jmethodID,jstr);
env->DeleteGlobalRef(jinstance);//删除全局引用
if(javaVM!=NULL){
javaVM=NULL;
LOGE("release JavaVM*");
}
if(jinstance!=NULL){
jinstance=NULL;
LOGE("release jinstance*");
}
LOGE("sub thread:task finish");
}

JNIEXPORT void JNICALL
Java_dev_mars_jnidemo_NativeThread_asyncCallBack(JNIEnv *env, jobject instance,jstring jstr) {
const char* chars = env->GetStringUTFChars(jstr,0);
LOGE("Java_dev_mars_jnidemo_NativeThread_asyncCallBack pass parameter:%s",chars);

//将jobject转换为全局变量,因为jclass、jobject也不能在线程间共享
jinstance = env->NewGlobalRef(instance);
pthread_t thread_1;
pthread_create(&thread_1,NULL,runAsync,(void*)chars);
}
}

小结:jobject、jclass、jmethodID这些类型无法在线程间共享,需要转换为全局变量。本例中在JNI接口线程中将jobject转换为全局变量,在子线程通过JavaVM*获取JNIEnv*,并通过全局变量jobect获取jclass,从而得到jmethod。

native调用java方法的函数主要有三种:
参考:
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp16656

NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
jmethodID methodID, va_list args);

通过观察发现,Method、MethodA、MethodV所带的参数类型不同,其中<type>是回调的返回类型。

创建jobject的全局引用通过 env->NewGlobalRef(instance);
回调完释放全局引用通过JNIEnv*的 env->DeleteGlobalRef(jinstance);

参考:
http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/sync.html

如本文有任何问题欢迎指出,谢谢
0 0
原创粉丝点击