当前位置:   article > 正文

Android Lua 相互调用

android lua 相互调用

前言

本文基于 Lua 5.3.

Lua 是一个轻量级脚本语言,常用于嵌入其他语言作为补充。关于更多Lua本身的问题不在本文讨论范围之内。
在 Android 中嵌入 Lua 优点很多,借助 Lua 脚本语言的优势,可以轻松实现动态逻辑控制,应用可以随时从服务器读取最新 Lua 脚本文件,在不更新应用的情况下修改程序逻辑。
可惜 Lua 官方只提供了 C API ,而 Android 主要使用 JAVA 作为开发语言。我们可以借助 JNI 来间接实现在 Android 中嵌入 Lua 。

准备

自己实现 JNI 是一件很费力的事情,还好有人已经造好了轮子叫做 Luajava ,并且又有人基于 Luajava 做了 Android 专用库。网上流传最广的是 Androlua ,不过作者已经多年不维护了,对 Lua 的支持依然停留在5.1并且有一些bug,有人Fork了这个项目,并将其更新至 Lua 5.3 :New Androlua ,不过这个项目也存在一些问题,我修复了一下但是作者并没有处理我的 pull request ,各位可以直接使用我修复优化后的:Android-Lua.

在修复bug的同时,我也添加了一些中文注释,减少第一次接触 Lua C API 朋友们的学习记忆成本。

由于最终需要调用 Lua C API,所以请先配置 NDK 开发环境。在 Android Studio 中打开 SDK Manager,切换到 SDK Tools 标签页,勾选CMakeLLDBNDK下载安装之。
NDK环境

导入工程

仅仅想实现 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 文档

你可以将 androidluaModule 导入自己工程作为依赖库使用。

Lua API 知识普及

此时我们已经可以在 Android 与 Lua 直接互相调用了。但是在开始之前,还要学习下 Lua C API 的有关东西,因为这是与 Lua 交互的基础。

Lua 与 C 依靠一个虚拟的栈来完成数据交换。包括变量、函数、参数、返回值等在内的一切数据,都要放入栈中来共享。为了调用方便,这个栈并不是严格遵循栈的规则。从索引来看,栈底索引为1,往上依次递增。而栈顶索引是-1,往下依次递减。因此,正负索引都是合法的,但是0不可以。下面是栈的示意图:
Lua 栈

常用 Lua C API 介绍

Lua 提供了大量的 C API,与其他语言的交互完全依赖这些 API,下面的基础教程中本文会介绍几个基础的,具体可以查看Lua 官方手册

这些函数均由 Lua 提供,在 Luajava 中被封装在 LuaState 类下。

luaL_openlibs

加载 Lua 标准库,一般需要调用一下。

luaL_dostring

执行一段 Lua 脚本。

luaL_dofile

执行给定文件中的 Lua 脚本。

lua_dump

获取当前栈的内容。
java 中对应函数是dumpStack(),返回String,可以直接输出,便于调试。

lua_pushXXX

将各种类型的数据压入栈,以便未来使用。

lua_toXXX

将栈中指定索引处的值以xxx类型取出。

lua_getglobal

获取 Lua 中的全局变量(包括函数),并压入栈顶,以便未来使用。
参数就是要获取的变量的名字。

lua_getfield

获取 Lua 中 某一 table 的元素,并压入栈顶。
第一个参数是 table 在栈中的索引,第二个参数是要获取元素的 key.

lua_pcall

执行 Lua 函数。
第一个参数是此函数的参数个数,第二个是返回值个数,第三个是错误处理函数在栈中的索引。

Enjoy coding

终于要开始调用了,想想还有点小激动呢~

为了方便说明,我们先定义一个脚本文件叫 test.lua,然后将其放在assets目录下,因为这下面的文件不会被编译。
可以使用下面的函数来读取 assets 中文件的内容:

  1. public static String readAssetsTxt( Context context, String fileName ){
  2. try {
  3. InputStream is = context.getAssets().open( fileName );
  4. int size = is.available();
  5. /* Read the entire asset into a local byte buffer. */
  6. byte[] buffer = new byte[size];
  7. is.read( buffer );
  8. is.close();
  9. /* Convert the buffer into a string. */
  10. String text = new String( buffer, "utf-8" );
  11. /* Finally stick the string into the text view. */
  12. return(text);
  13. } catch ( IOException e ) {
  14. e.printStackTrace();
  15. }
  16. return("err");
  17. }

创建 Lua 栈

