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.
This commit is contained in:
Julian Winkler
2023-08-08 10:16:17 +02:00
parent c6c4e8b3a2
commit 4491de7f63
7 changed files with 80 additions and 29 deletions

View File

@@ -5,6 +5,10 @@
#include <stdint.h>
#include <stdlib.h>
#include <glib.h>
#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);
}

View File

@@ -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);
/*

View File

@@ -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))

View File

@@ -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

View File

@@ -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<? extends Activity> cls = Class.forName(className).asSubclass(Activity.class);
Constructor<? extends Activity> 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;
}

View File

@@ -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.

View File

@@ -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);