android中使用luaj
- 2019 年 11 月 28 日
- 筆記
背景
開發中有一些場景,需要使用到動態化能力,除了插件和熱補丁。我們還可以使用腳本,相比插件化與熱補丁,腳本更加靈活的安全(Google對插件化持禁止態度),在android中常用的腳本有python和lua。
python因為包體較大、執行效率低等問題,在嵌入式android中使用較少。
lua雖然沒有python那麼強大,但是卻有着三大優勢,使得它非常適合在嵌入式設備中使用。
- 包體小,luaj-3.0.1也就347KB,壓縮後會更小。
- 執行效率高
- 內存佔用小
本文主要講述android與lua相互調用的問題。
android調用lua
首先我們准別一段lua腳本,這裡我們採用vscode來編寫,vscode可以編譯、運行、單步調試lua腳本。在android運行lua前我們就可以確保lua腳本是沒有問題,從而提高效率。

這裡我們需要安裝lua debug。配置方式可以參見文檔https://blog.csdn.net/qq_35331967/article/details/83864437。這裡需要注意的是,我們使用的luaj,所以有部分代碼是編譯不過的,調試的時候可以先去掉,例如:

然後我們嘗試通過java代碼來調用lua。
Globals globals = JsePlatform.standardGlobals(); public void compile(File file) { try { globals.load(new FileReader(file), "script").call(); } catch (FileNotFoundException e) { XLog.e(TAG, "compile: error", e); } } public Object invoke(String func, Object... parameters) { if (parameters != null && parameters.length > 0) { LuaValue[] values = new LuaValue[parameters.length]; for (int i = 0; i < parameters.length; i++) { values[i] = CoerceJavaToLua.coerce(parameters[i]); } return globals.get(func).call(LuaValue.listOf(values)); } else { return globals.get(func).call(); } }
這裡有幾點需要關注的:
- 首先會創建Globals對象,這個對象負責加載lua文件。
- invoke負責調用lua文件中的函數,func指函數名。parameters指參數,可以為多個。
- 參數通過LuaValue.listOf轉換為luatable,luatable是lua8種基礎類型之一。
- 一個文件一般對應一個Globals。
關於Globals,源碼注釋是這樣描述的。
Global environment used by luaj. Contains global variables referenced by executing lua.
lua的文檔比較少,有興趣可以直接看源碼,地址為:
https://github.com/luaj/luaj
這樣我們就可以使用android直接調用lua了。
lua調用java
luaj中提供了5中方法可以用來訪問java程序,這些方法定義在LuajavaLib中。
public class LuajavaLib extends VarArgFunction { static final int INIT = 0; static final int BINDCLASS = 1; static final int NEWINSTANCE = 2; static final int NEW = 3; static final int CREATEPROXY = 4; static final int LOADLIB = 5; static final String[] NAMES = { "bindClass", "newInstance", "new", "createProxy", "loadLib", }; }
在lua中,我們可以直接用 : 號來訪問方法。
bindclass
以在lua文件中輸出log為例,注意當我們綁定log的類後,使用符號:來調用方法i,最終輸出log。
local log = luajava.bindClass("android.util.Log") local tag = "main.lua" log:i(tag, "file is nil")
newInstance
對於普通對象,我們先要實例化對象再調用方法,這裡根據構造函數是否有參數,氛圍兩種。
注意這裡同樣使用符號:來調用對象的方法。
javaObject = luajava.newInstance(類名全稱) javaObject = luajava.newInstance(類名全稱,[構造方法參數(可變參數)]) //====== local dog = luajava.newInstance("com.water.Person") dog:setName("3water") local dog = luajava.newInstance("com.water.Person","3water") dog:getName()
new
可以從源碼中看出,newInstance與new非常的相似,new在創建class的時候,傳入的參數是userdata,也就是java的class類。
case NEWINSTANCE: case NEW: { // get constructor final LuaValue c = args.checkvalue(1); final Class clazz = (opcode==NEWINSTANCE? classForName(c.tojstring()): (Class) c.checkuserdata(Class.class)); final Varargs consargs = args.subargs(2); return JavaClass.forClass(clazz).getConstructor().invoke(consargs); }
createproxy
通過createproxy,可以讓lua繼承java中的interface,從而獲得對應的回調。
這裡我們創建了一個TimeAnimator,當動畫執行的時候,lua函數TimeListener.onTimeUpdate就會被回調。
local timeAnimator = luajava.newInstance('android.animation.TimeAnimator') local timeListener = luajava.createProxy('android.animation.TimeAnimator$TimeListener',TimeListener) timeAnimator:setTimeListener(timeListener) -- 實現回調 TimeListener = {} function TimeListener.onTimeUpdate(animation,totalTime,deltaTime) end
loadLib
loadlib這個還是用的比較少,應該是用來加載so文件,然後可以實現lua與c預研進行交互。
總結
lua作為一種輕量級語言,與其他語言結合後,在解決實際問題中會有非常多的好處。也會有一些缺點,比如資料較少,語法和java等有較大差異,開發者需要一段時間熟悉。但總的來說,還是非常推薦的。