JNI回調是指在c/c++代碼中調用java函數,當在c/c++的線程中執行回呼函數時,會導致回調失敗。
其中一種在Android系統的解決方案是:
把c/c++中所有線程的建立,由pthread_create函數替換為由Java層的建立線程的函數AndroidRuntime::createJavaThread。
假設有c++函數:
void *thread_entry(void *args){while(1){printf("thread running...\n");sleep(1);}}void init(){pthread_t thread;pthread_create(&thread,NULL,thread_entry,(void *)NULL);}
init()函數建立一個線程,需要在該線程中調用java類Test的回呼函數Receive:
public void Receive(char buffer[],int length){String msg = new String(buffer);msg = "received from jni callback:" + msg;Log.d("Test", msg);}
首先在c++中定義回呼函數指標:
//test.h#include <pthread.h>//function type for receiving data from nativetypedef void (*ReceiveCallback)(unsigned char *buf, int len);/** Callback for creating a thread that can call into the Java framework code. * This must be used to create any threads that report events up to the framework. */typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);typedef struct{ReceiveCallback recv_cb;CreateThreadCallback create_thread_cb;}Callback;
再修改c++中的init和thread_entry函數:
//test.c#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <sys/wait.h>#include <unistd.h>#include "test.h"void *thread_entry(void *args){char *str = "i'm happy now";Callback cb = NULL;int len;if(args != NULL){cb = (Callback *)args;}len = strlen(str);while(1){printf("thread running...\n");//invoke callback method to javaif(cb != NULL && cb->recv_cb != NULL){cb->recv_cb((unsigned char*)str, len);}sleep(1);}}void init(Callback *cb){pthread_t thread;//pthread_create(&thread,NULL,thread_entry,(void *)NULL);if(cb != NULL && cb->create_thread_cb != NULL){cb->create_thread_cb("thread",thread_entry,(void *)cb);}}
然後在jni中實現回呼函數,以及其他實現:
//jni_test.c#include <stdlib.h>#include <malloc.h>#include <jni.h>#include <JNIHelp.h>#include "android_runtime/AndroidRuntime.h"#include "test.h"#define RADIO_PROVIDER_CLASS_NAME "com/tonny/Test"using namespace android;static jobject mCallbacksObj = NULL;static jmethodID method_receive;static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { if (env->ExceptionCheck()) { LOGE("An exception was thrown by callback '%s'.", methodName); LOGE_EX(env); env->ExceptionClear(); }}static void receive_callback(unsigned char *buf, int len){int i; JNIEnv* env = AndroidRuntime::getJNIEnv();jcharArray array = env->NewCharArray(len);jchar *pArray ;if(array == NULL){LOGE("receive_callback: NewCharArray error.");return; }pArray = (jchar*)calloc(len, sizeof(jchar));if(pArray == NULL){LOGE("receive_callback: calloc error.");return; }//copy buffer to jchar arrayfor(i = 0; i < len; i++){*(pArray + i) = *(buf + i);}//copy buffer to jcharArrayenv->SetCharArrayRegion(array,0,len,pArray);//invoke java callback method env->CallVoidMethod(mCallbacksObj, method_receive,array,len);//release resourceenv->DeleteLocalRef(array);free(pArray);pArray = NULL; checkAndClearExceptionFromCallback(env, __FUNCTION__);}static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg){ return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);}static Callback mCallbacks = {receive_callback,create_thread_callback};static void jni_class_init_native(JNIEnv* env, jclass clazz){method_receive = env->GetMethodID(clazz, "Receive", "([CI)V");}static int jni_init(JNIEnv *env, jobject obj){if (!mCallbacksObj)mCallbacksObj = env->NewGlobalRef(obj);return init(&mCallbacks);}static const JNINativeMethod gMethods[] = { { "class_init_native","()V",(void *)jni_class_init_native }, { "native_init","()I",(void *)jni_init },}; static int registerMethods(JNIEnv* env) { const char* const kClassName = RADIO_PROVIDER_CLASS_NAME; jclass clazz; /* look up the class */ clazz = env->FindClass(kClassName); if (clazz == NULL) { LOGE("Can't find class %s/n", kClassName); return -1; } /* register all the methods */ if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK) { LOGE("Failed registering methods for %s/n", kClassName); return -1; } /* fill out the rest of the ID cache */ return 0; } jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; LOGI("Radio JNI_OnLoad"); if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed/n"); goto fail; } if(env == NULL){goto fail;}if (registerMethods(env) != 0) { LOGE("ERROR: PlatformLibrary native registration failed/n"); goto fail; } /* success -- return valid version number */ result = JNI_VERSION_1_4; fail: return result; }
jni的Android.mk檔案中共用庫設定為:
LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper
最後再實現Java中的Test類:
//com.tonny.Test.javapublic class Test {static{try {System.loadLibrary("test");class_init_native();} catch(UnsatisfiedLinkError ule){ System.err.println("WARNING: Could not load library libtest.so!"); }}public int initialize() {return native_radio_init();}public void Receive(char buffer[],int length){String msg = new String(buffer);msg = "received from jni callback" + msg;Log.d("Test", msg);}protected static native void class_init_native();protected native int native_init();}