我正在开发一个自定义 Gluon Attach 插件,并且在将 Java
Runnable
对象从 Java 传递到 JNI 时遇到问题。尽管查看了 Gluon Attach 插件源代码(不幸的是,在这种情况下没有多大帮助),但我无法在本机端成功调用 Runnable
对象。
这是自定义 Gluon Attach 插件中
AndroidAlertDialogService
的自定义实现。我想传递一个 Runnable
对象作为本机方法 showSaveAlert3
的最后一个参数。
当本机代码尝试调用
Runnable
时,我一直遇到此错误:
JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug):
jobject is an invalid JNI transition frame reference: 0x8000000000000008
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);
}
#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);
}
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();
}
});
}
}
NewGlobalRef
方法之前,使用 Runnable
创建对 run
对象的持久引用。dalvikEnv
)。Runnable
这样的复杂 Java 对象。尽管进行了这些尝试,我仍无法调用
Runnable
的 run()
方法。当在 JNI 上下文中使用时,jobject
引用似乎变得无效,从而导致上面提到的错误。
Runnable
对象传递给 JNI 并调用其 run()
方法的正确方法是什么?Runnable
对象的具体步骤吗?NewGlobalRef
、Detach
等)有关?面临的问题源于 GluonFX 工具中使用的双 JVM 设置,该工具同时运行 GraalVM(用于 JavaFX)和 Android/Java JVM(用于本机 Android 交互)。不幸的是,这种架构分离对如何在两个 JVM 之间共享对象施加了严格的限制。
Runnable
)。jobject
)在其所属 JVM 内存空间之外无效。Runnable
并调用其 run
方法失败,因为 Runnable
的 jobject
在第二个 JVM 中无效。NewGlobalRef
无法解决此问题,因为引用仍然绑定到原始 JVM。不要传递
Runnable
,而是传递编码为原始类型的简单回调,例如表示操作或状态的整数。
StorageService
和 DisplayService
这样的插件,它们有效地使用原语或简单数组进行跨虚拟机数据交换。GluonFX 框架对两个 JVM 的依赖本质上限制了直接传递复杂 Java 对象(如
Runnable
)的能力。相反,使用原始类型或轻量级序列化格式来弥补差距。
我通过简单地返回布尔值并在那里运行可运行代码来管理它!
AndroidAlertDialogService.java
@Override
public boolean showConfirmationAlert(String title, String content, Runnable r) {
boolean result = showConfirmationAlert1(title, content);
if (result) r.run();
return result;
}
alertdialog.c
JNIEXPORT jboolean JNICALL Java_com_gluonhq_attachextended_alertdialog_impl_AndroidAlertDialogService_showSaveAlert3
(JNIEnv *env, jclass jClass, jstring jtitle, jstring jcontent) {
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);
jboolean result = (*dalvikEnv)->CallBooleanMethod(dalvikEnv, jDalvikAlertDialogService, jAlertDialogServiceShowSaveAlert3Method, jTitleString, jContentString);
DETACH_DALVIK();
(*env)->ReleaseStringUTFChars(env, jtitle, titleChars);
(*env)->ReleaseStringUTFChars(env, jcontent, contentChars);
return result;
}
DalvikAlertDialogService.java
public boolean showSaveAlert3(String title, String content) {
AtomicReference<Boolean> isOk = new AtomicReference<>(false);
CountDownLatch latch = new CountDownLatch(1);
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();
isOk.set(true);
latch.countDown();
});
AlertDialog alert = dialog.create();
alert.show();
} else {
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return isOk.get();
}
参考@josé-pereda:https://github.com/gluonhq/attach/issues/264#issuecomment-892984783
参考botje:
您有两个 JVM,每个 JVM 都管理自己的内存。根本不可能使用其他 JVM 中的 jobject。