prevent reference cycles between Java and native objects

This commit is contained in:
Julian Winkler
2024-07-26 21:47:08 +02:00
parent 45801d8f17
commit e3c0931714
30 changed files with 257 additions and 181 deletions

View File

@@ -17,13 +17,19 @@ static void wrapper_widget_init (WrapperWidget *wrapper_widget)
static void wrapper_widget_dispose(GObject *wrapper_widget)
{
gtk_widget_unparent(gtk_widget_get_first_child(GTK_WIDGET(wrapper_widget)));
GtkWidget *widget = GTK_WIDGET(wrapper_widget);
GtkWidget *child = gtk_widget_get_first_child(widget);
while (child) {
GtkWidget *_child = gtk_widget_get_next_sibling(child);
gtk_widget_unparent(child);
child = _child;
}
WrapperWidget *wrapper = WRAPPER_WIDGET(wrapper_widget);
if (wrapper->jvm) {
JNIEnv *env;
(*wrapper->jvm)->GetEnv(wrapper->jvm, (void**)&env, JNI_VERSION_1_6);
if (wrapper->jobj)
_UNREF(wrapper->jobj);
_WEAK_UNREF(wrapper->jobj);
if (wrapper->canvas)
_UNREF(wrapper->canvas);
}
@@ -179,7 +185,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
wrapper->jvm = jvm;
wrapper->jobj = _REF(jobj);
wrapper->jobj = _WEAK_REF(jobj);
jmethodID on_draw_method = _METHOD(_CLASS(jobj), "onDraw", "(Landroid/graphics/Canvas;)V");
jmethodID draw_method = _METHOD(_CLASS(jobj), "draw", "(Landroid/graphics/Canvas;)V");
if (on_draw_method != handle_cache.view.onDraw || draw_method != handle_cache.view.draw) {
@@ -192,7 +198,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job
jmethodID ontouchevent_method = _METHOD(_CLASS(jobj), "onTouchEvent", "(Landroid/view/MotionEvent;)Z");
if (ontouchevent_method != handle_cache.view.onTouchEvent) {
_setOnTouchListener(env, jobj, GTK_WIDGET(wrapper), NULL);
_setOnTouchListener(env, jobj, GTK_WIDGET(wrapper));
}
jmethodID computeScroll_method = _METHOD(_CLASS(jobj), "computeScroll", "()V");
@@ -204,7 +210,7 @@ void wrapper_widget_set_jobject(WrapperWidget *wrapper, JNIEnv *env, jobject job
if (performClick_method != handle_cache.view.performClick) {
GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
g_signal_connect(controller, "released", G_CALLBACK(on_click), _REF(jobj));
g_signal_connect(controller, "released", G_CALLBACK(on_click), wrapper->jobj);
gtk_widget_add_controller(wrapper->child, controller);
widget_set_needs_allocation(wrapper->child);
}

View File

@@ -22,6 +22,7 @@ struct _WrapperWidget
int real_width;
int real_height;
gboolean needs_allocation;
gboolean intercepting_touch;
};
struct _WrapperWidgetClass
@@ -37,6 +38,6 @@ void wrapper_widget_set_layout_params(WrapperWidget *wrapper, int width, int hei
void wrapper_widget_set_background(WrapperWidget *wrapper, GdkPaintable *paintable);
void wrapper_widget_consume_touch_events(WrapperWidget *wrapper);
void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget, jobject on_touch_listener);
void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget);
#endif

View File

@@ -107,7 +107,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_AbsListView_native_1constructor(JNIE
RangeListModel *model = g_object_new(range_list_model_get_type(), NULL);
GtkWidget *list_view = gtk_list_view_new(GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(model))), factory);
model->list_view = list_view;
model->jobject = _REF(this);
model->jobject = _WEAK_REF(this);
GtkWidget *scrolled_window = gtk_scrolled_window_new();
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(scrolled_window), TRUE);
gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(scrolled_window), TRUE);

View File

@@ -36,7 +36,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_AbsSpinner_native_1constructor(JNIEn
GtkWidget *dropdown = gtk_drop_down_new(G_LIST_MODEL(model), NULL);
gtk_drop_down_set_factory(GTK_DROP_DOWN(dropdown), factory);
model->list_view = dropdown;
model->jobject = _REF(this);
model->jobject = _WEAK_REF(this);
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), dropdown);
gtk_widget_set_name(dropdown, "Spinner");
return _INTPTR(dropdown);

View File

