luaJavaBridge詳解

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 將被回收。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诱篷,隨后出現(xiàn)的幾起案子壶唤,更是在濱河造成了極大的恐慌,老刑警劉巖棕所,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闸盔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡琳省,警方通過(guò)查閱死者的電腦和手機(jī)迎吵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)针贬,“玉大人钓觉,你說(shuō)我怎么就攤上這事〖岵龋” “怎么了荡灾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瞬铸。 經(jīng)常有香客問(wèn)我批幌,道長(zhǎng),這世上最難降的妖魔是什么嗓节? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任荧缘,我火速辦了婚禮,結(jié)果婚禮上拦宣,老公的妹妹穿的比我還像新娘截粗。我一直安慰自己,他們只是感情好鸵隧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布绸罗。 她就那樣靜靜地躺著,像睡著了一般豆瘫。 火紅的嫁衣襯著肌膚如雪珊蟀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天外驱,我揣著相機(jī)與錄音育灸,去河邊找鬼。 笑死昵宇,一個(gè)胖子當(dāng)著我的面吹牛磅崭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓦哎,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼砸喻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柔逼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起恩够,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羡铲,沒(méi)想到半個(gè)月后蜂桶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡也切,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年扑媚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雷恃。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疆股,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倒槐,到底是詐尸還是另有隱情旬痹,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布讨越,位于F島的核電站两残,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏把跨。R本人自食惡果不足惜人弓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望着逐。 院中可真熱鬧崔赌,春花似錦、人聲如沸耸别。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秀姐。三九已至吟榴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間囊扳,已是汗流浹背吩翻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锥咸,地道東北人狭瞎。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搏予,于是被迫代替她去往敵國(guó)和親熊锭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,415評(píng)論 0 17
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法碗殷,類相關(guān)的語(yǔ)法精绎,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法锌妻,異常的語(yǔ)法代乃,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,631評(píng)論 18 399
  • 朋友找我訴苦,家里來(lái)了送孩子上學(xué)的一家親戚仿粹,普通人家卻把一個(gè)大男孩慣得渾身毛哺橄拧:吃飯?zhí)舴蕭荨⒛懽有〉阶?..
    前沿深度GFU閱讀 467評(píng)論 0 1
  • 來(lái)日并不方長(zhǎng) 王海芳 一天天的流水樣的日子過(guò)去了吭历,看似什么都沒(méi)變堕仔,但是心里又知道時(shí)間很倉(cāng)促,飛快的在我手里流失晌区,仔...
    王海芳閱讀 244評(píng)論 0 0
  • 你摩骨,眼藏光,偷走我魂 你朗若,眉帶笑仿吞,擾亂我心 你,嘴角輕揚(yáng)捡偏,困住不經(jīng)事的我
    素人小子閱讀 143評(píng)論 2 3