Android Lua 相互调用

前言

本文基于 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 标签页,勾选CMakeLLDBNDK下载安装之。
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

你能够将 androidluaModule 导入本身工程做为依赖库使用。ide

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 中文件的内容:

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");
}

建立 Lua 栈

以前说了,依靠一个虚拟的栈来完成数据交换。那么首先咱们固然要建立这个栈。

LuaState lua = LuaStateFactory.newLuaState(); //建立栈
lua.openLibs(); //加载标准库

lua.close(); //养成良好习惯,在执行完毕后销毁Lua栈。

执行 Lua 脚本

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

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

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

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

读取 Lua 变量与 table

首页要说明的是,只有全局变量(非 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 函数

调用 Lua 函数的通常流程为:

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

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个返回值。

传入 Java 对象

得益于 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个参数。

执行结果

注入 Lua 变量

有时咱们须要在 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.

相关连接

相关文章
相关标签/搜索