JNI 问题:在自定义 Gluon Attach 插件中将 Java Runnable 对象传递给 JNI

问题描述 投票:0回答:1

我正在开发一个自定义 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  

代码概述

Java 类 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 代码(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 类 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();
            }
        });
    }
}

我尝试过的事情

  1. 在调用
    NewGlobalRef
    方法之前,使用
    Runnable
    创建对
    run
    对象的持久引用。
  2. 检查Dalvik环境的有效性(
    dalvikEnv
    )。
  3. 探索现有插件(例如“DisplayService”和“StorageService”)的Gluon Attach 源代码。不幸的是,这些示例主要关注传递字符串、基元或数组,而不是像
    Runnable
    这样的复杂 Java 对象。

问题陈述

尽管进行了这些尝试,我仍无法调用

Runnable
run()
方法。当在 JNI 上下文中使用时,
jobject
引用似乎变得无效,从而导致上面提到的错误。


问题

  1. 在此上下文中将 Java
    Runnable
    对象传递给 JNI 并调用其
    run()
    方法的正确方法是什么?
  2. 我可能缺少处理 Gluon Attach 中的
    Runnable
    对象的具体步骤吗?
  3. 该问题是否与 Dalvik 环境中的引用管理不当(
    NewGlobalRef
    Detach
    等)有关?
java java-native-interface dalvik gluonfx
1个回答
0
投票

面临的问题源于 GluonFX 工具中使用的双 JVM 设置,该工具同时运行 GraalVM(用于 JavaFX)和 Android/Java JVM(用于本机 Android 交互)。不幸的是,这种架构分离对如何在两个 JVM 之间共享对象施加了严格的限制。


要点:

  1. 双 JVM 限制:
    • 每个 JVM 管理自己的内存和对象生命周期。
    • 由于内存和引用处理不兼容,不直接支持在这些 JVM 之间传递复杂的 Java 对象(如
      Runnable
      )。
  2. 为什么简单类型有效:
    • 可以传递字符串、基元或数组,因为它们要么转换为本机表示形式,要么跨 JVM 复制。
    • 对象引用(如
      jobject
      )在其所属 JVM 内存空间之外无效。
  3. 可运行调用挑战:
    • 尝试传递
      Runnable
      并调用其
      run
      方法失败,因为
      Runnable
      jobject
      在第二个 JVM 中无效。
    • 使用
      NewGlobalRef
      无法解决此问题,因为引用仍然绑定到原始 JVM。

解决方案:

1。避免直接传递复杂对象:

不要传递

Runnable
,而是传递编码为原始类型的简单回调,例如表示操作或状态的整数。

4。重新审视现有的胶子附加示例:

  • 研究像
    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。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.