Skip to content

LuaJIT 使用文档

AsLua 使用 LuaJIT 作为脚本运行时,并在启动 Lua 状态时注入 Android 上下文、LuaKt Java 桥、项目路径和常用辅助函数。开发者通常只需要编写 main.lua,然后通过 activityluaktimportloadlayout 等能力访问 Android 和 Java / Kotlin API。

本文基于 AsLua 项目当前实现编写,源码入口主要包括:

  • com.aslua.Main
  • com.aslua.LuaActivity
  • com.aslua.LuaApplication
  • com.luakt.LuaState
  • com.luakt.LuaKtAPI
  • src/main/resources/lua/import.lua
  • src/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

lua
require "import"

print("Hello AsLua")
print("Lua path:", luakt.luapath)
print("Lua dir:", luakt.luadir)

启动后,LuaActivity 会创建默认日志界面。如果脚本没有主动设置内容视图,日志列表会作为默认界面显示。

启动流程

AsLua 的 Lua 脚本执行流程如下:

  1. LuaApplication 初始化应用目录、Lua 模块路径和 C 模块路径。
  2. Main.onCreate() 调用 LuaActivity.onCreate()
  3. LuaActivity 解析要执行的 Lua 文件,默认是 localDir/main.lua
  4. LuaActivity.initLua() 创建 LuaState,打开 Lua 标准库和 LuaKt 库。
  5. 注入全局对象与路径信息。
  6. 设置 package.pathpackage.cpath
  7. 执行 main.lua
  8. 根据 Activity 生命周期调用 Lua 中同名回调函数。

内置全局对象

LuaActivity.initLua() 会向 Lua 环境注入以下对象:

名称类型说明
activityLuaActivity Java 对象当前 Activity,也是主要 Android 上下文
thisLuaActivity Java 对象activity 的别名
luaktLuaKt 库 tableJava / Kotlin 桥接库
printJava 注册函数输出到 AsLua 日志界面
setJava 注册函数LuaThread 设置变量
callJava 注册函数调用 LuaThread 中的函数

activity 被保存到 Lua registry 的 _LuaContext 中,luakt.getContext() 可以取回当前 LuaContext

lua
local context = luakt.getContext()

print(context.getLuaDir())
print(context.getWidth(), context.getHeight())

luakt 路径字段

初始化时,AsLua 会给 luakt 设置几个路径字段:

字段说明
luakt.luaextdir外部 AsLua 工作目录
luakt.luadir当前 Lua 项目目录
luakt.luapath当前执行的 Lua 文件路径

示例:

lua
print(luakt.luaextdir)
print(luakt.luadir)
print(luakt.luapath)

模块搜索路径

AsLua 会设置 package.path

txt
当前项目/?.lua;
当前项目/lua/?.lua;
当前项目/?/init.lua;
内置 lua 目录/?.lua;
内置 lua 目录/lua/?.lua;
内置 lua 目录/?/init.lua;

也会设置 package.cpath

txt
应用 nativeLibraryDir/lib?.so;
应用私有 lib 目录/lib?.so

因此你可以直接加载项目内模块:

lua
local log = require "modules.log"

目录示例:

txt
main.lua
modules/
  log.lua
  user.lua

modules/log.lua

lua
local log = {}

function log.info(message)
  print("[INFO] " .. tostring(message))
end

return log

使用 import

AsLua 内置 import.lua,建议在大多数脚本开头加载:

lua
require "import"

加载后会提供:

名称说明
import导入 Java 类、Lua 模块、dex 类或包
loadlayout加载 Lua table / aly 布局
loadbitmap加载图片
android.RAndroid 系统资源类
R当前应用资源类
MD3_RMaterial 组件资源类
luajava兼容对象,包含 bindClass = luakt.bindClass

import 默认会尝试从以下包中查找类:

txt
java.lang.
java.util.
com.aslua.

导入单个类:

lua
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)

导入包:

lua
require "import"

import "android.widget.*"

local button = Button(activity)
button.setText("Click")

导入 Lua 模块:

lua
require "import"

import "loadlayout"

从 dex 加载类:

lua
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

绑定类

lua
local StringBuilder = luakt.bindClass("java.lang.StringBuilder")
local builder = StringBuilder()

builder.append("Hello")
builder.append(" ")
builder.append("AsLua")

print(builder.toString())

通过 require "import" 后,也可以这样写:

lua
require "import"

import "java.lang.StringBuilder"

local builder = StringBuilder()
builder.append("Hello")
builder.append(" AsLua")

print(builder.toString())

创建对象

Java 类对象可以直接调用:

lua
local File = luakt.bindClass("java.io.File")
local file = File(luakt.luadir, "main.lua")

print(file.exists())

也可以显式使用 luakt.new

lua
local File = luakt.bindClass("java.io.File")
local file = luakt.new(File, luakt.luadir, "main.lua")

使用类名创建:

lua
local file = luakt.newInstance("java.io.File", luakt.luadir, "main.lua")

调用方法和字段

LuaKt 会通过 Java 反射查找方法、字段和 getter:

lua
require "import"

import "android.widget.TextView"

local view = TextView(activity)
view.setText("AsLua")

print(view.getText())

静态字段也可以访问:

lua
require "import"

import "android.graphics.Color"

print(Color.RED)

Java 数组与 table

把 Java 数组、集合或 Map 转成 Lua table:

lua
require "import"

local list = activity.getClassLoaders()
local tableList = luakt.astable(list)

for index, loader in ipairs(tableList) do
  print(index, loader)
end

创建 Java 数组:

lua
local array = luakt.createArray("java.lang.String", {
  "Lua",
  "LuaJIT",
  "AsLua"
})

Android UI

直接创建 View

lua
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 层级:

lua
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)配置变化

示例:

lua
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 可以拦截事件:

lua
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 页面。

lua
activity.newActivity("pages/detail")

如果路径不是绝对路径,会基于当前 luadir 查找。未带 .lua 后缀时会自动补齐。

带参数:

lua
activity.newActivity("pages/detail", { "hello", 123 })

在目标页面读取参数:

lua
local first = activity.getArg(0)
local second = activity.getArg(1)

print(first, second)

返回结果:

lua
activity.result({ "ok", 100 })

父页面接收:

lua
function onResult(name, status, value)
  print(name, status, value)
  return true
end

Service

activity.startService() 启动 LuaService

lua
activity.startService()

指定脚本:

lua
activity.startService("service/main")

带参数:

lua
activity.startService("service/main", { "sync" })

绑定服务:

lua
activity.bindService(0)

function onServiceConnected(component, service)
  print("service connected", service)
end

function onServiceDisconnected(component)
  print("service disconnected", component)
end

广播接收

activity.registerReceiver(filter) 会注册接收器,并在收到广播时调用:

lua
function onReceive(context, intent)
  print("receive:", intent.getAction())
end

共享数据

LuaContext 提供共享数据读写:

lua
activity.setSharedData("token", "abc")

local token = activity.getSharedData("token")
print(token)

带默认值:

lua
local theme = activity.getSharedData("theme", "system")

activity.getGlobalData() 返回全局 Map:

lua
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 模块搜索路径

示例:

lua
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 使用:

模块说明
importJava 类导入和常用环境初始化
loadlayoutLua table / aly 布局加载
loadbitmap图片加载
console控制台辅助
socketLuaSocket
mimeMIME 工具
ltn12LuaSocket 流工具
ftpFTP
smtpSMTP
xmlXML
protocProtocol Buffers 辅助
luaunitLua 单元测试
lanes多线程相关模块

示例:

lua
local socket = require "socket"
print(socket._VERSION)

内置 native 扩展

AsLua 的 JNI 目录中包含多种 Lua C 扩展,常见包括:

扩展说明
cjsonJSON 编解码
lsqlite3SQLite
lpeg解析表达式语法
md5MD5
sha256SHA-256
socket / mime网络与 MIME
zlib压缩
zipZIP
bsonBSON
urlURL 处理
xmlXML
luvlibuv 绑定
canvasCanvas 相关
sensor传感器相关
simdjson / yyjsonJSON 解析
lua-protobufProtocol Buffers

具体能否直接 require 取决于构建产物是否已打包到应用 native library 目录或私有 lib 目录。

lua
local cjson = require "cjson"

local text = cjson.encode({
  name = "AsLua",
  runtime = "LuaJIT"
})

print(text)

LuaJIT 基础注意点

Lua 5.1 兼容

LuaJIT 兼容 Lua 5.1,数组默认从 1 开始:

