Lua解析器 mLua

介紹

當下多數在java下執行lua腳本的程序都是用了

luajava。然而luajava存在一些嚴重的問題,它會將byte數組和string等同對待,而且它的反射執行效率比較低。為了彌補這些問題,我參考luajava,重寫了它的java和jni代碼,並以mLua為名重新發布。

Lua解析器 mLua

特點描述

和luajava類似的,mLua也有內置的全局lua函數;java對象和lua對象可以通過jni層代碼進行交換。但是mLua禁止lua直接操作java對象,如果想在lua中使用java對象,必須使用內置的全局函數實現。

mLua區分byte數組和string。在mLua中,java的byte數組對lua端而言,只是一個普通的userdata。

在跨語言數據交換的時候,string是被複制的,因此當一個string從lua傳遞到java後再於lua中修改它,它在java端的對應版本並不會隨著改變。

將lua端的number傳遞給java後,會被優先解釋為byte類型,否則將依照byte - short - int - long - float - double鏈條來嘗試解釋。

mLua不對外暴露lua解析器實例,所有的操作都基於MLua實例完成。

java端方法描述

mLua的java端方法集中在MLua中:

方法名稱方法解釋setBasedir(String)設置lua代碼的最外層目錄,所有lua代碼都應該存放在這個目錄或其子目錄下pushGlobal(String, Object)設置全局lua的全局變量或函數,可以push普通的Object,或者JavaFunction。

後者表示一個lua函數的java實現。只有在start方法執行前,設置的數據才會生

效start(String)啟動lua解析器,傳遞的參數表示lua代碼的入口文件stop()停止lua解析器並釋放資源

除此之外,JavaFunction也是使用者可能需要用到的接口。它表示一個lua函數的java實現。其回調方法execute(Object[])方法會傳入從lua端輸入的數據,並輸出一個結果傳回lua端。如果方法本身不需要返回數據,則返回null即可。

lua端函數描述

在mLua下,lua原來的require、print函數已經被改寫。

require

require必須使用設置在java端的basedir為根目錄的相對路徑引用其他lua腳本:

require "dir1/dir2/script1"
require "script2"

print

支持輸出一個或多個對象,但是不能將string與java對象作拼接:

-- 正確的做法 --
print("hello mLua")
print("context: ", getContext())
print("string " .. 111)
-- 錯誤的做法 --
print("context: " .. getContext())

通過逗號分隔的對象會在java端以tab號分隔顯示

操作java對象

mLua也採用反射來操作java對象,不過mobTools的ReflectHelper具備緩存功能,理論上會比luajava每次直接反射更快。mLua提供瞭如下的內置函數:

函數名稱函數解釋import(className)向ReflectHelper類緩存中導入一個類,此函數將返回一個

string,用於後續代碼從緩存中重新獲取導入的類實例import(name, className)向ReflectHelper類緩存中導入一個類,並將此緩存的key

設置為指定名稱new(className, ...)構造一個java實例,參數className是import函數的返回值,

後續參數為java構造方法的輸入參數invokeStatic(className, methodName, ...)調用一個java的靜態方法invoke(receiver, methodName, ...)調用一個Java的實例方法getStatic(className, fieldName)獲取一個java的靜態字段setStatic(className, fieldName, value)設置一個java的靜態字段get(receiver, fieldName)獲取一個java的實例字段set(receiver, fieldName, value)設置一個java的實例字段createProxy(proxyTable, ...)構造一個java接口代理。參數proxyTable是一個lua的table,

其中的key必須與java接口類的方法名稱相同,key對應的

value是一個lua的function,function的參數列表和返回值也

必須與java接口相同。proxyTable後的參數是被實現的接口

列表名稱,皆為string,由import函數返回。此函數將返回一

個java接口代理實例,可將此實例傳回java端並進行操作,

當實例中的接口函數被調用時,mLua會調用proxyTable中的

對應funtion代碼完成操作

例子

java端代碼

public class MainActivity extends Activity {
private MLua lua;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 構造一個解析器實例
lua = new MLua();
// 設置lua代碼存放位置
lua.setBasedir("/sdcard/mLua/LuaTest");
// push全局對象
lua.pushGlobal("getContext", new JavaFunction() {
public Object execute(Object[] args) throws Throwable {
return getApplication();
}
});
try {
// 啟動解析器,設置main.lua為入口代碼
lua.start("main");
} catch (Throwable e) {
e.printStackTrace();
}
}
protected void onDestroy() {
// 關閉解析器
lua.stop();
super.onDestroy();
}
}

lua端代碼

-- 導入ReflectHelper.ReflectRunnable類,並命名為ReflectRunnable --
import("ReflectRunnable", "com.mob.tools.utils.ReflectHelper$ReflectRunnable")
local function main()
-- 演示print和invoke --
print("hello world from mLua")
local context = getContext()
print("current context: ", context)
local packageName = invoke(context, "getPackageName")
print("packageName: ", packageName)
-- 演示java接口代理 --
local luaCode = {
run = function(arg)
print("luaCode.run(), input: ", arg)
return "yoyoyo"
end
}
local proxy = createProxy(luaCode, "ReflectRunnable")
local res = invoke(proxy, "run", packageName)
print("luaCode.run(), output: ", res)
-- 演示數組複製 --
local bArray = new("[B", 16)
for i = 0, 15 do
set(bArray, "[" .. i .. "]", i + 1)
end
local bArray2 = new("[B", get(bArray, "length"))
invokeStatic("System", "arraycopy", bArray, 0, bArray2, 0, 16)
for i = 0, 15 do
print("bArray2[" .. i .. "]: ", get(bArray2, "[" .. i .. "]"))
end
end
main()

擴展

mLua默認只能從文件系統中加載lua代碼,但是如果對MLua的setBasedir方法進行重寫,以其他的方式實現SourceLoader,則可以加載任意方式的lua代碼,包括assets中的,和加密的。


分享到:


相關文章: