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等有较大差异,开发者需要一段时间熟悉。但总的来说,还是非常推荐的。