返回「计算机、信息技术与工程」

Unity跨语言编程精华:CSharp与Java、Native代码的链式调用实践

本文深入剖析Unity中C 与Java/Native代码互调技术,详解JNI动态绑定、AndroidStudio插件工程配置及P/Invoke内存管理机制,提供链式调用实战案例(C →Java→C++→Java)。涵盖JNIEnv线程绑定策略、JVM内存模型对跨语言调用的影响,帮助你打通Unity Android原生层开发之路。

更多
Markdown 结构化数据
本文目录 27 个章节

本文深入剖析Unity中C#与Java/Native代码互调技术,详解JNI动态绑定、AndroidStudio插件工程配置及P/Invoke内存管理机制,提供链式调用实战案例(C#→Java→C++→Java)。涵盖JNIEnv线程绑定策略、JVM内存模型对跨语言调用的影响,帮助你打通Unity-Android原生层开发之路。

Unity跨语言编程精华:CSharp与Java、Native代码的链式调用实践

  • JNI (Java Native Interface) 是一种编程框架,使得 Java 代码可以与使用其他编程语言(例如 C、C++、汇编语言)编写的应用程序和库进行交互。

Unity C#调用Java代码

1. Java 代码

package com.prin.myplugin;

import android.util.Log;

public class TestJavaClass {
    public static String myJavaMethod(String message) {
        Log.d("MyJavaClass", message);
        return "Response from Java: " + message;
    }

    public static int multiply(int a, int b) {
        return a * b;
    }
}

可以直接将Java代码放置在Unity工程的Android目录中: Assets/Plugins/Android/ 。 也可以在Android Studio中将Java代码打成Jar/aar包后,再拷贝到 Assets/Plugins/Android/ 中。

2. Unity C# 代码

在 Unity 项目中创建一个新的 C# 脚本文件(例如 TestCallJava.cs),并编写如下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestCallJava : MonoBehaviour
{
    public void CallJava()
    {
        // 获取 UnityPlayer 类
        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        // 获取当前活动
        AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        // 调用自定义 Java 类的方法
        AndroidJavaClass myJavaClass = new AndroidJavaClass("com.prin.myplugin.TestJavaClass");

        // 调用 Java 静态方法并获取返回值
        string response = myJavaClass.CallStatic<string>("myJavaMethod", "Hello from Unity!");
        Debug.Log(response);

        // 调用 Java 静态方法 Multiply
        int res = myJavaClass.CallStatic<int>("Multiply", 3, 4);
        Debug.Log("Multiply result: " + res);
    }
}

Unity C#调用Native代码

  • P/Invoke 声明:在 C# 中使用 [DllImport("library-name")] 属性来声明外部的 C 函数,library-name 是不带前缀 lib 和后缀 .so 的库名。
  • 参数传递: Marshal 通常用于处理非托管代码中的复杂数据类型,比如字符串、结构体、指针等,而基本数据类型的传递可以通过 P/Invoke 直接完成。
  • 内存管理:由于 Native 代码返回的字符串在堆上分配,需要在 C# 中调用相应的释放函数来释放内存,防止内存泄漏。

Native代码:

#include <cstring>
#include <string>

extern "C" {
    const char* stringFromNative(const char* input) {
        std::string response = "Response from Native: ";
        response += input;
        char* result = new char[response.size() + 1];
        std::strcpy(result, response.c_str());
        return result;
    }

    void freeNativeString(const char* str) {
        delete[] str;
    }

    int add(int a, int b) {
        return a + b;
    }
}

CMAKE配置:

cmake_minimum_required(VERSION 3.4.1)

add_library(experimental-native-lib SHARED experimental-native-lib.cpp)

find_library(log-lib log)

target_link_libraries(experimental-native-lib ${log-lib})

Android Studio中构建so代码后,放入Unity工程的 Assets/Plugins/Android/ 目录下。

