cocos2d-x技術(shù)群新群:117871561
c++技術(shù)交流群:593010226
此文章獻(xiàn)給 苦于接sdk的猿類(參考某技術(shù)人員的博客)
要實(shí)現(xiàn)java與lua的相互操作 傳統(tǒng)做法是先用 C/C++ 借助 JNI(Java Native Interface)編寫(xiě)調(diào)用 Java 的接口函數(shù),然后再將這些函數(shù)通過(guò) tolua++ 導(dǎo)出給 Lua 使用弟翘。這種做法最大的問(wèn)題就是太繁瑣疗垛,而且稍微有一點(diǎn)點(diǎn)修改吠裆,就要重新編譯千诬,嚴(yán)重降低了開(kāi)發(fā)效率静袖,我們可以用luaj進(jìn)行實(shí)現(xiàn)阻星。
luaj 主要特征
可以從 Lua 調(diào)用 Java Class Static Method
調(diào)用 Java 方法時(shí)丁侄,支持 int/float/boolean/String/Lua function 五種參數(shù)類型
可以將 Lua function 作為參數(shù)傳遞給 Java惯雳,并讓 Java 保存 Lua function 的引用
可以從 Java 調(diào)用 Lua 的全局函數(shù),或者調(diào)用引用指向的 Lua function
luaj代碼示例(根據(jù)自己項(xiàng)目中愛(ài)貝支付簡(jiǎn)化版舉例 )
java方法原型
//傳入用戶id,購(gòu)買房卡數(shù)量鸿摇,房卡價(jià)格石景,lua回調(diào)方法的引用(用于java調(diào)用lua)
public static void IaPay(finnal string Userid,final int count, final float Price ,final int luaFunc)
lua部分
--用于處理支付結(jié)果的回調(diào)函數(shù)
local function payCallBack(result)
if type(result) == "string" and result =="true"
then
updateBean()
end
end
調(diào)用java方法
function IaPay()
--args是調(diào)用java方法需要的參數(shù)
local args = {1,"234",5.0,payCallBack}
--sigs是方法的此簽名
local sig = "(ILjava/lang/String;IFI)V"
--bridge_calss是調(diào)用java方法所在的java類 com.org.thirdParty中間的 . 換成 /
local bridge_class = "com/org/thirdParty"
--此處是調(diào)用java的靜態(tài)方法iaPay
local ok,ret = luaj.callStaticMethod(bridge_class," IaPay",args,sigs)
end
luaj 實(shí)現(xiàn)原理
luaj 的核心目標(biāo)有兩個(gè):從 Lua 調(diào)用 Java, 從 Java 調(diào)用 Lua拙吉。整理出來(lái)就是如下幾點(diǎn):
查找并調(diào)用指定的 Java 方法
檢查調(diào)用結(jié)果潮孽,并從 Java 方法獲取返回值
將 Lua function 作為參數(shù)傳遞給 Java 方法
在 Java 方法中調(diào)用 Lua function
查找并調(diào)用指定的 Java 方法
JNI 提供了 FindClass() 方法用于查找指定的 Class,所以 luaj.callStaticMethod() 的第一個(gè)參數(shù)就是要調(diào)用的 Java Class 的完整類名稱(類名稱中的“.”要替換為“/”)庐镐。
找到指定 Class 后恩商,利用 JNI 的 GetStaticMethodID() 方法就可以找到這個(gè)類的指定靜態(tài)方法变逃,前提是要提供靜態(tài)方法的名稱和簽名必逆。
所謂簽名,就是指 Java 方法的參數(shù)類型和返回類型定義揽乱。例如前面示例代碼中 IaPay() 方法的簽名是 "(ILjava/lang/String;IFI)V" 名眉。
示例代碼一共指定了 4 個(gè)參數(shù),分別是:字符串凰棉、整型损拢、浮點(diǎn)型、Lua function撒犀。
luaj 根據(jù)這 4 個(gè)參數(shù)福压,會(huì)構(gòu)造出正確的 IaPay()方法簽名掏秩。注意 Lua function 是以整數(shù)的形式傳入 Java 方法,所以 Java 方法的第四個(gè)參數(shù)是 int 類型)荆姆。
簽名使用“(依次排列的參數(shù)類型)返回值類型”的格式蒙幻,幾個(gè)例子如下:
簽名
解釋
()V
參數(shù):無(wú),返回值:無(wú)
(I)V
參數(shù):int胆筒,返回值:無(wú)
(Ljava/lang/String;)Z
參數(shù):字符串邮破,返回值:布爾值
(IF)Ljava/lang/String;
參數(shù):整數(shù)、浮點(diǎn)數(shù)仆救,返回值:字符串
這里列出不同類型對(duì)應(yīng)的 Java 簽名字符串:
類型名
類型
I
整數(shù)抒和,或者 Lua function
F
浮點(diǎn)數(shù)
Z
布爾值
Ljava/lang/String;
字符串
V
Void 空,僅用于指定一個(gè) Java 方法不返回任何值
Java 方法里接收 Lua function 的參數(shù)必須定義為 int 類型
檢查調(diào)用結(jié)果彤蔽,并從 Java 方法獲取返回值
luaj 調(diào)用 Java 方法時(shí)摧莽,可能會(huì)出現(xiàn)各種錯(cuò)誤,因此 luaj 提供了一種機(jī)制讓 Lua 調(diào)用代碼可以確定 Java 方法是否成功調(diào)用铆惑。
luaj.callStaticMethod() 會(huì)返回兩個(gè)值:
當(dāng)成功時(shí)范嘱,第一個(gè)值為 true,第二個(gè)值是 Java 方法的返回值(如果有)员魏。
當(dāng)失敗時(shí)丑蛤,第一個(gè)值為 false,第二個(gè)值是錯(cuò)誤代碼撕阎。
下面的代碼展示了如何檢查返回結(jié)果和獲得返回值:
Java 代碼1
public static int AddTwoNumbers(final int number1, final int number2)
{ return number1 + number2;}
Lua 代碼
local args = {2, 3}
local sig = "(II)I"
local ok, ret = luaj.callStaticMethod(className, "AddTwoNumbers", args, sig)
if not ok
then
print("luaj error:", ret)else print("ret:", ret)
-- 輸出 ret: 5
end
錯(cuò)誤代碼定義如下:
錯(cuò)誤代碼
描述
-1
不支持的參數(shù)類型或返回值類型
-2
無(wú)效的簽名
-3
沒(méi)有找到指定的方法
-4
Java 方法執(zhí)行時(shí)拋出了異常
-5
Java 虛擬機(jī)出錯(cuò)
-6
Java 虛擬機(jī)出錯(cuò)
~
將 Lua function 作為參數(shù)傳遞給 Java 方法
很多時(shí)候受裹,我們需要一種方法讓 Java 代碼可以向 Lua 代碼傳遞一些消息。例如在大部分游戲平臺(tái)的 SDK 中虏束,涉及支付的部分都是異步操作的棉饶。在支付操作結(jié)束后,Java 代碼需要通知 Lua 支付成功與否镇匀。
Lua 虛擬機(jī)中照藻,Lua function 以值的形式保存。但這個(gè)值無(wú)法直接給 Java 用汗侵,所以 luaj 做了一個(gè) Lua function 引用表幸缕。當(dāng)一個(gè) Lua function 傳遞給 Java 時(shí),這個(gè) function 對(duì)應(yīng)的值會(huì)被存在引用表中晰韵,并獲得一個(gè)唯一的引用 ID (整數(shù))发乔。Java 代碼拿到這個(gè)引用 ID 后,就可以很方便的調(diào)用該 Lua function 了雪猪。
回顧最開(kāi)始的示例代碼栏尚,GameInterface_doBilling() 函數(shù)用于接收 Lua function 的參數(shù)就是 int 類型。因?yàn)閷?shí)際傳入 Java 函數(shù)的值是 Lua function 的引用 Id只恨。
~
在 Java 方法中調(diào)用 Lua function
在 Java 代碼中拿到 Lua function 的引用 ID 后译仗,就可以很方便的調(diào)用該 Lua function 了:
1
LuaJavaBridge.callLuaFunctionWithString(luaFunctionId, "hello");
這里出現(xiàn)的 LuaJavaBridge 是 luaj 的 Java 部分定義的工具 class抬虽。 callLuaFunctionWithString() 方法可以將一個(gè)字符串參數(shù)傳遞給指定的 Lua function。
LuaJavaBridge 還提供了 callLuaGlobalFunctionWithString() 方法纵菌,可以直接調(diào)用 Lua 中指定名字的全局函數(shù)斥赋。這樣可以在沒(méi)有 Lua function 引用 ID 的情況下和 Lua 代碼交互。
GL 線程和 UI 線程的協(xié)調(diào)
cocos2d-x for Android 運(yùn)行在多線程環(huán)境下产艾,所以在 Lua 和 Java 交互時(shí)需要注意選擇適當(dāng)?shù)木€程疤剑。
~
cocos2d-x 在 Android 上以兩個(gè)線程來(lái)運(yùn)行,分別是負(fù)責(zé)圖像渲染的 GL 線程和負(fù)責(zé) Android 系統(tǒng)用戶界面的 UI 線程闷堡。
在 cocos2d-x 啟動(dòng)后隘膘,Lua 代碼將由 GL 線程調(diào)用,因此從 Lua 中調(diào)用的 Java 方法如果涉及到系統(tǒng)用戶界面的顯示杠览、更新操作弯菊,那么就必須讓這部分代碼切換到 UI 線程上去運(yùn)行。
反之亦然踱阿,從 Java 調(diào)用 Lua 代碼時(shí)管钳,需要讓這個(gè)調(diào)用在 GL 線程上執(zhí)行,否則 Lua 代碼雖然執(zhí)行了软舌,但會(huì)無(wú)法更新 cocos2d-x 內(nèi)部狀態(tài)才漆。
下面是 GameInterface_doBilling() 方法的主要代碼:
public static void GameInterface_doBilling(final String billingIndex, final boolean useSms, final boolean isRepeated, final int luaFunctionId)
{
context.runOnUiThread(new Runnable()
{
@Override
public void run()
{
GameInterface.doBilling(useSms, isRepeated, billingIndex, new BillingCallback()
{
... @Override
public void onBillingSuccess()
{
context.runOnGLThread(new Runnable(
{
@Override
public void run()
{
LuaJavaBridge.callLuaFunctionWithString(luaFunctionId, "success"); LuaJavaBridge.releaseLuaFunction(luaFunctionId); } }); } ... }); } });}
方法中,構(gòu)造了一個(gè) Runnable 對(duì)象佛点,用來(lái)包裝需要執(zhí)行的 Java 代碼醇滥。這個(gè) Runnable 對(duì)象被指定運(yùn)行在 UI 線程上。這樣當(dāng)調(diào)用 GameInterface.doBilling() 方法時(shí)就可以正確顯示出支付界面超营。
當(dāng)用戶支付成功后鸳玩,GameInterface.doBilling() 會(huì)調(diào)用 BillingCallback.onBillingSuccess() 方法。這個(gè)方法里構(gòu)造了另一個(gè) Runnable 對(duì)象演闭,包裝了調(diào)用 Lua function 的代碼不跟。
看上去代碼不少,實(shí)際上就是在兩個(gè)線程間互相切換米碰。確保 Lua function 跑在 GL 線程窝革,Java 代碼跑在 UI 線程。
Lua function 的引用計(jì)數(shù)器
Lua 虛擬機(jī)具有自動(dòng)垃圾回收機(jī)制见间。Lua function 既然是值聊闯,那么在沒(méi)有被使用時(shí)自然會(huì)被回收掉工猜。所以 luaj 提供了 retainLuaFunction() 和 releaseLuaFunction() 兩個(gè)函數(shù)用于增減 Lua function 的引用計(jì)數(shù)米诉。
將一個(gè) Lua function 以引用 ID 的形式傳入 Java 時(shí),luaj 會(huì)自動(dòng)增加引用 ID 的計(jì)數(shù)器篷帅,所以在 Java 方法里可以放心的異步調(diào)用 Lua function史侣。但在不需要使用該 Lua function 后拴泌,一定要調(diào)用 releaseLuaFunction() 減少該引用 ID 的計(jì)數(shù)器。當(dāng)計(jì)數(shù)器為 0 時(shí)惊橱,會(huì)自動(dòng)釋放該 Lua function蚪腐。
如果了解 cocos2d-x 中 CCObject 的 autorelease 機(jī)制,那么對(duì)引用計(jì)數(shù)應(yīng)該很熟悉税朴,兩者是完全相同的實(shí)現(xiàn)機(jī)制回季。
~
連接第三方 SDK 和 cocos2d-x 的中間層
雖然 luaj 可以讓開(kāi)發(fā)者從 Lua 中直接調(diào)用 Java 代碼。但大部分第三方 SDK 在初始化時(shí)都需要指定當(dāng)前應(yīng)用程序的 Activity 對(duì)象正林,并且還要切換不同線程泡一,所以對(duì)于大多數(shù)第三方 SDK,我們?nèi)匀灰獙?xiě)一個(gè)中間層用于 Lua 和 Java 的交互觅廓。
與使用 JNI 做中間層相比鼻忠,配合 luja 的中間層是使用 Java 來(lái)編寫(xiě)的,不但更簡(jiǎn)單明了杈绸,而且處理線程切換也非常簡(jiǎn)單帖蔓。
~
要實(shí)現(xiàn)一個(gè)中間層,只有兩個(gè)步驟:
實(shí)現(xiàn)供 luaj 調(diào)用的 Java 接口
修改游戲的 Java 入口文件瞳脓,將應(yīng)用程序的 Activity 對(duì)象傳入 SDK
第二步也相當(dāng)簡(jiǎn)單塑娇,只需要在游戲的 onCreate() 中調(diào)用 中間層 class 的 setContext() 方法:
public class mygame extends Cocos2dxActivity {
protected void onCreate(Bundle savedInstanceState) {
ChinaMobile_SDK.setContext(this); // init sdk
super.onCreate(savedInstanceState);
}
...
}
安裝 luaj
luaj 分為三個(gè)部分:
LuaJavaBridge.java, com_qeeplay_frameworks_LuaJavaBridge.h/.cpp - 供 Java 端使用的工具類,包含 Java 接口定義文件和 JNI 實(shí)現(xiàn)劫侧。
LuaJavaBridge.h/.cpp - 供 Lua 端使用的工具類钝吮。
luaj.lua - LuaJavaBridge 的 Lua 包裝,提供更簡(jiǎn)單和靈活的接口板辽。
下載地址:
Java/C++ 部分源代碼
Lua 部分源代碼
~
步驟:
將 LuaJavaBridge.java 添加到 Android 項(xiàng)目中奇瘦;
修改 proj.android/jni/Android.mk:
LOCAL_SRC_FILES := ... \ luaj/jni/com_qeeplay_frameworks_LuaJavaBridge.cpp \ luaj/luabinding/LuaJavaBridge.cppLOCAL_C_INCLUDES := ... \ luaj
修改 AppDelegate.cpp,加入以下代碼:
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "LuaJavaBridge.h"
#endif
bool AppDelegate::applicationDidFinishLaunching()
{
...
CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();
CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
LuaJavaBridge_luabinding_open(pEngine->getLuaState());
#endif
...
}
修改proj.android/jni/hellocpp/main.cpp劲弦,加入以下代碼:
jint JNI_OnLoad(JavaVM *vm, void *reserved){ ... LuaJavaBridge_setJavaVM(vm); return JNI_VERSION_1_4;}
luaj 方法參考
[Lua] luaj.callStaticMethod(className, methodName, args, methodSig)
調(diào)用指定的 Java class static method耳标,允許傳入 int/float/boolean/string/function 五種類型的參數(shù)。
[Java] LuaJavaBridge.callLuaFunctionWithString(int luaFunctionId, String value)
調(diào)用引用 ID 指向的 Lua function邑跪,并傳入一個(gè)字符串作為參數(shù)次坡。
[Java] LuaJavaBridge.callLuaGlobalFunctionWithString(int luaFunctionId, String value)
調(diào)用指定名字的 Lua 全局函數(shù),并傳入一個(gè)字符串作為參數(shù)画畅。
[Java] LuaJavaBridge.retainLuaFunction(int luaFunctionId)
增加引用 ID 的計(jì)數(shù)砸琅,確保 Lua function 不會(huì)被 Lua 虛擬機(jī)自動(dòng)回收。
[Java] LuaJavaBridge.releaseLuaFunction(int luaFunctionId)
減少引用 ID 的計(jì)數(shù)轴踱,當(dāng)計(jì)數(shù)等于 0 時(shí)症脂,引用 ID 指向的 Lua function 將被回收。