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腳本是沒有問題,從而提高效率。

image.png

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

image.png

然後我們嘗試通過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等有較大差異,開發者需要一段時間熟悉。但總的來說,還是非常推薦的。