背景
开发中有一些场景,需要使用到动态化能力,除了插件和热补丁。我们还可以使用脚本,相比插件化与热补丁,脚本更加灵活的安全(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。
代码语言:txt复制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中。
代码语言:txt复制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。
代码语言:txt复制local log = luajava.bindClass("android.util.Log")
local tag = "main.lua"
log:i(tag, "file is nil")
newInstance
对于普通对象,我们先要实例化对象再调用方法,这里根据构造函数是否有参数,氛围两种。
注意这里同样使用符号:来调用对象的方法。
代码语言:txt复制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类。
代码语言:txt复制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就会被回调。
代码语言:txt复制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等有较大差异,开发者需要一段时间熟悉。但总的来说,还是非常推荐的。