核心概念
- Java Bridge: Cocos Creator 提供的一套简化了
Jni调用的工具,让你不需要直接写复杂的JniC++ 代码。 - Jni (Java Native Interface): Java 标准的一部分,允许 Java 代码和其他语言(如 C/C++)进行交互,Cocos Creator 底层就是通过它来和 Java 通信的。
luajit: Cocos Creator 使用的是LuaJit,它对Jni有很好的支持。
详细步骤
第 1 步:配置 Cocos Creator 项目
-
创建项目: 打开 Cocos Creator,创建一个新的 JavaScript 或 TypeScript 项目,在项目的 属性检查器 中,将 脚本语言 设置为 Lua。
-
修改
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 调用的桥梁。
- 创建包名: 为了避免冲突,建议创建一个独特的包名,
com.cocos.game.plugin。 - 创建 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.java 和 JniBridge.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 库在不同版本间可能存在差异,最可靠的方式是:
- 在 Lua 中定义回调函数。
- 将该函数通过
luaj.callStaticMethod传递给 Java。 - Java 端的
callStaticMethod会自动将 Lua 函数转换为一个可以传递的对象。 - Java 保存这个对象,并在需要时通过
Cocos2dxLuaJavaBridge.callLuaFunctionWithUpvalues来调用。
常见问题与注意事项
- 签名错误:
luaj.callStaticMethod失败最常见的原因就是 Java 方法签名错误,请务必核对参数类型和返回值类型。 - 数据类型转换:
- Lua 的
number在 Java 端可能被转换为Integer或Double,根据你的签名(I)或(D)来决定。 - Lua 的
table传递到 Java 比较复杂,通常需要传递字符串,然后在 Java 中解析。
- Lua 的
- 线程问题: 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(); } }); - 混淆配置: 如果你的 Android 项目启用了代码混淆(
proguard-rules.pro),你需要保留你的 Java Bridge 类和Cocos2dxLuaJavaBridge,否则在打包后代码会被混淆掉,导致调用失败。-keep class com.cocos.game.plugin.** { *; } -keep class org.cocos2dx.lib.** { *; } - 调试: 调试 Java 和 Lua 的交互非常困难。
- Java 端: 使用
Log.d()打印日志,通过adb logcat查看。 - Lua 端: 使用
print()在 Cocos Creator 的 开发者 -> 控制台 中查看输出。 luaj.callStaticMethod的返回值:ok表示是否成功,ret在失败时通常会返回一个错误码或错误信息,仔细检查这个值。
- Java 端: 使用
通过以上步骤,你就可以在 Cocos Creator 的 Lua 项目中成功调用 Java 代码,实现强大的原生功能扩展了。
