<p>转载请附原文链接:<a href="http://yongyu.itscoder.com/2018/04/16/yongyu_20180416_lua_android_two/#more" target="_blank" rel="external noopener noreferrer"> Lua 在 Android 中应用下,具体使用</a></p>

一、概要

在上一章节中介绍了 Android 中如何引入 Lua ,本章节主要介绍 Lua 在 Android 中的具体使用,本章节主要分为两个部分,第一部分是使用 Lua API 绘制 Android 的 View ,第二部分是介绍学习 Lua 和 Android 之间函数回调及参数传递。

二、调用 Lua 布局 Android View

2.1 一些知识点储备

Lua 进程一般不单独运行,标准的 Lua虚拟机 通过C语言编写的,一般是通过CC++来拓展Lua的函数,这样接口兼容速度更快。

Lua 虚拟机与 C/C++ 之间的数据交换基本都是通过 Lua 构建虚拟 栈* 来交互的,无论何时 Lua 调用 C,被调用的函数都得到一个新的栈, 这个栈独立于 C 函数本身的栈,也独立于之前的 Lua 栈。它里面包含了Lua传递给 C 函数的所有参数,而 C 函数则把要返回的结果放入这个栈以返回给调用者。即这里记住 Lua 和 C 之间的数据传递交互是通过虚拟栈进行的,主要通过数据的压栈和出栈进行,虚拟栈里面的数据模型如下:

lua_stack

如图 Lua的 栈的访问索引分为正索引和负索引。正的索引指的是栈上的绝对位置(从1开始),负的索引则指从栈顶开始的偏移量,加入栈里有 n 个元素,那么栈顶元素表示为 -1 或者 n。

初始化 Lua 裤子

1
2
3
4
mLuaState = LuaStateFactory.newLuaState();
mLuaState.openLibs();

2.2 调用 Lua 启动新的 Activity 接口

1
2
3
4
5
6
7
8
9
10
function (context)
intent = luajava.newInstance("android.content.Intent")
--创建 ComponentName,第一个参数是类名,其他参数是这个类构造函数所需要的参数,这里 com.nightfarmer.luabridge.sample 是包名字, CreateViewAndAnimationActivity 是类名
componentName = luajava.newInstance("android.content.ComponentName","com.nightfarmer.luabridge.sample","com.example.zhangpeng.androidlua.UI.CreateViewAndAnimationActivity")
intent:setFlags(intent.FLAG_ACTIVITY_NEW_TASK)
intent:setComponent(componentName)
context:startActivity(intent)
end

这里用到了 luajava 的一个重要方法 newInstance(className, ......) 函数可以创建一个新 java 对象,同时返回一个真正的 java 对象的引用,这样就可以在 Lua 语言中使用语法糖通过面向对象的方式访问该对象,其中第一个参数是要创建的类名,后面的是这个类构造方法所需要的参数。

1
2
3
4
5
6
7
//java 代码
//将一个 lua 的一个全局变量压入共享堆栈中,该值可能是一个全局变量或者一个函数,即将一个全局变量或者一个函数压入共享堆栈中,获取 lua startCreateViewActivity 函数
mLuaState.getGlobal("startCreateViewActivity");
//将 MainActivity.this 对象压栈
mLuaState.pushJavaObject(this);
// 调用 lua startCreateViewActivity 函数 有一个参数 MainActivity.this ,返回值 0 个
mLuaState.call(1, 0);

经过这两个步骤我们就可以启动 CreateViewAndAnimationActivity 这个 Activity 接口了

2.3 调用 Lua 创建 TextView 添加布局

1
2
3
4
5
6
7
8
9
10
11
12
--lua 方法,layout 为父布局,des String类型描述文字,color 色值
function (context,layout,des,color)
--创建 TextView 对象
tv = luajava.newInstance("android.widget.TextView",context)
--调用方法
tv:setText(des.."..追加中文")
layout:addView(tv)
--负数的负数,这个值为绿色,也就是说二进制取反+1再加上负号
tv:setTextColor(-16711936)
-- 返回 TextView 对象
return tv
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// java 方法
private void createTextViewByLua(){
// 将 lua createTextViewByLua 函数压如入栈顶
mLuaState.getGlobal("createTextViewByLua");
//将 lua 函数需要参数压栈
mLuaState.pushJavaObject(getApplicationContext());
// llParent 为父布局 layout
mLuaState.pushJavaObject(llParent);
mLuaState.pushString("android 传递到 lua 的数据");
//调用 createTextViewByLua 函数,该函数共需要 3 个参数,并且有1个返回值
mLuaState.call(3, 1);
try {
// 取出 lua 返回值 即 返回的 TextView,-1 位置为栈顶
TextView tv = (TextView) mLuaState.toJavaObject(-1);
tv.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
} catch (LuaException e) {
e.printStackTrace();
}
}

2.4 调用 Lua 执行 Android 属性动画

1
2
3
4
5
6
7
8
--lua 方法
function startAnimation(animationView)
--bindClass 接受一个class类. 然后返回一个对象可以访问该对象的静态知道和该类的方法
ObjectAnimator = luajava.bindClass("com.example.zhangpeng.androidlua.ObjectAnimator")
local animator = ObjectAnimator:ofFloat(animationView, "rotation",0,360)
animator:setDuration(1000)
animator:start()
end
1
2
3
4
5
6
//java 方法
private void startAnimationByLua() {
mLuaState.getGlobal("startAnimation");
mLuaState.pushJavaObject(btnStartAnimation);
mLuaState.call(1,0);
}

