【问题标题】:Create a message dialog in Android via NDK callback通过 NDK 回调在 Android 中创建消息对话框
【发布时间】:2024-04-10 16:55:02
【问题描述】:

我有一个 android 应用程序,在其中我在 Java 端设置了 OpenGL 上下文,并从 NDK/C++ 端发送绘图。这一切似乎都运作良好。

我希望 C++ 端能够弹出一个对话框。我实现了一个 java MakeADialog 函数,它可以通过 env->CallVoidMethod(javaClass, javaMethod); 从 C 端很好地触发我的 Java 端接收函数如下所示:

public static void MakeADialog() {  
     Log.w("title", "MakeADialog fired!");
}

这是一个单独的类(不是ActivityRunnable)。以上工作正常,我可以看到我的 MakeADialg 日志消息。但是,当我尝试创建一个实际的对话框时,我会遇到很多崩溃。当我从 C 端调用到 Java 端时,我可以说我并没有在运行什么“线程”。当我尝试创建新线程/对话框时,我似乎遇到了麻烦。

我在这里尝试了很多关于创建 Runnable、线程等的建议 - 但它们似乎总是给我可怕的“无法在未调用 Looper.prepare() 的线程内创建处理程序”或该视图的父级为空。这些方法中的大多数都围绕将 Activity 和 Context 指针存储为静态并在我处于 MakeADialog 回调时通过 get 函数检索它们。

AlertDialog alertDialog = new AlertDialog.Builder(MyApp.GetMyContext()).create(); 其中 GetMyContext() 函数仅返回我在应用启动期间存储的主 Activity 创建线程的 this 指针。

有没有人弹出从他们的 NDK 端启动的对话框,或者可以向我指出一些相关文档,以帮助我了解如何从 NDK 回调创建新对话框?

提前致谢!

