From 4491de7f63719891e955436e486be09c3d19c27a Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Tue, 8 Aug 2023 10:16:17 +0200 Subject: [PATCH] MessageQueue: integrate with glib main loop Adds a special treatment for the main Looper to not block in java code, but instead return to glib managed thread loop. Timeouts in the mainloop are now handled using g_timeout_add_full(). Also defer Activity construction, so that every thing is set up properly when the constructor runs. --- src/api-impl-jni/android_os_MessageQueue.c | 57 ++++++++++++++++++- .../android_os_MessageQueue.h | 4 +- src/api-impl-jni/util.c | 7 +-- src/api-impl-jni/util.h | 5 +- src/api-impl/android/app/Activity.java | 16 ++---- src/api-impl/android/os/MessageQueue.java | 6 +- src/main-executable/main.c | 14 +++-- 7 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/api-impl-jni/android_os_MessageQueue.c b/src/api-impl-jni/android_os_MessageQueue.c index b48e47e3..1be0e383 100644 --- a/src/api-impl-jni/android_os_MessageQueue.c +++ b/src/api-impl-jni/android_os_MessageQueue.c @@ -5,6 +5,10 @@ #include #include +#include + +#include "generated_headers/android_os_MessageQueue.h" + /* TODO put these in a header */ typedef void ALooper; ALooper * ALooper_prepare(void); @@ -15,14 +19,30 @@ int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outDa struct native_message_queue { ALooper *looper; bool in_callback; + bool is_main_thread; }; +static GThread *main_thread_id; + +void prepare_main_looper(JNIEnv* env) { + main_thread_id = g_thread_self(); + + jclass class = (*env)->FindClass(env, "android/os/Looper"); + (*env)->CallStaticVoidMethod(env, class, _STATIC_METHOD(class, "prepareMainLooper", "()V")); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + (*env)->CallStaticVoidMethod(env, class, _STATIC_METHOD(class, "loop", "()V")); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); +} + JNIEXPORT jlong JNICALL Java_android_os_MessageQueue_nativeInit(JNIEnv *env, jclass this) { struct native_message_queue *message_queue = malloc(sizeof(struct native_message_queue)); message_queue->in_callback = false; message_queue->looper = ALooper_prepare(); + message_queue->is_main_thread = g_thread_self() == main_thread_id; return _INTPTR(message_queue); } @@ -34,10 +54,37 @@ JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeDestroy(JNIEnv *env, j free(message_queue); } -JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce(JNIEnv *env, jclass this, jlong ptr, jint timeout_millis) +/** + * callback to execute java handlers on glib managed thread loops + */ +static gboolean glib_context_callback(gpointer user_data) { + JavaVM *jvm = user_data; + JNIEnv *env; + (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6); + + jclass class = (*env)->FindClass(env, "android/os/Looper"); + (*env)->CallStaticVoidMethod(env, class, _STATIC_METHOD(class, "loop", "()V")); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + return FALSE; // cancel timer +} + +JNIEXPORT jboolean JNICALL Java_android_os_MessageQueue_nativePollOnce(JNIEnv *env, jclass this, jlong ptr, jint timeout_millis) { struct native_message_queue *message_queue = _PTR(ptr); + if (message_queue->is_main_thread) { // thread loop is managed by glib + JavaVM *jvm; + (*env)->GetJavaVM(env, &jvm); + if (timeout_millis) { + g_timeout_add_full(0, timeout_millis, glib_context_callback, jvm, NULL); + return true; // indicate that java side should return to block in glib managed loop + } else { + return false; + } + } + // printf("Java_android_os_MessageQueue_nativePollOnce: entry (timeout: %d)\n", timeout_millis); message_queue->in_callback = true; ALooper_pollOnce(timeout_millis, NULL, NULL, NULL); @@ -45,12 +92,20 @@ JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce(JNIEnv *env, // printf("Java_android_os_MessageQueue_nativePollOnce: exit\n"); /* TODO: what's with the exception stuff */ + return false; } JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeWake(JNIEnv *env, jclass this, jlong ptr) { struct native_message_queue *message_queue = _PTR(ptr); + if (message_queue->is_main_thread) { // thread loop is managed by glib + JavaVM *jvm; + (*env)->GetJavaVM(env, &jvm); + g_idle_add_full(0, glib_context_callback, jvm, NULL); + return; + } + ALooper_wake(message_queue->looper); } diff --git a/src/api-impl-jni/generated_headers/android_os_MessageQueue.h b/src/api-impl-jni/generated_headers/android_os_MessageQueue.h index 18a7ea1f..c0bec95f 100644 --- a/src/api-impl-jni/generated_headers/android_os_MessageQueue.h +++ b/src/api-impl-jni/generated_headers/android_os_MessageQueue.h @@ -26,9 +26,9 @@ JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeDestroy /* * Class: android_os_MessageQueue * Method: nativePollOnce - * Signature: (JI)V + * Signature: (JI)Z */ -JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce +JNIEXPORT jboolean JNICALL Java_android_os_MessageQueue_nativePollOnce (JNIEnv *, jclass, jlong, jint); /* diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index f79817d3..52e34f90 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -39,15 +39,11 @@ void _gdb_get_java_stack_trace(void) (*env)->ExceptionDescribe(env); } -void set_up_handle_cache(JNIEnv *env, char *apk_main_activity_class) +void set_up_handle_cache(JNIEnv *env) { (*env)->GetJavaVM(env, &jvm); handle_cache.apk_main_activity.class = _REF((*env)->FindClass(env, "android/app/Activity")); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); - jmethodID createMainActivity = _STATIC_METHOD(handle_cache.apk_main_activity.class, "createMainActivity", "(Ljava/lang/String;)Landroid/app/Activity;"); - handle_cache.apk_main_activity.object = _REF((*env)->CallStaticObjectMethod(env, handle_cache.apk_main_activity.class, createMainActivity, _JSTRING(apk_main_activity_class))); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); handle_cache.apk_main_activity.onCreate = _METHOD(handle_cache.apk_main_activity.class, "onCreate", "(Landroid/os/Bundle;)V"); @@ -55,7 +51,6 @@ void set_up_handle_cache(JNIEnv *env, char *apk_main_activity_class) handle_cache.apk_main_activity.onWindowFocusChanged = _METHOD(handle_cache.apk_main_activity.class, "onWindowFocusChanged", "(Z)V"); handle_cache.apk_main_activity.onResume = _METHOD(handle_cache.apk_main_activity.class, "onResume", "()V"); handle_cache.apk_main_activity.onDestroy = _METHOD(handle_cache.apk_main_activity.class, "onDestroy", "()V"); - handle_cache.apk_main_activity.set_window = _METHOD((*env)->FindClass(env, "android/app/Activity"), "set_window", "(J)V"); handle_cache.attribute_set.class = _REF((*env)->FindClass(env, "android/util/AttributeSet")); if((*env)->ExceptionCheck(env)) diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index 59401e91..45eba1dd 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -14,7 +14,6 @@ struct handle_cache { jmethodID onResume; jmethodID onWindowFocusChanged; jmethodID onDestroy; - jmethodID set_window; } apk_main_activity; struct { jclass class; @@ -81,7 +80,9 @@ extern struct handle_cache handle_cache; const char * attribute_set_get_string(JNIEnv *env, jobject attrs, char *attribute, char *schema); int attribute_set_get_int(JNIEnv *env, jobject attrs, char *attribute, char *schema, int default_value); -void set_up_handle_cache(JNIEnv *env, char *apk_main_activity_class); +void set_up_handle_cache(JNIEnv *env); void extract_from_apk(const char *path, const char *target); +void prepare_main_looper(JNIEnv* env); + #endif diff --git a/src/api-impl/android/app/Activity.java b/src/api-impl/android/app/Activity.java index 9837c592..c1ff8fe9 100644 --- a/src/api-impl/android/app/Activity.java +++ b/src/api-impl/android/app/Activity.java @@ -36,7 +36,7 @@ public class Activity extends Context { * @param className class name of activity or null * @return instance of main activity class */ - private static Activity createMainActivity(String className) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { + private static Activity createMainActivity(String className, long native_window) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { if (className == null) { InputStream inStream = ClassLoader.getSystemClassLoader().getResourceAsStream("AndroidManifest.xml"); AndroidManifestBlock block = AndroidManifestBlock.load(inStream); @@ -49,11 +49,9 @@ public class Activity extends Context { } Class cls = Class.forName(className).asSubclass(Activity.class); Constructor constructor = cls.getConstructor(); - return constructor.newInstance(); - } - - protected void set_window(long native_window) { - window.native_window = native_window; + Activity activity = constructor.newInstance(); + activity.window.native_window = native_window; + return activity; } public Activity() { @@ -104,12 +102,6 @@ public class Activity extends Context { protected void onCreate(Bundle savedInstanceState) { System.out.println("- onCreate - yay!"); - /* TODO: this probably belongs elsewhere, but this is our entry point for better or worse */ - Looper looper = Looper.myLooper(); - if(looper == null) { - Looper.prepareMainLooper(); - } - return; } diff --git a/src/api-impl/android/os/MessageQueue.java b/src/api-impl/android/os/MessageQueue.java index e80acde2..b7a57d6b 100644 --- a/src/api-impl/android/os/MessageQueue.java +++ b/src/api-impl/android/os/MessageQueue.java @@ -50,7 +50,7 @@ public final class MessageQueue { private native static long nativeInit(); private native static void nativeDestroy(long ptr); - private native static void nativePollOnce(long ptr, int timeoutMillis); + private native static boolean nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr); private native static boolean nativeIsIdling(long ptr); @@ -134,7 +134,9 @@ public final class MessageQueue { // We can assume mPtr != 0 because the loop is obviously still running. // The looper will not call this method after the loop quits. - nativePollOnce(mPtr, nextPollTimeoutMillis); + if (nativePollOnce(mPtr, nextPollTimeoutMillis)) { + return null; // thread is managed by glib, so return instead of blocking + } synchronized (this) { // Try to retrieve the next message. Return if found. diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 1d9bb4b0..b07218dc 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -315,7 +315,7 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h free(app_lib_dir); - set_up_handle_cache(env, d->apk_main_activity_class); + set_up_handle_cache(env); jclass display_class = (*env)->FindClass(env, "android/view/Display"); _SET_STATIC_INT_FIELD(display_class, "window_width", d->window_width); @@ -329,9 +329,6 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h FIXME__HEIGHT = d->window_height; window = gtk_application_window_new(app); - (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.set_window, _INTPTR(window)); - if((*env)->ExceptionCheck(env)) - (*env)->ExceptionDescribe(env); gtk_window_set_title(GTK_WINDOW(window), "com.example.demo_application"); gtk_window_set_default_size(GTK_WINDOW(window), d->window_width, d->window_height); @@ -364,6 +361,15 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h /* extract native libraries from apk*/ extract_from_apk("lib/" NATIVE_ARCH "/", "lib/"); + prepare_main_looper(env); + + // construct main Activity + handle_cache.apk_main_activity.object = _REF((*env)->CallStaticObjectMethod(env, handle_cache.apk_main_activity.class, + _STATIC_METHOD(handle_cache.apk_main_activity.class, "createMainActivity", "(Ljava/lang/String;J)Landroid/app/Activity;"), + _JSTRING(d->apk_main_activity_class), _INTPTR(window))); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + /* -- run the main activity's onCreate -- */ (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onCreate, NULL);