C#代码:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class TestCallNative : MonoBehaviour
{
    // 使用 P/Invoke 声明 C++ 函数
    [DllImport("experimental-native-lib")]
    private static extern IntPtr stringFromNative(string input);

    [DllImport("experimental-native-lib")]
    private static extern void freeNativeString(IntPtr str);

    [DllImport("experimental-native-lib")]
    private static extern int add(int a, int b);

    public void CallNative()
    {
        Debug.Log("Call native----------------------------------------");

        IntPtr responsePtr = stringFromNative("Hello from Unity!");
        string response = Marshal.PtrToStringAnsi(responsePtr);
        Debug.Log(response);

        // 释放返回的指针
        freeNativeString(responsePtr);

        int c = add(3, 4);
        Debug.Log("native 3+4: " + c);
    }
}

Java与Native代码互相调用

知识储备

JVM 的底层实现

1. 线程和调度

JVM 在启动时会创建多个线程,包括主线程、垃圾收集线程、编译线程等。每个 Java 线程都对应一个操作系统级别的线程。操作系统负责线程的创建、调度和销毁。

  • Java 线程:在 Java 程序中,每个通过 new Thread() 创建的线程都是一个独立的线程,它们共享相同的内存空间。
  • 操作系统线程:Java 线程被映射为操作系统线程。操作系统负责管理这些线程,包括调度、上下文切换和优先级管理。

2. 内存管理

JVM 的内存区域主要包括:

  • 堆 (Heap):用于存储对象实例。垃圾收集器负责回收不再使用的对象。
  • 栈 (Stack):每个线程都有自己的栈空间,用于存储局部变量和方法调用信息。
  • 方法区 (Method Area):存储类信息、常量、静态变量等。
  • 本地方法栈 (Native Method Stack):用于支持本地方法执行。

3. JNI 的底层机制

JNI 是 Java 调用本地代码的接口,允许 Java 程序与本地应用程序和库进行交互。JNI 使得 Java 程序可以调用用 C/C++ 编写的代码,反之亦然。

JNIEnv 和 JavaVM 的底层机制

1. JNIEnv 的本质

JNIEnv 是一个指向函数表的指针,这些函数表定义了 JNI 提供的所有功能。JNIEnv 是线程私有的,每个线程都有一个独立的 JNIEnv 实例。

struct JNIEnv {
    const struct JNINativeInterface* functions;
};

struct JNINativeInterface {
    // 定义了所有的 JNI 函数
    jint (*GetVersion)(JNIEnv *env);
    jclass (*FindClass)(JNIEnv *env, const char *name);
    jmethodID (*GetMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    // 其他函数省略
};

2. JavaVM 的本质

JavaVM 是 JVM 实例的一个抽象表示,通常只有一个全局的 JavaVM 指针。JavaVM 提供了一些全局操作,比如创建和销毁 JVM,附加和分离线程等。

struct JavaVM {
    const struct JNIInvokeInterface* functions;
};

struct JNIInvokeInterface {
    jint (*DestroyJavaVM)(JavaVM *vm);
    jint (*AttachCurrentThread)(JavaVM *vm, JNIEnv **p_env, void *thr_args);
    jint (*DetachCurrentThread)(JavaVM *vm);
    // 其他函数省略
};

线程的附加和分离

Java 线程是由 JVM 管理的线程,用于并发执行任务。每个 Java 线程都有一个关联的 JNIEnv 指针,这是线程与 JVM 交互的接口。 Native 线程是由操作系统管理的线程,它们通常用来执行与平台相关的任务。Native 线程可以通过 JNI 与 JVM 进行交互。 JNIEnv 是一个指针,指向 JVM 提供的一组函数表,用于 Native 代码与 JVM 交互。每个 Java 线程都有一个关联的 JNIEnv,但 Native 线程默认情况下没有 JNIEnv。 当一个 Native 线程需要与 JVM 交互时,它需要首先附加到 JVM 上,以获得一个 JNIEnv。 当一个 Native 线程附加到 JVM 时,JVM 会为该线程分配一个新的 JNIEnv 结构,并将其存储在线程局部存储 (Thread Local Storage, TLS) 中。AttachCurrentThread 会执行以下操作:

  • 分配一个新的 JNIEnv 实例,并初始化其函数表。
  • JNIEnv 指针存储在当前线程的 TLS 中。
  • 将线程注册到 JVM 的线程管理系统中。
JNIEnv* env;
int getEnvStat = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
    if (gJvm->AttachCurrentThread(&env, NULL) != 0) {
        // 处理附加失败
    }
    needDetach = true;
}

Java代码调用Native代码

当 Java 调用 Native 方法时,JVM 会执行以下步骤:

  • 查找并加载包含 Native 方法实现的共享库。
  • 获取方法的本地函数指针。
  • 创建一个 Native 帧并推送到当前线程的调用栈。
  • 切换到 Native 模式,执行本地函数。
  • 当本地函数返回时,切换回 Java 模式,并弹出 Native 帧。
  1. 创建一个 Native C++ 库并定义一个带返回值的方法。
// native-lib.cpp
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_unityplugin_NativeLib_stringFromJNI(JNIEnv *env, jobject /* this */, jstring input) {
    const char *inputStr = env->GetStringUTFChars(input, 0); // JNI 提供的函数,用于将 `jstring` 类型的 `input` 转换为 C 风格的字符串 (`const char*`)。

    std::string hello = "Response from C++: ";
    hello += inputStr;
    env->ReleaseStringUTFChars(input, inputStr); //释放之前通过 `GetStringUTFChars` 获取的内存, 是必要的,防止内存泄漏.

    return env->NewStringUTF(hello.c_str());
}
  • env->GetStringUTFChars JNI 提供的函数,将 jstring 类型的 input 转换为 C 风格的字符串 (const char*)。
  • ReleaseStringUTFChars 释放之前通过 GetStringUTFChars 获取的内存, 防止内存泄漏.
  • hello.c_str() 返回 C++ 字符串对象 hello 的底层 C 风格字符串指针。
  • env->NewStringUTF 将 C 风格的字符串 (const char*) 转换为 jstring,以便返回给 Java 层。

UTF (Unicode Transformation Format) 是一种字符编码形式,用于将 Unicode 字符转换为字节序列。常见的 UTF 编码格式:UTF-8, UTF-16等. JNI 提供了函数将 jstring(Java 字符串)转换为 UTF-8 编码的 C 字符串,并且允许将 C 字符串转换为 jstring

  1. 在 Java 代码中调用 Native 方法并处理返回值。
// MainActivity.java
package com.example.unityplugin;

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    public native String stringFromJNI(String input);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.sample_text);
        String response = stringFromJNI("Hello from Java!");
        tv.setText(response);
    }
}

extern "C"

  • 含义:用来告诉编译器按 C 语言的函数命名规则(而不是 C++ 的命名规则)来生成符号。
  • 目的:防止 C++ 编译器对函数名进行名称改编(name mangling),使其能够与 Java 的 JNI 调用正确匹配。由于 JNI 是以 C 的方式调用方法,所以需要使用 extern "C"