通过上面几个小例子,对通过使用 Lua 代码布局 Android View 有了感性上的认识,更多小栗子请见 AndroidLua

三、Android 和 Lua 之间多参数传递及回调操作

注:注意 lua 存储数据的结构只有一种就是表

3.1 Lua 传递参数给 Adroid

1
2
3
4
5
6
7
--lua 代码
function pushDataToAndroid()
--创建表
local luaData={fromLua1="来自 lua 1",fromLua2="来自 lua 2"}
--调用 android 代码
androidLua:getDataFromLua(luaData);
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//java 代码,将 AndroidLua 对象 push 到 lua 内部
public void registerObject() {
try {
AndroidLua androidLua = new AndroidLua();
luaState.pushObjectValue("androidLua");
luaState.setGlobal(name);
} catch (LuaException e) {
e.printStackTrace();
}
}
//AndroidLua 类中代码
/**
* lua 传递数据给 android
* @param luaObject
*/
public void getDataFromLua(LuaObject luaObject){
LuaState luaState=luaObject.getLuaState();
Map<String, String> stringStringMap = (Map<String, String>) LuaUtil.parseLuaTable(luaState);
for (Map.Entry<String, String> entry :stringStringMap.entrySet()){
Log.d(TAG,entry.getKey());
Log.d(TAG,entry.getValue());
}
}
// LuaUtil 中代码,解析 lua 表中数据
public static Object parseLuaTable(LuaState luaState){
Object luaData=null;
// 取出栈里面元素个数
int top = luaState.getTop();
// 判断栈定是否为表元素
if (luaState.isTable(top)) {
// 向栈顶部 push 一个 nil 元素
luaState.pushNil();
while (luaState.next(top) != 0) {
// key type
// -2 位置为 key
int keyType = luaState.type(-2);
String keyName=luaState.typeName(keyType);
// -1 位置为值,即栈顶了
int valueType = luaState.type(-1);
String valueName = luaState.typeName(valueType);
//数组
if (keyName.equals("number")) {
if(luaData==null){
luaData=new ArrayList<>();
}
if(luaData instanceof List){
parseTableValueList(luaState, valueName, (List<Object>) luaData);
}else {
parseTableValueMap(luaState, valueName, (Map<String, Object>) luaData);
}
//map 键值对
}
else if (keyName.equals("string")) {
if(luaData==null){
luaData = new HashMap<>();
}
if(luaData instanceof Map){
parseTableValueMap(luaState, valueName, (Map<String, Object>) luaData);
}else {
parseTableValueList(luaState, valueName, (List<Object>) luaData);
}
}
if(!"function".equals(valueName)){
luaState.pop(1);
}
}
}
return luaData;
}
//解析 map 结构
private static void parseTableValueMap(LuaState mLuaState, String valueName, Map<String, Object> objectMap) {
switch (valueName) {
case "null":
break;
case "number":
objectMap.put(mLuaState.toString(-2), mLuaState.toNumber(-1));
break;
case "string":
objectMap.put(mLuaState.toString(-2), mLuaState.toString(-1));
break;
case "boolean":
objectMap.put(mLuaState.toString(-2), mLuaState.toBoolean(-1));
break;
case "int":
case "integer":
objectMap.put(mLuaState.toString(-2), mLuaState.toInteger(-1));
break;
case "function" :
String key=mLuaState.toString(-2);
int value=mLuaState.Lref(LuaState.LUA_REGISTRYINDEX);
objectMap.put(key,value);
break;
case "table":
objectMap.put(mLuaState.toString(-2), parseLuaTable(mLuaState));
break;
default:
break;
}
}
// 解析数组结构
private static void parseTableValueList(LuaState mLuaState, String valueName, List<Object> objectMap) {
switch (valueName) {
case "null":
break;
case "number":
objectMap.add( mLuaState.toNumber(-1));
break;
case "string":
objectMap.add(mLuaState.toString(-1));
break;
case "boolean":
objectMap.add( mLuaState.toBoolean(-1));
break;
case "int":
case "integer":
objectMap.add(mLuaState.toInteger(-1));
break;
case "function" :
int value=mLuaState.Lref(LuaState.LUA_REGISTRYINDEX);
objectMap.add(value);
break;
case "table":
objectMap.add(parseLuaTable(mLuaState)) ;
break;
default:
break;
}
}
//java 代码,调用 lua 函数
private void getDataFromLua() {
luaState.getGlobal("pushDataToAndroid");
luaState.call(0, 0);
}

3.2 Android 传递数据给 Lua

1
2
3
4
5
6
7
-- lua 代码
function getMapDataFromAndroid(map)
for k, v in pairs(map) do
Log(TAG, k)
Log(TAG, v)
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//java 代码
/**
* push android map data to lua
*/
private void pushAndroidMapDataToLua() {
luaState.getGlobal("getMapDataFromAndroid");
Map<String, String> mapDta = new HashMap<>();
mapDta.put("formAndroid1", "I am from Android map1");