@@ -15,6 +15,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_Button_native_1constructor(JNIEnv *e
GtkWidget *label = gtk_button_new_with_label(text);
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), label);
wrapper_widget_consume_touch_events(WRAPPER_WIDGET(wrapper)); // Android button consumes touch events
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
return _INTPTR(label);
}
@@ -27,38 +28,20 @@ JNIEXPORT void JNICALL Java_android_widget_Button_native_1setText(JNIEnv *env, j
((*env)->ReleaseStringUTFChars(env, text, nativeText));
}
struct touch_callback_data {
JavaVM *jvm;
jobject this;
jobject listener;
jmethodID listener_method;
};
static void clicked_cb(GtkWidget *button, gpointer user_data) {
JNIEnv *env = get_jni_env();
WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(button));
static void clicked_cb(GtkWidget *button, struct touch_callback_data *d) {
JNIEnv *env;
(*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6);
(*env)->CallVoidMethod(env, d->listener, d->listener_method, d->this);
(*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performClick);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
}
JNIEXPORT void JNICALL Java_android_widget_Button_native_1setOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr, jobject on_click_listener)
JNIEXPORT void JNICALL Java_android_widget_Button_nativeSetOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr)
{
GtkWidget *button = GTK_WIDGET(_PTR(widget_ptr));
g_signal_handlers_disconnect_matched(button, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, clicked_cb, NULL);
if (!on_click_listener)
return;
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data));
callback_data->jvm = jvm;
callback_data->this = _REF(this);
callback_data->listener = _REF(on_click_listener);
callback_data->listener_method = _METHOD(_CLASS(on_click_listener), "onClick", "(Landroid/view/View;)V");
g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), callback_data);
g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), NULL);
}

View File

@@ -89,7 +89,7 @@ JNIEXPORT void JNICALL Java_android_widget_EditText_native_1setOnEditorActionLis
return;
struct changed_callback_data *callback_data = malloc(sizeof(struct changed_callback_data));
callback_data->this = _REF(this);
callback_data->this = _WEAK_REF(this);
callback_data->listener = _REF(listener);
callback_data->listener_method = _METHOD(_CLASS(listener), "onEditorAction", "(Landroid/widget/TextView;ILandroid/view/KeyEvent;)Z");

View File

@@ -26,41 +26,23 @@ JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setPixbuf(JNIEnv
Java_android_widget_ImageButton_native_1setDrawable(env, this, widget_ptr, _INTPTR(paintable));
}
struct touch_callback_data {
JavaVM *jvm;
jobject this;
jobject listener;
jmethodID listener_method;
};
static void clicked_cb(GtkWidget *button, struct touch_callback_data *d) {
static void clicked_cb(GtkWidget *button, gpointer user_data) {
printf("clicked_cb\n");
JNIEnv *env;
(*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6);
JNIEnv *env = get_jni_env();
WrapperWidget *wrapper = WRAPPER_WIDGET(gtk_widget_get_parent(button));
(*env)->CallVoidMethod(env, d->listener, d->listener_method, d->this);
(*env)->CallBooleanMethod(env, wrapper->jobj, handle_cache.view.performClick);
if((*env)->ExceptionCheck(env))
(*env)->ExceptionDescribe(env);
}
JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr, jobject on_click_listener)
JNIEXPORT void JNICALL Java_android_widget_ImageButton_nativeSetOnClickListener(JNIEnv *env, jobject this, jlong widget_ptr)
{
GtkWidget *button = GTK_WIDGET(_PTR(widget_ptr));
g_signal_handlers_disconnect_matched(button, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, clicked_cb, NULL);
if (!on_click_listener)
return;
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
struct touch_callback_data *callback_data = malloc(sizeof(struct touch_callback_data));
callback_data->jvm = jvm;
callback_data->this = _REF(this);
callback_data->listener = _REF(on_click_listener);
callback_data->listener_method = _METHOD(_CLASS(on_click_listener), "onClick", "(Landroid/view/View;)V");
g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), callback_data);
g_signal_connect(button, "clicked", G_CALLBACK(clicked_cb), NULL);
}
JNIEXPORT void JNICALL Java_android_widget_ImageButton_native_1setDrawable(JNIEnv *env, jobject this, jlong widget_ptr, jlong paintable_ptr)

View File

@@ -30,6 +30,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_TextView_native_1constructor(JNIEnv
gtk_widget_set_hexpand(label, TRUE);
gtk_box_append(GTK_BOX(box), label);
wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), box);
wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
PangoAttrList* pango_attrs = pango_attr_list_new();
pango_attr_list_insert(pango_attrs, pango_attr_font_features_new("tnum"));