【问题讨论】:

    标签: java android opengl-es dialog android-ndk


    【解决方案1】:

    也许我们可以使用gist 中的示例来创建模态对话框。我怀疑你是从工作线程中调用它的,所以“他们似乎总是给我可怕的‘无法在未调用 Looper.prepare() 的线程内创建处理程序’,或者视图的父级为空。” (另见Can't create handler inside thread that has not called Looper.prepare())。

    基于官方示例Native Activitythe gist code的键码:

    1. 在 Java 方面,
    package ss.fang.brickgo;
    
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.app.NativeActivity;
    import android.content.DialogInterface;
    import android.content.pm.ApplicationInfo;
    import android.graphics.Color;
    import android.graphics.drawable.ColorDrawable;
    import android.os.Bundle;
    import android.os.Looper;
    import android.util.Log;
    import android.view.Window;
    import android.view.WindowManager;
    
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class GameActivity extends NativeActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        /**
         * This function will be called from C++ by name and signature (Ljava/lang/String;Z)I)
         *
         * @param message the message text to show
         * @param model   if true, it will block the current thread, otherwise, it acts like a modeless dialog.
         * @return return id of the button that was clicked for a model dialog, otherwise, 0.
         * @see #showAlertCallback
         * @see <a href="https://*.com/questions/11730001/create-a-message-dialog-in-android-via-ndk-callback/60611870#60611870">
         * Create a message dialog in Android via NDK callback</a>
         * @see <a href="https://*.com/questions/6120567/android-how-to-get-a-modal-dialog-or-similar-modal-behavior">
         * Android: How to get a modal dialog or similar modal behavior?</a>
         */
        public int showAlert(final String message, boolean model) {
            //https://*.com/questions/11411022/how-to-check-if-current-thread-is-not-main-thread
            if (Looper.myLooper() == Looper.getMainLooper() && model) {
                // Current Thread is UI Thread. Looper.getMainLooper().isCurrentThread()
                //android.os.NetworkOnMainThreadException
                throw new RuntimeException("Can't create a model dialog inside Main thread");
            }
            ApplicationInfo applicationInfo = getApplicationInfo();
            final CharSequence appName = getPackageManager().getApplicationLabel(applicationInfo);
            // Use a semaphore to create a modal dialog. Also, it's holden by the dialog's listener.
            final Semaphore semaphore = model ? new Semaphore(0, true) : null;
            // The button that was clicked (ex. BUTTON_POSITIVE) or the position of the item clicked
            final AtomicInteger buttonId = new AtomicInteger();
            this.runOnUiThread(new Runnable() {
                public void run() {
                    AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this, AlertDialog.THEME_HOLO_DARK);
                    builder.setTitle(appName);
                    builder.setMessage(message);
                    DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            buttonId.set(id);
                            if (null != semaphore)
                                semaphore.release();
                            else
                                showAlertCallback(id);
                            if (DialogInterface.BUTTON_POSITIVE == id) {
                                GameActivity.this.finish();
                            }
                        }
                    };
                    builder.setNegativeButton(android.R.string.cancel, listener);
                    builder.setPositiveButton(android.R.string.ok, listener);
                    builder.setCancelable(false);
                    AlertDialog dialog = builder.create();
                    dialog.show();
                }
            });
            if (null != semaphore)
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    Log.v("GameActivity", "ignored", e);
                }
            return buttonId.get();
        }
    
        /**
         * The callback for showAlert when it acts like a modeless dialog
         *
         * @param id the button that was clicked
         */
        public native void showAlertCallback(int id);
    
        /**
         * @see <a href="https://*.com/questions/13822842/dialogfragment-with-clear-background-not-dimmed">
         * DialogFragment with clear background (not dimmed)</a>
         */
        protected void showDialog() {
            Dialog dialog = new Dialog(this);
            dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
            dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
            // layout to display
            dialog.setContentView(R.layout.dialog_layout);
    
            // set color transpartent
            dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    
            dialog.show();
        }
    }
    
    1. 在本机 (C++11) 方面,
    /** @param onClickListener MUST be NULL because the model dialog is not implemented. */
    typedef void *(OnClickListener)(int id);
    
    jint showAlert(struct android_app *state, const char *message, bool model = false);
    
    /** Process the next input event. */
    static int32_t engine_handle_input(struct android_app *app, AInputEvent *event) {
        auto *engine = (struct engine *) app->userData;
        auto type = AInputEvent_getType(event);
        if (AINPUT_EVENT_TYPE_MOTION == type) {
            engine->animating = 1;
            engine->state.x = AMotionEvent_getX(event, 0);
            engine->state.y = AMotionEvent_getY(event, 0);
            return 1;
        } else if (AINPUT_EVENT_TYPE_KEY == type) {
            auto action = AKeyEvent_getAction(event);
            if (AKEY_EVENT_ACTION_DOWN == action && AKEYCODE_BACK == AKeyEvent_getKeyCode(event)) {
                //https://*.com/questions/15913080/crash-when-closing-soft-keyboard-while-using-native-activity
                // skip predispatch (all it does is send to the IME)
                //if (!AInputQueue_preDispatchEvent(app->inputQueue, event))
                //int32_t handled = 0;
                //if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
                //AInputQueue_finishEvent(app->inputQueue, event, handled);
                LOGI("Before showAlert in modal behavior, it blocks until the dialog dismisses.");
                showAlert(app, "Go Back?", true);
                LOGI("After showAlert in modal behavior, the dialog should have been dismissed.");
                return 1;
            }
        }
        return 0; //not handled
    }
    
    /** @return the id of the button clicked if model is true, or 0 */
    jint showAlert(struct android_app *state, const char *message, bool model /* = false */) {
        JNIEnv *jni = NULL;
        state->activity->vm->AttachCurrentThread(&jni, NULL);
    
        jclass clazz = jni->GetObjectClass(state->activity->clazz);
    
        // Get the ID of the method we want to call
        // This must match the name and signature from the Java side Signature has to match java
        // implementation (second string hints a java string parameter)
        jmethodID methodID = jni->GetMethodID(clazz, "showAlert", "(Ljava/lang/String;Z)I");
    
        // Strings passed to the function need to be converted to a java string object
        jstring jmessage = jni->NewStringUTF(message);
    
        jint result = jni->CallIntMethod(state->activity->clazz, methodID, jmessage, model);
    
        // Remember to clean up passed values
        jni->DeleteLocalRef(jmessage);
    
        state->activity->vm->DetachCurrentThread();
    
        return result;
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_ss_fang_brickgo_GameActivity_showAlertCallback(JNIEnv *env, jobject thiz, jint id) {
        LOGI("showAlertCallback %d", id);
    }
    

    【讨论】: