LuaJIT 使用文档
AsLua 使用 LuaJIT 作为脚本运行时,并在启动 Lua 状态时注入 Android 上下文、LuaKt Java 桥、项目路径和常用辅助函数。开发者通常只需要编写 main.lua,然后通过 activity、luakt、import、loadlayout 等能力访问 Android 和 Java / Kotlin API。
本文基于 AsLua 项目当前实现编写,源码入口主要包括:
com.aslua.Maincom.aslua.LuaActivitycom.aslua.LuaApplicationcom.luakt.LuaStatecom.luakt.LuaKtAPIsrc/main/resources/lua/import.luasrc/main/resources/lua/loadlayout.lua
运行时版本
AsLua 当前使用 LuaJIT,语法基础兼容 Lua 5.1。请不要直接使用 Lua 5.2、Lua 5.3 或 Lua 5.4 才提供的语法和标准库行为。
最小入口
主 Activity 是 com.aslua.Main,它继承自 LuaActivity,默认执行应用私有目录下的 main.lua。
一个最小 main.lua:
require "import"
print("Hello AsLua")
print("Lua path:", luakt.luapath)
print("Lua dir:", luakt.luadir)启动后,LuaActivity 会创建默认日志界面。如果脚本没有主动设置内容视图,日志列表会作为默认界面显示。
启动流程
AsLua 的 Lua 脚本执行流程如下:
LuaApplication初始化应用目录、Lua 模块路径和 C 模块路径。Main.onCreate()调用LuaActivity.onCreate()。LuaActivity解析要执行的 Lua 文件,默认是localDir/main.lua。LuaActivity.initLua()创建LuaState,打开 Lua 标准库和 LuaKt 库。- 注入全局对象与路径信息。
- 设置
package.path和package.cpath。 - 执行
main.lua。 - 根据 Activity 生命周期调用 Lua 中同名回调函数。
内置全局对象
LuaActivity.initLua() 会向 Lua 环境注入以下对象:
| 名称 | 类型 | 说明 |
|---|---|---|
activity | LuaActivity Java 对象 | 当前 Activity,也是主要 Android 上下文 |
this | LuaActivity Java 对象 | activity 的别名 |
luakt | LuaKt 库 table | Java / Kotlin 桥接库 |
print | Java 注册函数 | 输出到 AsLua 日志界面 |
set | Java 注册函数 | 给 LuaThread 设置变量 |
call | Java 注册函数 | 调用 LuaThread 中的函数 |
activity 被保存到 Lua registry 的 _LuaContext 中,luakt.getContext() 可以取回当前 LuaContext。
local context = luakt.getContext()
print(context.getLuaDir())
print(context.getWidth(), context.getHeight())luakt 路径字段
初始化时,AsLua 会给 luakt 设置几个路径字段:
| 字段 | 说明 |
|---|---|
luakt.luaextdir | 外部 AsLua 工作目录 |
luakt.luadir | 当前 Lua 项目目录 |
luakt.luapath | 当前执行的 Lua 文件路径 |
示例:
print(luakt.luaextdir)
print(luakt.luadir)
print(luakt.luapath)模块搜索路径
AsLua 会设置 package.path:
当前项目/?.lua;
当前项目/lua/?.lua;
当前项目/?/init.lua;
内置 lua 目录/?.lua;
内置 lua 目录/lua/?.lua;
内置 lua 目录/?/init.lua;也会设置 package.cpath:
应用 nativeLibraryDir/lib?.so;
应用私有 lib 目录/lib?.so因此你可以直接加载项目内模块:
local log = require "modules.log"目录示例:
main.lua
modules/
log.lua
user.luamodules/log.lua:
local log = {}
function log.info(message)
print("[INFO] " .. tostring(message))
end
return log使用 import
AsLua 内置 import.lua,建议在大多数脚本开头加载:
require "import"加载后会提供:
| 名称 | 说明 |
|---|---|
import | 导入 Java 类、Lua 模块、dex 类或包 |
loadlayout | 加载 Lua table / aly 布局 |
loadbitmap | 加载图片 |
android.R | Android 系统资源类 |
R | 当前应用资源类 |
MD3_R | Material 组件资源类 |
luajava | 兼容对象,包含 bindClass = luakt.bindClass |
import 默认会尝试从以下包中查找类:
java.lang.
java.util.
com.aslua.导入单个类:
require "import"
import "android.widget.TextView"
import "android.graphics.Color"
local view = TextView(activity)
view.setText("Hello AsLua")
view.setTextColor(Color.RED)
activity.setContentView(view)导入包:
require "import"
import "android.widget.*"
local button = Button(activity)
button.setText("Click")导入 Lua 模块:
require "import"
import "loadlayout"从 dex 加载类:
require "import"
local MyClass = import("plugin.dex:com.example.MyClass")LuaKt API
luakt 是 AsLua 的 Java / Kotlin 桥接库,当前导出的函数包括:
| 函数 | 说明 |
|---|---|
luakt.bindClass(name) | 绑定 Java 类名,返回 Class 对象 |
luakt.new(class, ...) | 根据 Class 和参数创建 Java 对象 |
luakt.newInstance(name, ...) | 根据类名创建 Java 对象 |
luakt.loadLib(class, method) | 调用 Java 静态 open 方法注册 Lua 扩展 |
luakt.createProxy(interface, table) | 通过 Lua table 创建 Java interface 代理 |
luakt.newArray(class, ...) | 创建 Java 数组 |
luakt.createArray(className, table) | 通过 Lua table 创建 Java 数组 |
luakt.astable(object) | 将 Java 数组、集合或 Map 转成 Lua table |
luakt.tostring(object) | 调用 Java 对象字符串表示 |
luakt.coding(text, encoding) | 按指定编码处理字符串 |
luakt.clear(object) | 清理 Java 对象引用 |
luakt.instanceof(object, class) | 判断 Java 对象是否是指定类型 |
luakt.getContext() | 获取当前 LuaContext |
绑定类
local StringBuilder = luakt.bindClass("java.lang.StringBuilder")
local builder = StringBuilder()
builder.append("Hello")
builder.append(" ")
builder.append("AsLua")
print(builder.toString())通过 require "import" 后,也可以这样写:
require "import"
import "java.lang.StringBuilder"
local builder = StringBuilder()
builder.append("Hello")
builder.append(" AsLua")
print(builder.toString())创建对象
Java 类对象可以直接调用:
local File = luakt.bindClass("java.io.File")
local file = File(luakt.luadir, "main.lua")
print(file.exists())也可以显式使用 luakt.new:
local File = luakt.bindClass("java.io.File")
local file = luakt.new(File, luakt.luadir, "main.lua")使用类名创建:
local file = luakt.newInstance("java.io.File", luakt.luadir, "main.lua")调用方法和字段
LuaKt 会通过 Java 反射查找方法、字段和 getter:
require "import"
import "android.widget.TextView"
local view = TextView(activity)
view.setText("AsLua")
print(view.getText())静态字段也可以访问:
require "import"
import "android.graphics.Color"
print(Color.RED)Java 数组与 table
把 Java 数组、集合或 Map 转成 Lua table:
require "import"
local list = activity.getClassLoaders()
local tableList = luakt.astable(list)
for index, loader in ipairs(tableList) do
print(index, loader)
end创建 Java 数组:
local array = luakt.createArray("java.lang.String", {
"Lua",
"LuaJIT",
"AsLua"
})Android UI
直接创建 View
require "import"
import "android.widget.TextView"
import "android.view.Gravity"
local title = TextView(activity)
title.setText("Hello AsLua")
title.setTextSize(22)
title.setGravity(Gravity.CENTER)
activity.setContentView(title)使用 loadlayout
loadlayout.lua 支持用 Lua table 描述 Android View 层级:
require "import"
import "android.widget.*"
import "android.view.*"
local layout = {
LinearLayout,
orientation = "vertical",
layout_width = "match",
layout_height = "match",
padding = "16dp",
{
TextView,
text = "AsLua",
textSize = "22sp",
gravity = "center"
},
{
Button,
text = "Click",
onClick = function(view)
print("clicked")
end
}
}
activity.setContentView(loadlayout(layout))loadlayout 会处理常见属性值,例如:
| 写法 | 含义 |
|---|---|
"match" / "match_parent" | MATCH_PARENT |
"wrap" / "wrap_content" | WRAP_CONTENT |
"vertical" | 垂直方向 |
"horizontal" | 水平方向 |
"center" | 居中 |
"gone" | 隐藏且不占位 |
Activity 生命周期回调
LuaActivity 会在对应 Android 生命周期中调用 Lua 全局函数:
| Lua 函数 | 调用时机 |
|---|---|
onStart() | Activity onStart |
onResume() | Activity onResume |
onPause() | Activity onPause |
onStop() | Activity onStop |
onDestroy() | Activity onDestroy |
onActivityResult(requestCode, resultCode, data) | Activity result |
onResult(name, ...) | 子页面通过 activity.result() 返回 |
onNewIntent(intent) | Main 收到新 Intent |
onVersionChanged(newVersionName, oldVersionName) | 版本变化 |
onConfigurationChanged(config) | 配置变化 |
示例:
function onResume()
print("页面恢复")
end
function onDestroy()
print("页面销毁")
end输入与菜单回调
AsLua 会缓存以下按键和触摸回调:
| Lua 函数 | 说明 |
|---|---|
onKeyShortcut(keyCode, event) | 快捷键 |
onKeyDown(keyCode, event) | 按键按下 |
onKeyUp(keyCode, event) | 按键抬起 |
onKeyLongPress(keyCode, event) | 长按 |
onTouchEvent(event) | 触摸事件 |
返回 true 可以拦截事件:
function onKeyDown(keyCode, event)
print("key down:", keyCode)
return false
end菜单相关回调:
| Lua 函数 | 说明 |
|---|---|
onCreateOptionsMenu(menu) | 创建选项菜单 |
onOptionsItemSelected(item) | 菜单项点击 |
onCreateContextMenu(menu, view, menuInfo) | 创建上下文菜单 |
onContextItemSelected(item) | 上下文菜单项点击 |
页面跳转
activity.newActivity() 可以打开另一个 Lua 页面。
activity.newActivity("pages/detail")如果路径不是绝对路径,会基于当前 luadir 查找。未带 .lua 后缀时会自动补齐。
带参数:
activity.newActivity("pages/detail", { "hello", 123 })在目标页面读取参数:
local first = activity.getArg(0)
local second = activity.getArg(1)
print(first, second)返回结果:
activity.result({ "ok", 100 })父页面接收:
function onResult(name, status, value)
print(name, status, value)
return true
endService
activity.startService() 启动 LuaService:
activity.startService()指定脚本:
activity.startService("service/main")带参数:
activity.startService("service/main", { "sync" })绑定服务:
activity.bindService(0)
function onServiceConnected(component, service)
print("service connected", service)
end
function onServiceDisconnected(component)
print("service disconnected", component)
end广播接收
activity.registerReceiver(filter) 会注册接收器,并在收到广播时调用:
function onReceive(context, intent)
print("receive:", intent.getAction())
end共享数据
LuaContext 提供共享数据读写:
activity.setSharedData("token", "abc")
local token = activity.getSharedData("token")
print(token)带默认值:
local theme = activity.getSharedData("theme", "system")activity.getGlobalData() 返回全局 Map:
local data = activity.getGlobalData()
data.put("count", 1)文件路径
常用路径方法:
| 方法 | 说明 |
|---|---|
activity.getLuaDir() | 当前 Lua 项目目录 |
activity.getLuaDir(name) | 当前项目下子目录,不存在则创建 |
activity.getLuaPath() | 当前 Lua 文件路径 |
activity.getLuaPath(path) | 当前项目下文件路径 |
activity.getLuaPath(dir, name) | 当前项目子目录下文件路径 |
activity.getLuaExtDir() | 外部 AsLua 工作目录 |
activity.getLuaExtDir(name) | 外部工作目录下子目录 |
activity.getLuaExtPath(path) | 外部工作目录下文件路径 |
activity.getLuaLpath() | Lua 模块搜索路径 |
activity.getLuaCpath() | C 模块搜索路径 |
示例:
local path = activity.getLuaPath("config.json")
local file = io.open(path, "r")
if file then
local text = file:read("*a")
file:close()
print(text)
end内置 Lua 模块
AsLua 在 src/main/resources/lua 中内置了一批 Lua 模块,可以通过 require 使用:
| 模块 | 说明 |
|---|---|
import | Java 类导入和常用环境初始化 |
loadlayout | Lua table / aly 布局加载 |
loadbitmap | 图片加载 |
console | 控制台辅助 |
socket | LuaSocket |
mime | MIME 工具 |
ltn12 | LuaSocket 流工具 |
ftp | FTP |
smtp | SMTP |
xml | XML |
protoc | Protocol Buffers 辅助 |
luaunit | Lua 单元测试 |
lanes | 多线程相关模块 |
示例:
local socket = require "socket"
print(socket._VERSION)内置 native 扩展
AsLua 的 JNI 目录中包含多种 Lua C 扩展,常见包括:
| 扩展 | 说明 |
|---|---|
cjson | JSON 编解码 |
lsqlite3 | SQLite |
lpeg | 解析表达式语法 |
md5 | MD5 |
sha256 | SHA-256 |
socket / mime | 网络与 MIME |
zlib | 压缩 |
zip | ZIP |
bson | BSON |
url | URL 处理 |
xml | XML |
luv | libuv 绑定 |
canvas | Canvas 相关 |
sensor | 传感器相关 |
simdjson / yyjson | JSON 解析 |
lua-protobuf | Protocol Buffers |
具体能否直接 require 取决于构建产物是否已打包到应用 native library 目录或私有 lib 目录。
local cjson = require "cjson"
local text = cjson.encode({
name = "AsLua",
runtime = "LuaJIT"
})
print(text)LuaJIT 基础注意点
Lua 5.1 兼容
LuaJIT 兼容 Lua 5.1,数组默认从 1 开始:
local list = { "Lua", "LuaJIT", "AsLua" }
print(list[1])只有 false 和 nil 是假值:
if 0 then
print("0 在 Lua 中是真值")
end优先使用 local
local TextView = luakt.bindClass("android.widget.TextView")
local view = TextView(activity)少用无意的全局变量:
-- 不推荐
view = TextView(activity)
-- 推荐
local view = TextView(activity)require 会缓存模块
local log = require "modules.log"如果调试时要重新加载:
package.loaded["modules.log"] = nil
local log = require "modules.log"协程与线程
Lua 协程是协作式调度,不是 Android 系统线程:
local co = coroutine.create(function()
print("step 1")
coroutine.yield()
print("step 2")
end)
coroutine.resume(co)
coroutine.resume(co)AsLua 项目中另有 LuaThread、LuaAsyncTask、LuaTimer、LuaRunnable 等 Java 组件用于异步和定时场景。跨线程调用 Lua 状态时要注意生命周期和同步。
错误处理
AsLua 执行 Lua 文件和 Lua 函数时会使用 debug.traceback 包装错误。脚本侧仍然建议用 pcall 保护可恢复错误:
local ok, result = pcall(function()
return require "missing_module"
end)
if not ok then
print("load failed:", result)
end定义 onError 可以接收 AsLua 分发的错误:
function onError(title, message)
print("error:", title, message)
return true
end调试建议
打印当前路径:
print("luadir", luakt.luadir)
print("luapath", luakt.luapath)
print("luaextdir", luakt.luaextdir)
print("package.path", package.path)
print("package.cpath", package.cpath)查看 Java 对象类型:
local view = activity.getDecorView()
print(luakt.tostring(view))
print(view.getClass().getName())查看堆栈:
print(debug.traceback())推荐项目结构
main.lua
config.lua
modules/
log.lua
android.lua
json.lua
pages/
detail.lua
layouts/
main.lua
assets/
icon.png推荐入口写法:
require "import"
local log = require "modules.log"
function onCreate()
log.info("created")
end
log.info("main loaded")注意:LuaActivity 当前不会自动调用 Lua 的 onCreate(),入口文件本身就是创建阶段执行的代码。如果需要统一初始化,可以在 main.lua 末尾主动调用:
if onCreate then
onCreate()
end常见问题
为什么找不到 Java 类?
先确认是否执行了:
require "import"然后确认类名是否完整:
local TextView = luakt.bindClass("android.widget.TextView")内部类可以用 $:
local OnClickListener = luakt.bindClass("android.view.View$OnClickListener")import.lua 也会把类名里的 _ 替换为 $,用于兼容内部类写法。
为什么 require 找不到模块?
检查:
- 文件是否在当前项目目录或内置 Lua 目录。
- 模块名是否和路径一致。
package.path是否包含目标目录。- 模块文件是否返回了值。
print(package.path)为什么修改模块后没有生效?
require 会缓存模块。调试时可以清理:
package.loaded["modules.log"] = nil什么时候用 luakt.bindClass,什么时候用 import?
简单脚本推荐:
require "import"
import "android.widget.TextView"框架层或工具模块推荐显式绑定,依赖更清楚:
local TextView = luakt.bindClass("android.widget.TextView")可以直接用 FFI 吗?
LuaJIT 支持 FFI,但 Android 下更推荐优先使用 AsLua 已编译的 native 扩展或 Java / Kotlin 桥。FFI 需要自己保证 C ABI、动态库路径、内存生命周期和崩溃风险。
local ffi = require "ffi"
ffi.cdef[[
int abs(int n);
]]
print(ffi.C.abs(-1))print 输出到哪里?
print 被 LuaActivity 注册为 LuaPrint,会输出到 AsLua 默认日志界面;如果脚本设置了自己的 UI,也可以继续用它做调试输出。
下一步
- 阅读 LuaKt 文档,了解 Java / Kotlin 桥接细节。
- 从
main.lua开始,先完成require "import"、页面布局和生命周期回调。 - 将 Java 类导入、日志、布局和业务逻辑拆成模块,避免入口文件越来越重。