lua
local list = { "Lua", "LuaJIT", "AsLua" }

print(list[1])

只有 falsenil 是假值:

lua
if 0 then
  print("0 在 Lua 中是真值")
end

优先使用 local

lua
local TextView = luakt.bindClass("android.widget.TextView")
local view = TextView(activity)

少用无意的全局变量:

lua
-- 不推荐
view = TextView(activity)

-- 推荐
local view = TextView(activity)

require 会缓存模块

lua
local log = require "modules.log"

如果调试时要重新加载:

lua
package.loaded["modules.log"] = nil
local log = require "modules.log"

协程与线程

Lua 协程是协作式调度,不是 Android 系统线程:

lua
local co = coroutine.create(function()
  print("step 1")
  coroutine.yield()
  print("step 2")
end)

coroutine.resume(co)
coroutine.resume(co)

AsLua 项目中另有 LuaThreadLuaAsyncTaskLuaTimerLuaRunnable 等 Java 组件用于异步和定时场景。跨线程调用 Lua 状态时要注意生命周期和同步。

错误处理

AsLua 执行 Lua 文件和 Lua 函数时会使用 debug.traceback 包装错误。脚本侧仍然建议用 pcall 保护可恢复错误:

lua
local ok, result = pcall(function()
  return require "missing_module"
end)

if not ok then
  print("load failed:", result)
end

定义 onError 可以接收 AsLua 分发的错误:

lua
function onError(title, message)
  print("error:", title, message)
  return true
end

调试建议

打印当前路径:

lua
print("luadir", luakt.luadir)
print("luapath", luakt.luapath)
print("luaextdir", luakt.luaextdir)
print("package.path", package.path)
print("package.cpath", package.cpath)

查看 Java 对象类型:

lua
local view = activity.getDecorView()

print(luakt.tostring(view))
print(view.getClass().getName())

查看堆栈:

lua
print(debug.traceback())

推荐项目结构

txt
main.lua
config.lua
modules/
  log.lua
  android.lua
  json.lua
pages/
  detail.lua
layouts/
  main.lua
assets/
  icon.png

推荐入口写法:

lua
require "import"

local log = require "modules.log"

function onCreate()
  log.info("created")
end

log.info("main loaded")

注意:LuaActivity 当前不会自动调用 Lua 的 onCreate(),入口文件本身就是创建阶段执行的代码。如果需要统一初始化,可以在 main.lua 末尾主动调用:

lua
if onCreate then
  onCreate()
end

常见问题

为什么找不到 Java 类?

先确认是否执行了:

lua
require "import"

然后确认类名是否完整:

lua
local TextView = luakt.bindClass("android.widget.TextView")

内部类可以用 $

lua
local OnClickListener = luakt.bindClass("android.view.View$OnClickListener")

import.lua 也会把类名里的 _ 替换为 $,用于兼容内部类写法。

为什么 require 找不到模块?

检查:

  • 文件是否在当前项目目录或内置 Lua 目录。
  • 模块名是否和路径一致。
  • package.path 是否包含目标目录。
  • 模块文件是否返回了值。
lua
print(package.path)

为什么修改模块后没有生效?

require 会缓存模块。调试时可以清理:

lua
package.loaded["modules.log"] = nil

什么时候用 luakt.bindClass,什么时候用 import?

简单脚本推荐:

lua
require "import"
import "android.widget.TextView"

框架层或工具模块推荐显式绑定,依赖更清楚:

lua
local TextView = luakt.bindClass("android.widget.TextView")

可以直接用 FFI 吗?

LuaJIT 支持 FFI,但 Android 下更推荐优先使用 AsLua 已编译的 native 扩展或 Java / Kotlin 桥。FFI 需要自己保证 C ABI、动态库路径、内存生命周期和崩溃风险。

lua
local ffi = require "ffi"

ffi.cdef[[
int abs(int n);
]]

print(ffi.C.abs(-1))

printLuaActivity 注册为 LuaPrint,会输出到 AsLua 默认日志界面;如果脚本设置了自己的 UI,也可以继续用它做调试输出。

下一步

  • 阅读 LuaKt 文档,了解 Java / Kotlin 桥接细节。
  • main.lua 开始,先完成 require "import"、页面布局和生命周期回调。
  • 将 Java 类导入、日志、布局和业务逻辑拆成模块,避免入口文件越来越重。