JNI: invoke java dialog with native callback:
store native function address in java, and invoke native another method to this function.
key points:
1.Store callback function pointer(address) in java: to support multiple calls, use data block for each call.
2.Use primitive long(64bit) in Java to store native callback pointer, for 64 bit native compatibility
3.intptr_t for pointer in native.
4.Show dialog in UI thread (Activity.runOnUiThread )
5.optional: Let the native code to handle localization (minimize Java code)
Java:
1 public static native void nativeOnSystemDialogResult(long nativeFuncAddr); 2 3 4 class DialogRunnable implements Runnable { 5 public String mTitle; 6 public String mMessage; 7 public String mYes; 8 public String mNo; 9 public long mOnYesAddr; 10 public long mOnNoAddr; 11 public boolean mTwoButton; 12 13 ////////////////////////////////////////////////////////////////////////// 14 ///title, message, localized Yes No 15 DialogRunnable(String tittle, String message, String locYes, String locNo, long onYesAddr, long onNoAddr, boolean twoButton) 16 { 17 mTitle = tittle; 18 mMessage = message; 19 mYes = locYes; 20 mNo = locNo; 21 mOnYesAddr = onYesAddr; 22 mOnNoAddr = onNoAddr; 23 mTwoButton = twoButton; 24 } 25 26 ////////////////////////////////////////////////////////////////////////// 27 public void run() { 28 29 if( mTwoButton ) { 30 Dialog dialog = new AlertDialog.Builder( GameActivity.getInstance() ) 31 .setTitle(mTitle) 32 .setMessage(mMessage) 33 .setPositiveButton( mYes, new DialogInterface.OnClickListener() { 34 public void onClick(DialogInterface dialog, int whichButton) { 35 GameActivity.getInstance().nativeOnSystemDialogResult( mOnYesAddr ); 36 } 37 }) 38 .setNegativeButton( mNo, new DialogInterface.OnClickListener() { 39 public void onClick(DialogInterface dialog, int whichButton) { 40 GameActivity.getInstance().nativeOnSystemDialogResult( mOnNoAddr ); 41 } 42 }) 43 .setCancelable(false) 44 .create(); 45 dialog.show(); 46 }else { 47 Dialog dialog = new AlertDialog.Builder( GameActivity.getInstance() ) 48 .setTitle(mTitle) 49 .setMessage(mMessage) 50 .setPositiveButton( mNo, new DialogInterface.OnClickListener() { 51 public void onClick(DialogInterface dialog, int whichButton) { 52 GameActivity.getInstance().nativeOnSystemDialogResult( mOnYesAddr ); 53 } 54 }) 55 .setCancelable(false) 56 .create(); 57 dialog.show(); 58 } 59 } 60 } 61 62 63 ////////////////////////////////////////////////////////////////////////// 64 //Cooperate with native code. DO NOT call on Java 65 ////////////////////////////////////////////////////////////////////////// 66 public void showDialogYesNo(String title, String showText, String locYes, String locNo, long onYesAddr, long onNoAddr) { 67 this.runOnUiThread( new DialogRunnable(title, showText, locYes, locNo, onYesAddr, onNoAddr, true) ); 68 } 69 70 71 72
C (for C++, JNI calls are simpler & different)
1 JNIEXPORT void JNICALL Java_com_org_package_GameActivity_nativeOnSystemDialogResult(JNIEnv *env, jobject thiz, jlong functionAddr) 2 { 3 typedef void(*FUNCPTR)(void); 4 FUNCPTR ptr = (FUNCPTR)(void*)function; 5 if( ptr != null ) 6 ptr(); 7 }
1 ////////////////////////////////////////////////////////////////////////// 2 //Note: onOK & onCancel can be NULL 3 void Android_SystemDialog(const char* title, const char* message, const char* yes, const char* no, void(*onOK)(void), void(*onCancel)(void) ) 4 { 5 android_app* app = GetApp(); 6 JNIEnv* env = app->activity->env; 7 //note: we need to attach dalvik VM to current thread, as it is not main thread 8 JavaVM* vm = app->activity->vm; 9 if ( (*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) < 0 ) 10 (*vm)->AttachCurrentThread(vm, &env, NULL); 11 12 jclass ActivityClass = (*env)->GetObjectClass(env, app->activity->clazz); 13 jmethodID java_method = (*env)->GetMethodID(env, ActivityClass, 14 (char8*)"showDialogYesNo", 15 (char8*)"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJ)V"); 16 assert( java_method != NULL ); 17 18 jstring jTitle = (*env)->NewStringUTF(env, title, strlen(title) ); 19 jstring jMsg = (*env)->NewStringUTF(env, message, strlen(message) ); 20 jstring jOK = (*env)->NewStringUTF(env, yes, strlen(yes) ); 21 jstring jCancel = (*env)->NewStringUTF(env, no, strlen(no) ); 22 //Note: jlong is 64 bit 23 jlong jOnOK = (intptr_t)onOK; 24 jlong jOnCancel = (intptr_t)onCancel; 25 //invoke UI Dialog in another thread 26 (*env)->CallVoidMethod(env, app->activity->clazz , java_method, jTitle, jMsg, jOK, jCancel, jOnOK, jOnCancel); 27 28 (*env)->DeleteLocalRef(env, jTitle); 29 (*env)->DeleteLocalRef(env, jMsg); 30 (*env)->DeleteLocalRef(env, jOK); 31 (*env)->DeleteLocalRef(env, jCancel); 32 (*env)->DeleteLocalRef(env, ActivityClass); 33 }
native usage sample:
1 static void OnExit() 2 { 3 exit(0); 4 } 5 6 void Android_Confirm_Exit() 7 { 8 const char* title = "Quit"; 9 const char* message = "Unsaved progress will be lost. Are you sure you want to quit game?"; 10 const char* yes = "Ok"; 11 const char* no = "Cancel"; 12 Android_SystemDialog(title, message, yes, no, &OnExit, NULL); 13 }