I am developing a custom Gluon Attach plugin and encountering issues with passing a Java Runnable
object from Java to JNI. Despite reviewing the Gluon Attach plugin source code (which unfortunately didn’t help much in this case), I’m unable to successfully invoke the Runnable
object on the native side.
Context
This is a custom implementation of the AndroidAlertDialogService
within a custom Gluon Attach plugin. I want to pass a Runnable
object as the last parameter of the native method showSaveAlert3
.
When the native code attempts to invoke the Runnable
, I consistently run into this error:
JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug):
jobject is an invalid JNI transition frame reference: 0x8000000000000008
Code Overview
Java Class 1 (AndroidAlertDialogService)
package com.gluonhq.attachextended.alertdialog.impl;
import com.gluonhq.attachextended.alertdialog.AlertDialogService;
public class AndroidAlertDialogService implements AlertDialogService {
static {
System.loadLibrary("alertdialog");
}
@Override
public void showSaveAlert(String title, String content, Runnable r) {
showSaveAlert3(title, content, r);
}
private native static void showSaveAlert3(String title, String content, Runnable r);
}
JNI Code (alertdialog.c)
#include "util.h"
static jclass jAlertDialogServiceClass;
static jobject jDalvikAlertDialogService;
static jmethodID jAlertDialogServiceShowSaveAlert3Method;
static void initializeAlertDialogDalvikHandles() {
jAlertDialogServiceClass = GET_REGISTER_DALVIK_CLASS(jAlertDialogServiceClass, "com/gluonhq/helloandroid/DalvikAlertDialogService");
ATTACH_DALVIK();
jmethodID jAlertDialogServiceInitMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, jAlertDialogServiceClass, "<init>", "(Landroid/app/Activity;)V");
jAlertDialogServiceShowSaveAlert3Method = (*dalvikEnv)->GetMethodID(dalvikEnv, jAlertDialogServiceClass, "showSaveAlert3", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Runnable;)V");
jobject jActivity = substrateGetActivity();
jobject jObj = (*dalvikEnv)->NewObject(dalvikEnv, jAlertDialogServiceClass, jAlertDialogServiceInitMethod, jActivity);
jDalvikAlertDialogService = (*dalvikEnv)->NewGlobalRef(dalvikEnv, jObj);
DETACH_DALVIK();
}
//////////////////////////
// From Graal to native //
//////////////////////////
JNIEXPORT jint JNICALL
JNI_OnLoad_alertdialog(JavaVM *vm, void *reserved)
{
JNIEnv* graalEnv;
ATTACH_LOG_INFO("JNI_OnLoad_alertdialog called");
#ifdef JNI_VERSION_1_8
if ((*vm)->GetEnv(vm, (void **)&graalEnv, JNI_VERSION_1_8) != JNI_OK) {
ATTACH_LOG_WARNING("Error initializing native AlertDialog from OnLoad");
return JNI_FALSE;
}
ATTACH_LOG_FINE("[AlertDialog Service] Initializing native AlertDialog from OnLoad");
initializeAlertDialogDalvikHandles();
return JNI_VERSION_1_8;
#else
#error Error: Java 8+ SDK is required to compile Attach
#endif
}
// from Java to Android
JNIEXPORT void JNICALL Java_com_gluonhq_attachextended_alertdialog_impl_AndroidAlertDialogService_showSaveAlert3
(JNIEnv *env, jclass jClass, jstring jtitle, jstring jcontent, jobject jr) {
const char *titleChars = (*env)->GetStringUTFChars(env, jtitle, NULL);
const char *contentChars = (*env)->GetStringUTFChars(env, jcontent, NULL);
ATTACH_DALVIK();
jstring jTitleString = (*dalvikEnv)->NewStringUTF(dalvikEnv, titleChars);
jstring jContentString = (*dalvikEnv)->NewStringUTF(dalvikEnv, contentChars);
jobject globalRunnable = (*dalvikEnv)->NewGlobalRef(dalvikEnv, jr);
jclass rClass = (*dalvikEnv)->GetObjectClass(dalvikEnv, globalRunnable);
jmethodID midMyCustomMethod = (*dalvikEnv)->GetMethodID(dalvikEnv, rClass, "run", "()V");
(*dalvikEnv)->CallVoidMethod(dalvikEnv, jDalvikAlertDialogService, jAlertDialogServiceShowSaveAlert3Method, jTitleString, jContentString, midMyCustomMethod);
DETACH_DALVIK();
(*env)->ReleaseStringUTFChars(env, jtitle, titleChars);
(*env)->ReleaseStringUTFChars(env, jcontent, contentChars);
}
Java Class 2 (DalvikAlertDialogService)
package com.gluonhq.helloandroid;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.app.Activity;
public class DalvikAlertDialogService {
private final Activity activity;
public DalvikAlertDialogService(Activity activity) {
this.activity = activity;
}
public void showSaveAlert3(String title, String content, Runnable r) {
activity.runOnUiThread(() -> {
if (!activity.isFinishing()) {
AlertDialog.Builder dialog = new AlertDialog.Builder(activity);
dialog.setCancelable(false);
dialog.setTitle(title);
dialog.setMessage(content);
dialog.setPositiveButton("Ok", (DialogInterface dialog1, int id) -> {
dialog1.dismiss();
r.run();
});
AlertDialog alert = dialog.create();
alert.show();
}
});
}
}
What I’ve Tried
- Using
NewGlobalRef
to create a persistent reference to theRunnable
object before invoking itsrun
method. - Checking the validity of the Dalvik environment (
dalvikEnv
). - Exploring the Gluon Attach source code for existing plugins such as “DisplayService” and “StorageService.” Unfortunately, those examples primarily focus on passing strings, primitives, or arrays, not complex Java objects like
Runnable
.
Problem Statement
Despite these attempts, I am unable to invoke the Runnable
‘s run()
method. The jobject
reference seems to become invalid when used in the JNI context, resulting in the error mentioned above.
Questions
- What is the correct way to pass a Java
Runnable
object to JNI and call itsrun()
method in this context? - Are there any specific steps for handling
Runnable
objects in Gluon Attach that I might be missing? - Could the issue be related to improper reference management (
NewGlobalRef
,Detach
, etc.) in the Dalvik environment?