本文基于 Lua 5.3.html
Lua 是一个轻量级脚本语言,经常使用于嵌入其余语言做为补充。关于更多Lua自己的问题不在本文讨论范围以内。
在 Android 中嵌入 Lua 优势不少,借助 Lua 脚本语言的优点,能够轻松实现动态逻辑控制,应用能够随时从服务器读取最新 Lua 脚本文件,在不更新应用的状况下修改程序逻辑。
惋惜 Lua 官方只提供了 C API ,而 Android 主要使用 JAVA 做为开发语言。咱们能够借助 JNI 来间接实如今 Android 中嵌入 Lua 。java
本身实现 JNI 是一件很费力的事情,还好有人已经造好了轮子叫作 Luajava ,而且又有人基于 Luajava 作了 Android 专用库。网上流传最广的是 Androlua ,不过做者已经多年不维护了,对 Lua 的支持依然停留在5.1而且有一些bug,有人Fork了这个项目,并将其更新至 Lua 5.3 :New Androlua ,不过这个项目也存在一些问题,我修复了一下可是做者并无处理个人 pull request ,各位能够直接使用我修复优化后的:Android-Lua. android
在修复bug的同时,我也添加了一些中文注释,减小第一次接触 Lua C API 朋友们的学习记忆成本。git
因为最终须要调用 Lua C API,因此请先配置 NDK 开发环境。在 Android Studio 中打开 SDK Manager,切换到 SDK Tools 标签页,勾选CMake
、LLDB
、NDK
下载安装之。github
仅仅想实现 Android Lua 互相调用,不关心具体过程的,能够直接添加依赖:implementation 'cc.chenhe:android-lua:1.0.2'
而后后边的导入部分能够跳过了。服务器
Clone github 项目到本地并用 Android Studio 打开。大体能够看到下图目录结构。(因为后期更新,结构不必定彻底相同)
其中 androidlua
是库,app
是demo工程。库中,lua
下是 Lua 解释引擎,luajava
是 JNI 的有关代码。*.mk
文件是NDK配置文件,详情请参考Google NDK 文档。app
你能够将 androidlua
Module 导入本身工程做为依赖库使用。ide
此时咱们已经能够在 Android 与 Lua 直接互相调用了。可是在开始以前,还要学习下 Lua C API 的有关东西,由于这是与 Lua 交互的基础。函数
Lua 与 C 依靠一个虚拟的栈来完成数据交换。包括变量、函数、参数、返回值等在内的一切数据,都要放入栈中来共享。为了调用方便,这个栈并非严格遵循栈的规则。从索引来看,栈底索引为1,往上依次递增。而栈顶索引是-1,往下依次递减。所以,正负索引都是合法的,可是0不能够。下面是栈的示意图:学习
Lua 提供了大量的 C API,与其余语言的交互彻底依赖这些 API,下面的基础教程中本文会介绍几个基础的,具体能够查看Lua 官方手册
这些函数均由 Lua 提供,在 Luajava 中被封装在 LuaState
类下。
加载 Lua 标准库,通常须要调用一下。
执行一段 Lua 脚本。
执行给定文件中的 Lua 脚本。
获取当前栈的内容。
java 中对应函数是dumpStack()
,返回String,能够直接输出,便于调试。
将各类类型的数据压入栈,以便将来使用。
将栈中指定索引处的值以xxx类型取出。
获取 Lua 中的全局变量(包括函数),并压入栈顶,以便将来使用。
参数就是要获取的变量的名字。
获取 Lua 中 某一 table 的元素,并压入栈顶。
第一个参数是 table 在栈中的索引,第二个参数是要获取元素的 key.
执行 Lua 函数。
第一个参数是此函数的参数个数,第二个是返回值个数,第三个是错误处理函数在栈中的索引。
终于要开始调用了,想一想还有点小激动呢~
为了方便说明,咱们先定义一个脚本文件叫 test.lua
,而后将其放在assets
目录下,由于这下面的文件不会被编译。
可使用下面的函数来读取 assets 中文件的内容:
public static String readAssetsTxt( Context context, String fileName ){ try { InputStream is = context.getAssets().open( fileName ); int size = is.available(); /* Read the entire asset into a local byte buffer. */ byte[] buffer = new byte[size]; is.read( buffer ); is.close(); /* Convert the buffer into a string. */ String text = new String( buffer, "utf-8" ); /* Finally stick the string into the text view. */ return(text); } catch ( IOException e ) { e.printStackTrace(); } return("err"); }
以前说了,依靠一个虚拟的栈来完成数据交换。那么首先咱们固然要建立这个栈。
LuaState lua = LuaStateFactory.newLuaState(); //建立栈 lua.openLibs(); //加载标准库 lua.close(); //养成良好习惯,在执行完毕后销毁Lua栈。
咱们可使用LdoString()
来执行一段简单的脚本。
String l = "local a = 1"; lua.LdoString(l);
这样就执行了一个很简单的脚本,他声明了一个全局变量l
,值为1.
固然,也可使用LdoFile()
来加载脚本文件,不过这须要你先把脚本复制到 SD 卡才行。由于在 APK 中是没有“路径”可言的。
首页要说明的是,只有全局变量(非 local)才能够读取。
test.lua:
a = 111; t = { ["name"] = "Chenhe", [2] = 2222, }
android:
lua.getGlobal("a"); //获取变量a并将值压入栈 Log.i("a", lua.toInteger(-1) + ""); //以int类型取出栈顶的值(也就是a) lua.getGlobal("t"); //获取变量t并压入栈顶,此时table位于栈顶。 lua.getField(-1, "name"); //取出栈顶的table的name元素,压入栈顶。 Log.i("t.name", lua.toString(-1)); //以string类型取出栈顶的值(也就是t.name) Log.i("dump",lua.dumpStack()); //输出当前栈
运行后能够看到log:
I/a: 111 I/t.name: Chenhe I/dump: 1: number = 111.0 2: table 3: string = 'Chenhe'
咱们已经成功读取了 Lua 中的变量。
调用 Lua 函数的通常流程为:
test.lua:
function test(a, b) return a + b, a - b; end
android:
lua.getGlobal( "test" ); //获取函数并入栈 lua.pushInteger( 5 ); //压入第一个参数a lua.pushInteger( 3 ); //压入第二个参数b lua.pcall( 2, 2, 0 ); //执行函数,有2个参数,2个返回值,不执行错误处理。 Log.i( "r1", lua.toInteger( -2 ) + "" ); //输出第一个返回值 Log.i( "r2", lua.toInteger( -1 ) + "" ); //输出第二个返回值 Log.i( "dump", lua.dumpStack() );
运行后能够看到log:
I/r1: 8 I/r2: 2 I/dump: 1: number = 8.0 2: number = 2.0
这就成功地执行了 Lua 函数,并取得2个返回值。而以前入栈的函数以及参数,在执行的时候 Lua 已经弹出了,因此最后栈里只剩下2个返回值。
得益于 Luajava 以及 Androlua 的封装,咱们能够直接将对象做为参数传入,并在 Lua 中直接执行对象的成员函数。
test.lua:
function setText(tv,s) tv:setText("set by Lua."..s); tv:setTextSize(50); end
android:
lua.getGlobal("setText"); //获取函数 lua.pushJavaObject(textView); //把TextView传入 lua.pushString("Demo"); //传入一个字符串 lua.pcall(2,0,0); //执行函数,有2个参数。
有时咱们须要在 android 中建立 Lua 变量,这样就能够在 Lua 中直接使用了。注意,这里建立的变量都是全局的(非 local)。
test.lua:
function setText(tv) tv:setText("set by Lua."..s); --这里的s变量由java注入 tv:setTextSize(50); end
android:
lua.pushString( "from java" ); //压入欲注入变量的值 lua.setGlobal( "s" ); //压入变量名 lua.getGlobal( "setText" ); //获取Lua函数 lua.pushJavaObject( textView ); //压入参数 lua.pcall( 1, 0, 0 ); //执行函数
能够看到 Lua 成功调用了 Java 注入的变量s
.