2

Java 側で OpenGL コンテキストを設定した Android アプリがあり、NDK/C++ 側から描画を送信しています。これはすべてうまくいくようです。

C++ 側でダイアログをポップアップできるようにしたい。MakeADialog私はJava側の受信関数を介してC側から問題なく起動されるJava関数を実装しました。これはenv->CallVoidMethod(javaClass, javaMethod);次のようになります。

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

これは別のクラスにあります (ActivityまたはではありませんRunnable)。上記は問題なく動作し、MakeADialg ログ メッセージを確認できます。ただし、実際のダイアログ ボックスを作成しようとすると、多くのクラッシュが発生します。C 側から Java 側に呼び出したときに、実行中の「スレッド」を認識していないことがわかります。新しいスレッド/ダイアログを作成しようとすると、問題が発生しているようです。

Runnable、Thread などの作成について、ここで多くの提案を試みましたが、常に、「Looper.prepare() を呼び出していないスレッド内でハンドラーを作成できません」という恐ろしいメッセージが表示されるようです。ビューでは null です。これらのメソッドのほとんどは、Activity ポインターと Context ポインターを静的として格納し、MakeADialog コールバックにいるときに get 関数を介してそれらを取得することを中心に展開します。

AlertDialog alertDialog = new AlertDialog.Builder(MyApp.GetMyContext()).create();ここで、GetMyContext() 関数は、thisアプリの起動時に保存したメイン アクティビティ作成スレッドのポインターを返すだけです。

NDK 側から起動されたダイアログをポップアップした人はいますか、または NDK コールバックから新しいダイアログを作成する方法を理解するのに役立つ関連ドキュメントを教えてくれますか?

前もって感謝します!

4

1 に答える 1

1

gistの例を使用して、モーダル ダイアログを作成できます。あなたはワーカースレッドからそれを呼び出したのではないかと疑ったので、「彼らはいつも私に恐ろしい「呼び出されていないスレッド内でハンドラーを作成できないLooper.prepare()」またはビューの親がnullであることを示しているようです。( Looper.prepare() を呼び出していないスレッド内でハンドラーを作成できないも参照してください)。

公式のネイティブ アクティビティの例と要点コードに基づくキーコード:

  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://stackoverflow.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://stackoverflow.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://stackoverflow.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://stackoverflow.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://stackoverflow.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);
}
于 2020-03-10T05:12:28.210 に答える