杰瑞科技汇

cocos lua 调用java

核心概念

  1. Java Bridge: Cocos Creator 提供的一套简化了 Jni 调用的工具,让你不需要直接写复杂的 Jni C++ 代码。
  2. Jni (Java Native Interface): Java 标准的一部分,允许 Java 代码和其他语言(如 C/C++)进行交互,Cocos Creator 底层就是通过它来和 Java 通信的。
  3. luajit: Cocos Creator 使用的是 LuaJit,它对 Jni 有很好的支持。

详细步骤

第 1 步:配置 Cocos Creator 项目

  1. 创建项目: 打开 Cocos Creator,创建一个新的 JavaScript 或 TypeScript 项目,在项目的 属性检查器 中,将 脚本语言 设置为 Lua

  2. 修改 frameworks/runtime-src/proj.android-studio/build.gradle:

    • 找到 android 块。
    • defaultConfig 内部,添加 multiDexEnabled true,这是为了处理方法数超过 65535 的问题,在混合开发中很常见。
    // frameworks/runtime-src/proj.android-studio/build.gradle
    android {
        // ... 其他配置 ...
        defaultConfig {
            // ... 其他配置如 applicationId, minSdkVersion ...
            multiDexEnabled true // 添加这一行
        }
        // ... 其他配置 ...
    }

第 2 步:编写 Java 代码

在你的 Android 项目中(通常是 frameworks/runtime-src/proj.android-studio/app/src/main/java),创建一个 Java 类来作为 Lua 调用的桥梁。

  1. 创建包名: 为了避免冲突,建议创建一个独特的包名,com.cocos.game.plugin
  2. 创建 Java 类: 在该包下创建一个 Java 类,JniBridge.java

JniBridge.java 示例代码:

package com.cocos.game.plugin;
import org.cocos2dx.lib.Cocos2dxLuaJavaBridge; // 关键导入
public class JniBridge {
    // 1. 一个简单的无参无返回值方法
    public void showToast() {
        android.widget.Toast.makeText(com.cocos.game.AppActivity.getContext(), "Hello from Lua!", android.widget.Toast.LENGTH_SHORT).show();
    }
    // 2. 一个带参数和返回值的方法
    public int addNumbers(int a, int b) {
        return a + b;
    }
    // 3. 一个接收字符串参数的方法
    public void printLog(String message) {
        android.util.Log.d("Cocos2dx-Lua", "Java received message: " + message);
    }
    // 4. 一个调用 Lua 回调的方法
    public void callLuaCallback(int luaFunctionRef) {
        // Cocos2dxLuaJavaBridge 类提供了直接调用 Lua 函数的方法
        // 参数1: Lua 函数在 Lua 虚拟机中的引用
        // 参数2及以后: 传递给 Lua 函数的参数
        Cocos2dxLuaJavaBridge.callLuaFunctionWithUpvalues(luaFunctionRef, "This is a message from Java!");
    }
}

代码说明:

  • import org.cocos2dx.lib.Cocos2dxLuaJavaBridge;: 这是调用 Java Bridge 的核心类,必须导入。
  • showToast(): 演示了如何调用 Android 原生功能(显示 Toast)。
  • addNumbers(): 演示了如何在 Java 和 Lua 之间传递基本数据类型(int)。
  • printLog(): 演示了如何传递字符串。
  • callLuaCallback(): 这是一个非常重要的功能,展示了如何从 Java 反向调用 Lua 代码。luaFunctionRef 是一个 Lua 函数的引用。

第 3 步:在 Lua 中调用 Java 代码

我们可以在 Lua 脚本中调用上面定义的 Java 方法了。

在任意 Lua 脚本中(MainScene.lua):

local JniBridge = require("app.JniBridge") -- 引入我们即将创建的Lua封装模块
local MainScene = class("MainScene", function()
    return cc.Scene:create()
end)
function MainScene:ctor()
    -- ... 场景初始化代码 ...
    -- 添加一个按钮来触发调用
    local item = cc.MenuItemImage:create("HelloWorld.png", "HelloWorld.png")
    item:onClicked(function()
        self:onTestButtonClicked()
    end)
    local menu = cc.Menu:create(item)
    menu:alignItemsVertically()
    self:addChild(menu)
end
function MainScene:onTestButtonClicked()
    -- 获取 Java 虚拟机实例
    local luaj = JniBridge.getInstance()
    -- --- 示例1: 调用无参无返回值方法 ---
    print("Lua: Calling Java showToast()...")
    luaj.showToast()
    -- --- 示例2: 调用带参数和返回值的方法 ---
    print("Lua: Calling Java addNumbers(5, 10)...")
    local result = luaj.addNumbers(5, 10)
    print("Lua: Result from Java:", result)
    -- --- 示例3: 调用接收字符串的方法 ---
    print("Lua: Calling Java printLog('Hello Java!')...")
    luaj.printLog("Hello Java!")
    -- --- 示例4: 调用 Java 并让 Java 回调 Lua ---
    -- 首先定义一个 Lua 函数
    local luaCallback = function(message)
        print("Lua: Callback executed! Message from Java:", message)
    end
    -- 将 Lua 函数传递给 Java
    -- luaj.callLuaCallback(luaCallback) -- 这种方式在某些版本可能不稳定
    -- 更推荐的方式是让 Java 返回一个引用,然后我们保存这个引用
    print("Lua: Calling Java to get a callback reference...")
    local callbackRef = luaj.getLuaCallbackRef(luaCallback)
    luaj.callLuaCallbackWithRef(callbackRef)
end
return MainScene

第 4 步:创建 Lua 封装模块 (JniBridge.lua)

直接在业务代码里写 luaj 调用会显得很混乱,我们创建一个专门的 Lua 模块来封装所有 Java 调用,使代码更清晰。

app 目录下创建 JniBridge.lua 文件:

-- app/JniBridge.lua
local JniBridge = {}
-- 获取 luaj 全局对象 (Cocos Creator 提供的全局对象,用于调用 Java)
local luaj = JniBridge
-- 保存 Java 类的完整路径
local JAVA_CLASS_NAME = "com/cocos/game/plugin/JniBridge"
-- 辅助函数:将 Lua 表转换为 Java 字符串数组
local function convertTableToStringArray(luaTable)
    if type(luaTable) ~= "table" then
        return nil
    end
    local str = "{"
    for i, v in ipairs(luaTable) do
        str = str .. tostring(v)
        if i < #luaTable then
            str = str .. ","
        end
    end
    str = str .. "}"
    return str
end
-- --- 封装 Java 方法的调用 ---
-- 1. showToast
function JniBridge.showToast()
    local ok, ret = luaj.callStaticMethod(JAVA_CLASS_NAME,
        "showToast",
        {},
        "()V")
    if not ok then
        print("Lua: callStaticMethod showToast failed:", ret)
    end
end
-- 2. addNumbers
function JniBridge.addNumbers(a, b)
    local ok, ret = luaj.callStaticMethod(JAVA_CLASS_NAME,
        "addNumbers",
        {a, b},
        "(II)I")
    if not ok then
        print("Lua: callStaticMethod addNumbers failed:", ret)
        return 0
    end
    return ret
end
-- 3. printLog
function JniBridge.printLog(message)
    local ok, ret = luaj.callStaticMethod(JAVA_CLASS_NAME,
        "printLog",
        {message},
        "(Ljava/lang/String;)V")
    if not ok then
        print("Lua: callStaticMethod printLog failed:", ret)
    end
end
-- 4. getLuaCallbackRef
-- 这个方法用于将 Lua 函数转换为 Java 可以使用的引用
function JniBridge.getLuaCallbackRef(luaFunction)
    -- luaj.callStaticMethod 的返回值可以是 Lua 对象
    -- 这里我们让 Java 返回这个引用
    local ok, ret = luaj.callStaticMethod(JAVA_CLASS_NAME,
        "getLuaCallbackRef",
        {luaFunction},
        "(I)I")
    if not ok then
        print("Lua: callStaticMethod getLuaCallbackRef failed:", ret)
        return nil
    end
    return ret
end
-- 5. callLuaCallbackWithRef
-- 这个方法使用上一步获取的引用来调用 Java 的回调方法
function JniBridge.callLuaCallbackWithRef(callbackRef)
    if not callbackRef or type(callbackRef) ~= "number" then
        print("Lua: Invalid callback reference!")
        return
    end
    local ok, ret = luaj.callStaticMethod(JAVA_CLASS_NAME,
        "callLuaCallback",
        {callbackRef},
        "(I)V")
    if not ok then
        print("Lua: callStaticMethod callLuaCallback failed:", ret)
    end
end
return JniBridge

JniBridge.lua 代码详解:

  • local luaj = JniBridge: Cocos Creator 在初始化时会创建一个全局的 luaj 对象,为了代码清晰,我们习惯用一个局部变量 luaj 来指向它。
  • JAVA_CLASS_NAME: 定义 Java 类的完整路径,使用 代替 。
  • luaj.callStaticMethod: 这是核心函数,用于调用 Java 的静态方法。
    • 参数1: Java 类的完整路径。
    • 参数2: 要调用的 Java 方法名。
    • 参数3: 一个 Lua 表,包含传递给 Java 方法的参数。
    • 参数4: Java 方法的 签名,这是最关键也最容易出错的部分。
  • 方法签名:
    • ()V: 表示无参数,无返回值 (Void)。
    • (II)I: 表示两个 int 参数,返回一个 int
    • (Ljava/lang/String;)V: 表示一个 String 参数,无返回值。L 表示对象类型,后面是类的完整路径,以
    • 其他常见类型: Z (boolean), D (double), F (float), J (long), [I (int 数组)。

第 5 步:处理回调 (Java -> Lua)

为了实现 Java 回调 Lua,我们需要对 JniBridge.javaJniBridge.lua 进行微调。

修改 JniBridge.java:

// ... (imports remain the same) ...
public class JniBridge {
    // ... (other methods remain the same) ...
    // 添加一个方法来获取 Lua 函数的引用
    public static int getLuaCallbackRef(LuaFunction L) {
        // Cocos2dxLuaJavaBridge 提供了获取函数引用的方法
        // 参数1: Lua 状态机
        // 参数2: Lua 函数在栈中的索引
        // 返回值: 一个整数引用
        return Cocos2dxLuaJavaBridge.getLuaFunctionRef(L, 1);
    }
    // 修改原有的回调方法,使其接受一个整数引用
    public static void callLuaCallback(int luaFunctionRef) {
        if (luaFunctionRef > 0) {
            Cocos2dxLuaJavaBridge.callLuaFunctionWithUpvalues(luaFunctionRef, "This is a message from Java!");
            // 调用完成后,可以可选地释放引用
            // Cocos2dxLuaJavaBridge.releaseLuaFunctionRef(luaFunctionRef);
        }
    }
}

修改 JniBridge.lua:

-- ... (其他函数保持不变) ...
-- 修改 getLuaCallbackRef,使其更符合 luaj 的调用习惯
-- luaj.callStaticMethod 会自动处理 Lua 函数到 Java 对象的转换
-- 所以我们只需要让 Java 方法返回这个引用即可
function JniBridge.getLuaCallbackRef(luaFunction)
    -- 我们直接将 luaFunction 作为参数传递
    -- luaj 会自动处理
    local ok, ret = luaj.callStaticMethod(JAVA_CLASS_NAME,
        "getLuaCallbackRef",
        {luaFunction}, -- 传递 Lua 函数本身
        "(I)I") -- 假设我们的 Java 方法接受一个 LuaFunction 对象并返回 int
    if not ok then
        print("Lua: callStaticMethod getLuaCallbackRef failed:", ret)
        return nil
    end
    return ret
end
-- ... (其他函数保持不变) ...

注意: 关于回调,Cocos Creator 的 luaj 库在不同版本间可能存在差异,最可靠的方式是:

  1. 在 Lua 中定义回调函数。
  2. 将该函数通过 luaj.callStaticMethod 传递给 Java。
  3. Java 端的 callStaticMethod 会自动将 Lua 函数转换为一个可以传递的对象。
  4. Java 保存这个对象,并在需要时通过 Cocos2dxLuaJavaBridge.callLuaFunctionWithUpvalues 来调用。

常见问题与注意事项

  1. 签名错误: luaj.callStaticMethod 失败最常见的原因就是 Java 方法签名错误,请务必核对参数类型和返回值类型。
  2. 数据类型转换:
    • Lua 的 number 在 Java 端可能被转换为 IntegerDouble,根据你的签名 (I)(D) 来决定。
    • Lua 的 table 传递到 Java 比较复杂,通常需要传递字符串,然后在 Java 中解析。
  3. 线程问题: Java 的 UI 操作(如 Toast, Dialog)必须在主线程(UI 线程)中执行,如果你的 Java 代码被从子线程调用,你需要使用 Handler 将任务切换到主线程。
    // 在 Java 方法中,如果需要更新 UI
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            // 在这里执行 UI 操作,Toast
            Toast.makeText(...).show();
        }
    });
  4. 混淆配置: 如果你的 Android 项目启用了代码混淆(proguard-rules.pro),你需要保留你的 Java Bridge 类和 Cocos2dxLuaJavaBridge,否则在打包后代码会被混淆掉,导致调用失败。
    -keep class com.cocos.game.plugin.** { *; }
    -keep class org.cocos2dx.lib.** { *; }
  5. 调试: 调试 Java 和 Lua 的交互非常困难。
    • Java 端: 使用 Log.d() 打印日志,通过 adb logcat 查看。
    • Lua 端: 使用 print() 在 Cocos Creator 的 开发者 -> 控制台 中查看输出。
    • luaj.callStaticMethod 的返回值: ok 表示是否成功,ret 在失败时通常会返回一个错误码或错误信息,仔细检查这个值。

通过以上步骤,你就可以在 Cocos Creator 的 Lua 项目中成功调用 Java 代码,实现强大的原生功能扩展了。

分享:
扫描分享到社交APP
上一篇
下一篇