之前说了,依靠一个虚拟的栈来完成数据交换。那么首先我们当然要创建这个栈。

  1. LuaState lua = LuaStateFactory.newLuaState(); //创建栈
  2. lua.openLibs(); //加载标准库
  3. lua.close(); //养成良好习惯,在执行完毕后销毁Lua栈。

执行 Lua 脚本

我们可以使用LdoString()来执行一段简单的脚本。

  1. String l = "local a = 1";
  2. lua.LdoString(l);

这样就执行了一个很简单的脚本,他声明了一个全局变量l,值为1.

当然,也可以使用LdoFile()来加载脚本文件,不过这需要你先把脚本复制到 SD 卡才行。因为在 APK 中是没有“路径”可言的。

读取 Lua 变量与 table

首页要说明的是,只有全局变量(非 local)才可以读取。
test.lua:

  1. a = 111;
  2. t = {
  3. ["name"] = "Chenhe",
  4. [2] = 2222,
  5. }

android:

  1. lua.getGlobal("a"); //获取变量a并将值压入栈
  2. Log.i("a", lua.toInteger(-1) + ""); //以int类型取出栈顶的值(也就是a)
  3. lua.getGlobal("t"); //获取变量t并压入栈顶,此时table位于栈顶。
  4. lua.getField(-1, "name"); //取出栈顶的table的name元素,压入栈顶。
  5. Log.i("t.name", lua.toString(-1)); //以string类型取出栈顶的值(也就是t.name)
  6. Log.i("dump",lua.dumpStack()); //输出当前栈

运行后可以看到log:

  1. I/a: 111
  2. I/t.name: Chenhe
  3. I/dump: 1: number = 111.0
  4. 2: table
  5. 3: string = 'Chenhe'

我们已经成功读取了 Lua 中的变量。

执行 Lua 函数

调用 Lua 函数的一般流程为:

  1. 获取函数并入栈。
  2. 压入各个参数(如果有)
  3. 调用函数,指明参数个数、返回值个数、错误处理函数。
  4. 获取返回值(如果有)

test.lua:

  1. function test(a, b)
  2. return a + b, a - b;
  3. end

android:

  1. lua.getGlobal( "test" ); //获取函数并入栈
  2. lua.pushInteger( 5 ); //压入第一个参数a
  3. lua.pushInteger( 3 ); //压入第二个参数b
  4. lua.pcall( 2, 2, 0 ); //执行函数,有2个参数,2个返回值,不执行错误处理。
  5. Log.i( "r1", lua.toInteger( -2 ) + "" ); //输出第一个返回值
  6. Log.i( "r2", lua.toInteger( -1 ) + "" ); //输出第二个返回值
  7. Log.i( "dump", lua.dumpStack() );

运行后可以看到log:

  1. I/r1: 8
  2. I/r2: 2
  3. I/dump: 1: number = 8.0
  4. 2: number = 2.0

这就成功地执行了 Lua 函数,并取得2个返回值。而之前入栈的函数以及参数,在执行的时候 Lua 已经弹出了,所以最后栈里只剩下2个返回值。

传入 Java 对象

得益于 Luajava 以及 Androlua 的封装,我们可以直接将对象作为参数传入,并在 Lua 中直接执行对象的成员函数。

test.lua:

  1. function setText(tv,s)
  2. tv:setText("set by Lua."..s);
  3. tv:setTextSize(50);
  4. end

android:

  1. lua.getGlobal("setText"); //获取函数
  2. lua.pushJavaObject(textView); //把TextView传入
  3. lua.pushString("Demo"); //传入一个字符串
  4. lua.pcall(2,0,0); //执行函数,有2个参数。

执行结果

注入 Lua 变量

有时我们需要在 android 中创建 Lua 变量,这样就可以在 Lua 中直接使用了。注意,这里创建的变量都是全局的(非 local)。

test.lua:

  1. function setText(tv)
  2. tv:setText("set by Lua."..s); --这里的s变量由java注入
  3. tv:setTextSize(50);
  4. end

android:

  1. lua.pushString( "from java" ); //压入欲注入变量的值
  2. lua.setGlobal( "s" ); //压入变量名
  3. lua.getGlobal( "setText" ); //获取Lua函数
  4. lua.pushJavaObject( textView ); //压入参数
  5. lua.pcall( 1, 0, 0 ); //执行函数

执行结果

可以看到 Lua 成功调用了 Java 注入的变量s.

相关链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/548012
推荐阅读
相关标签
  

闽ICP备14008679号