JNIEXPORT`

  • 含义:一个宏,通常在 jni.h 中定义,用来修饰 JNI 函数,表示该函数会被导出,使 Java 虚拟机 (JVM) 能够访问到此函数。
  • 目的:使函数可以被 JVM 调用。根据具体的平台,JNIEXPORT 会展开为相应的导出修饰符,例如在 Windows 上它可能展开为 __declspec(dllexport)

JNICALL

  • 含义:一个宏,通常在 jni.h 中定义,用来修饰 JNI 函数,表示该函数使用的调用约定 (calling convention)。
  • 目的:保证函数使用正确的调用约定,使 JVM 能够正确地调用此函数。在 Windows 上,这通常展开为 __stdcall

jstring

  • 含义:这是 JNI 提供的一个类型,用于表示 Java 中的 String 类型。
  • 目的:用来表示从 Java 传递过来的字符串,或者返回一个 Java 字符串给 Java 层。

JNIEnv *env

  • 含义:JNI 环境指针,指向一个指针数组,包含了许多 JNI 函数指针。
  • 目的:通过这个指针可以调用 JNI 提供的各种函数,例如将 C++ 字符串转换为 Java 字符串等。

jobject /* this */

  • 含义:代表 Java 对象的引用。在非静态方法中,这个参数是必需的,指向调用这个 JNI 函数的 Java 对象。
  • 目的:在 C++ 代码中可以通过这个引用访问调用该方法的 Java 对象。对于静态方法,则为 jclass,表示类的引用。

Java_com_example_unityplugin_NativeLib_callJavaMethod

  • 含义:这是 JNI 函数的名称,按照 JNI 的命名规范,由 Java_ 前缀、包名、类名和方法名组成。
  • 目的:通过这种命名方式,JVM 能够根据 Java 代码中的 NativeLib.callJavaMethod 方法正确地找到对应的 JNI 实现。

Native代码调用Java代码

当 Native 代码调用 Java 方法时,执行以下步骤:

  • 使用 JNIEnv 查找 Java 类和方法 ID。
  • 准备方法调用参数并调用相应的 JNI 函数。
  • JNI 函数会创建一个 Java 帧并推送到当前线程的调用栈。
  • JVM 解释或编译执行该 Java 方法。
  • 当 Java 方法返回时,JNI 函数会弹出 Java 帧,并返回结果给 Native 代码。

基础

  1. 在 Java 类中定义一个带返回值的静态方法。
// MyJavaClass.java
package com.example.unityplugin;

import android.util.Log;

public class MyJavaClass {
    public static String myJavaMethod(String message) {
        Log.d("MyJavaClass", message);
        return "Response from Java: " + message;
    }
}
  1. 在 Native C++ 代码中调用 Java 方法并处理返回值。
// native-lib.cpp
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_unityplugin_NativeLib_callJavaMethod(JNIEnv *env, jobject /* this */, jstring input) {
    jclass javaClass = env->FindClass("com/example/unityplugin/MyJavaClass");
    jmethodID methodID = env->GetStaticMethodID(javaClass, "myJavaMethod", "(Ljava/lang/String;)Ljava/lang/String;");
    jstring response = (jstring) env->CallStaticObjectMethod(javaClass, methodID, input);
    return response;
}

进阶-以JavaVM为中介

Java调用Native层时,Native层将JavaVM指针存储下来.

static JavaVM* gJvm = NULL;
static jobject gNativeCrashHandler = NULL;

void initializeNativeCrashHandler(JNIEnv* env, jobject obj) {
    LOGI("NativeCrashWatcher->initializeNativeCrashHandler");
    env->GetJavaVM(&gJvm);
    gNativeCrashHandler = env->NewGlobalRef(obj);

    installSignalHandlers();
}

Native层需要回调Java代码时,通过JavaVM

void handleSignal(int signal, siginfo_t* info, void* context) {
    LOGI("NativeCrashWatcher->handleSignal Caught signal %d", signal);

    JNIEnv* env;
    int getEnvStat = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
    bool needDetach = false;

    if (getEnvStat == JNI_EDETACHED) {
        if (gJvm->AttachCurrentThread(&env, NULL) != 0) {
            LOGE("Failed to attach current thread");
            return;
        }        needDetach = true;
    } else if (getEnvStat == JNI_OK) {
        needDetach = true;
//        LOGD("Thread already attached");
    } else {
        LOGE("Failed to get the environment: %d", getEnvStat);
        return;
    }
    jclass handlerClass = env->GetObjectClass(gNativeCrashHandler);
    if (handlerClass == NULL) {
        LOGE("Failed to get handler class");
        gJvm->DetachCurrentThread();
        return;
    }
    jmethodID methodID = env->GetStaticMethodID(handlerClass, "reportException", "(Ljava/lang/String;)V");
    if (methodID == NULL) {
        LOGE("Failed to get method ID for reportException");
        gJvm->DetachCurrentThread();
        return;
    }//    LOGD("NativeCrashWatcher->handleSignal Got method id");

    char message[256];
    snprintf(message, sizeof(message), "Caught signal %d", signal);
//    LOGD("NativeCrashWatcher->handleSignal generate msg: %s", message);

    jstring exceptionMessage = env->NewStringUTF(message);
    if (exceptionMessage == NULL) {
        LOGE("Failed to create new string");
        gJvm->DetachCurrentThread();
        return;
    }
    env->CallStaticVoidMethod(handlerClass, methodID, exceptionMessage);
//    LOGD("NativeCrashWatcher->handleSignal Called java reportException");

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
        //LOG输出Java层异常信息
        LOGE("Exception occurred while calling Java method");

    }
    env->DeleteLocalRef(exceptionMessage);
//    LOGD("NativeCrashWatcher->handleSignal Deleted local ref");
    if (needDetach) {
        LOGD("Detaching thread...");
        gJvm->DetachCurrentThread();
        LOGD("NativeCrashWatcher->handleSignal Detached current thread");
    }}

Android Studio工程配置

在 Android Studio 中创建用于 Unity 插件的工程

1. 创建新的 Android Studio 工程

  • 选择 "No Activity" 模板,点击 Next
  • Language: 选择 Java
  • Minimum API level: 选择适合您的项目的最低 API 级别,建议使用 API 16: Android 4.1 (Jelly Bean) 或更高版本。

2. 配置工程结构

创建 Java 代码结构
  • 创建 Java 包:在 src/main/java 目录下,右键选择 New > Package,输入包名,例如 com.prin.myplugin
  • 创建 Java 类:在新创建的包中,右键选择 New > Java Class,输入类名 TestJavaClass,然后编写 Java 代码。
package com.prin.myplugin;

import android.util.Log;

public class TestJavaClass {
    public static String myJavaMethod(String message) {
        Log.d("MyJavaClass", message);
        return "Response from Java: " + message;
    }

    public static int Multiply(int a, int b) {
        return a * b;
    }
}
创建 Native 代码结构
  • 创建 C++ 目录:在 src/main 目录下,右键选择 New > Directory,输入目录名 cpp
  • 创建 C++ 文件:在 cpp 目录下,右键选择 New > C++ Source File,输入文件名 native-lib

编写示例 C++ 代码:

#include <jni.h>

extern "C" JNIEXPORT jint JNICALL
Java_com_prin_myplugin_TestJavaClass_multiply(JNIEnv *env, jclass clazz, jint a, jint b) {
    return a * b;
}

配置 CMake 构建脚本 在 cpp 目录下,右键选择 New > File,输入文件名 CMakeLists.txt,并编写以下内容:

cmake_minimum_required(VERSION 3.4.1)

add_library( native-lib SHARED native-lib.cpp )

find_library( log-lib log )

target_link_libraries( native-lib ${log-lib} )

3. 配置 build.gradle 文件

app/build.gradle 文件中

  • 配置 CMake 和 JNI 支持。
android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }
}

添加依赖以生成 Jar 包:

task createJar {
    doFirst {
        logger.lifecycle("Starting to create Jar file...")
    }

    doLast {
        def classesDir = file("$buildDir/intermediates/javac/debug/classes")
        def jarDir = file("$buildDir/libs")
        def jarFilePath = "$jarDir/ExperimentalJavaLib.jar"

        if (!classesDir.exists()) {
            logger.lifecycle("Classes directory does not exist: $classesDir")

            return
        }

        if (!jarDir.exists()) {
            jarDir.mkdirs()
        }

        ant.jar(destfile: jarFilePath, basedir: classesDir) {
            exclude(name: "**/R.class")
            exclude(name: '**/R$*.class')
            exclude(name: "**/BuildConfig.class")
            exclude(name: "**/Manifest.class")
        }

        def jarFile = file(jarFilePath)
        if (jarFile.exists()) {
            logger.lifecycle("Jar file created successfully: ${jarFile.absolutePath}")
        } else {
            logger.lifecycle("Jar file creation failed.")
        }
    }
}

确保 createJar 任务在 assembleDebug 或其他具体的 assemble 任务完成之后执行。

afterEvaluate {
    tasks.getByName("assembleDebug").finalizedBy(createJar)
}

4. 编译项目

  • 点击 Build > Make Project
  • 构建完成后,生成的 Jar 包位于 app/build/libs/MyPlugin.jar,SO 库位于 app/build/intermediates/cmake/debug/obj/<架构>/libnative-lib.so

5. 将生成的文件集成到 Unity 中

  • MyPlugin.jar 复制到 Assets/Plugins/Android/ 目录。
  • libnative-lib.so 复制到 Assets/Plugins/Android/libs/<架构>/ 目录。
  • 在 Unity 中调用 Java 和 Native 方法

案例:CS->Java->Native->Java

  • 在Unity中,通过C#代码调用Java方法CallNativeMethod
  • Java方法CallNativeMethod调用Native方法stringFromJNI
  • Native方法stringFromJNI调用Java方法myJavaMethod
  • Java方法myJavaMethod返回结果给Native方法stringFromJNI
  • Native方法stringFromJNI处理返回结果并返回给Java方法CallNativeMethod
  • Java方法CallNativeMethod返回最终结果给C#代码。
  1. C#代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestCallJavaCallNativeCallJava : MonoBehaviour
{
    public void CallJavaCallNativeCallJava()
    {
        Debug.Log("CallJavaCallNativeCallJava----------------------------------------");

        // 获取 UnityPlayer 类
        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");

        // 获取当前活动
        AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

        // 调用自定义 Java 类的方法
        AndroidJavaClass myJavaClass = new AndroidJavaClass("com.experimental.javalib.TestJavaCallNativeClass");

        // 调用 Java 静态方法并获取返回值
        string response = myJavaClass.CallStatic<string>("CallNativeMethod", "Hello from Unity!");
        Debug.Log(response);
    }
}
  1. Java代码
package com.experimental.javalib;

public class TestJavaCallNativeClass {
    static {
        System.loadLibrary("experimental-native-lib");
    }

    // 声明本地方法
    public static native String stringFromJNI(String input);

    // 调用本地方法
    public static String CallNativeMethod(String input) {
        String responseFromNative = stringFromJNI(input);
        return "Response from Java(CallNativeMethod): " + responseFromNative;
    }
}
  1. Native代码
#include <jni.h>
#include <string>

jstring callJavaMethod(JNIEnv *env, jclass clazz, jstring input) {
    // 获取 Java 类
    jclass javaClass = env->FindClass("com/experimental/javalib/TestJavaClass2");

    // 获取 Java 静态方法ID
    jmethodID methodID = env->GetStaticMethodID(javaClass, "myJavaMethod", "(Ljava/lang/String;)Ljava/lang/String;");

    // 调用 Java 静态方法并获取返回值
    jstring response = (jstring) env->CallStaticObjectMethod(javaClass, methodID, input);

    return response;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_experimental_javalib_TestJavaCallNativeClass_stringFromJNI(JNIEnv *env, jclass clazz, jstring input) {
    // 调用 Java 方法
    jstring javaMethodRes = callJavaMethod(env, clazz, input);

    // 将 Java 返回的字符串转换为 C++ 字符串并拼接
    const char *tmpStr = env->GetStringUTFChars(javaMethodRes, 0);
    std::string hello = "Response from C++(stringFromJNI): ";
    hello += tmpStr;
    env->ReleaseStringUTFChars(javaMethodRes, tmpStr);

    // 返回拼接后的字符串
    return env->NewStringUTF(hello.c_str());
}
  1. Java代码
package com.experimental.javalib;

import android.util.Log;

public class TestJavaClass2 {
    // 被Native代码调用的Java静态方法
    public static String myJavaMethod(String message) {
        Log.d("MyJavaClass", message);
        return "Response from Java(myJavaMethod): " + message;
    }
}

assets/image-20240607183651124.png