Lua 插件系统
约 3226 字大约 11 分钟
2026-08-01
FolkPatch 提供了一套轻量级 Lua 插件系统,插件是介于 APM 与 KPM 之间的扩展形态:不修改系统文件、不注入内核,只在 apd 守护进程的生命周期中运行 Lua 回调。
插件基于 mlua(Lua 5.4)实现,以 root 权限运行,适合编写定时任务、属性调整、缓存清理等轻量级自动化脚本。
安全提示
Lua 插件以 root 权限运行,且 Lua 标准库完全可用(可执行任意命令、读写任意文件)。请只安装来源可信的插件,安装前务必审查代码。
插件 vs APM vs KPM
| 对比项 | APM | Lua 插件 | KPM |
|---|---|---|---|
| 运行空间 | 用户空间 | 用户空间(apd 内) | 内核空间 |
| 修改系统文件 | 是(挂载) | 否 | 否 |
| 注入内核 | 否 | 否 | 是 |
| 实现语言 | Shell / 任意 | Lua | C |
| 典型用途 | 系统级修改、替换文件 | 定时任务、属性调整、自动化脚本 | 内核 Hook、底层定制 |
| 风险等级 | 中 | 中 | 高 |
插件包格式
插件以 zip 包或目录形式安装,安装后位于 /data/adb/plugins/<id>/。zip 包结构:
my-plugin.zip
plugin.json# 清单文件(必需,声明元数据与依赖)
main.lua# 入口脚本(必需,或由清单 entry 指定)
目录识别
zip 内的文件可置于根目录或单一顶层目录,安装时会自动识别入口文件所在的目录并提取。
清单文件 plugin.json
plugin.json 声明插件的元数据、依赖、入口与配置项:
{
"id": "my-plugin",
"name": "My Plugin",
"author": "your-name",
"version": "1.0.0",
"description": "What this plugin does",
"descriptions": { "zh": "插件功能描述", "ja": "プラグインの説明" },
"license": "MIT",
"min_version": 0,
"depends": ["another-plugin"],
"entry": "main.lua",
"config": [],
"quick_action": null
}idRequiredstring
插件标识,须与目录名一致,仅限字母数字与 -_.。
nameOptionalstring
显示名称,缺省回退为 id。
authorOptionalstring
作者。
versionOptionalstring
版本号。
descriptionOptionalstring
描述。
descriptionsOptionalobject
本地化描述,键为语言代码(如 zh、ja、tr)。
licenseOptionalstring
许可证。
min_versionOptionalnumber
要求的 APD 最低版本码(数字)。安装时校验,当前版本低于该值将拒绝安装。
dependsOptionalstring[]
依赖的其他插件 id 列表,安装与运行时都会校验(依赖缺失或被停用将报错)。
entryOptionalstring
入口 Lua 文件,缺省为 main.lua。不允许包含路径分隔符或 ..。
configOptionalConfigField[]
声明的配置项列表,管理端会显示「配置」按钮。详见 用户配置。
quick_actionOptionalQuickAction
快捷操作:管理端显示的一键按钮,运行指定回调。详见 Action 操作。
入口脚本 main.lua
main.lua 必须返回一个 Lua table,可选声明生命周期回调:
return {
post_fs_data = function()
info("plugin loaded")
end,
post_mount = function()
-- 模块挂载完成后
end,
service = function()
-- service 阶段适合轻量的后台初始化
end,
boot_completed = function()
-- Android 启动完毕后
end,
}运行时环境提供以下全局变量:
PLUGIN_ID:当前插件标识PLUGIN_DIR:当前插件目录(如/data/adb/plugins/my-plugin)
info() / warn() 输出的日志会追加到插件目录下的 last_output.log。
插件 API
插件运行环境在 Lua 标准库之外额外提供以下函数:
| 函数 | 说明 |
|---|---|
getprop(name) | 读取系统属性,返回字符串 |
setprop(name, value) | 设置系统属性(绕过只读限制),返回布尔 |
sysctl(key, value) | 写内核参数(如 sysctl("vm.swappiness", "60")),返回布尔 |
| 函数 | 说明 |
|---|---|
exec(...) | 运行命令,返回 { ok, code, stdout, stderr } 表。可用 exec("cmd", "arg") 或 exec("sh", "-c", "...") |
| 函数 | 说明 |
|---|---|
write_file(path, content) | 写入文本文件(自动创建父目录),返回布尔 |
read_file(path) | 读取文件内容,返回字符串 |
list_dir(path) | 列出目录条目,返回名称数组 table |
file_exists(path) | 判断路径是否存在(文件或目录),返回布尔 |
chmod(path, mode) | 设置权限(数字或字符串,如 0755 或 "0755"),返回布尔 |
mkdir(path, recursive) | 创建目录,recursive 为 true 时递归创建,返回布尔 |
rm(path) | 删除文件或空目录,返回布尔 |
| 函数 | 说明 |
|---|---|
json_decode(str) | 解析 JSON 字符串为 Lua 值 |
json_encode(value) | 序列化 Lua 值为 JSON 字符串 |
| 函数 | 说明 |
|---|---|
get_config(key) | 读取本插件保存的用户配置值(字符串) |
set_config(key, value) | 保存本插件的一个用户配置值,返回布尔 |
info(msg) | 输出信息日志 |
warn(msg) | 输出警告日志 |
| 函数 | 说明 |
|---|---|
start_daemon(function, interval_secs) | 把指定回调作为后台守护进程,每隔 interval_secs 秒循环执行,返回布尔 |
exec 使用示例
local result = exec("sh", "-c", "df -h /data")
if result.ok then
info("stdout: " .. result.stdout)
else
warn("failed, code=" .. result.code .. ", stderr=" .. result.stderr)
endAction 操作
插件可声明 action 回调,在管理端显示「运行」按钮,用于手动执行一个操作(如手动清理缓存):
return {
action = function()
-- 手动触发的操作逻辑
info("manual clean triggered")
end,
}命令行触发:
apd plugin action <id>此外,还可在 plugin.json 中通过 quick_action 声明按钮标签:
{
"quick_action": {
"function": "clean_now",
"label": "Clean Now",
"labels": { "zh": "立即清理", "ja": "今すぐクリーン" }
}
}用户配置
插件可在 plugin.json 中声明配置项,管理端会显示「配置」按钮弹出编辑框,配置保存到 /data/adb/plugins/<id>/config.json:
{
"id": "my-plugin",
"config": [
{ "key": "clean_hour", "label": "Cleaning hour (0-23)", "labels": { "zh": "清理时间" }, "type": "number", "default": 4 },
{ "key": "clear_all", "label": "Also clear running app caches", "labels": { "zh": "同时清理运行中应用缓存" }, "type": "bool", "default": false },
{ "key": "mode", "label": "Mode", "type": "select", "options": ["auto", "manual"] }
]
}keyRequiredstring
配置键,供 get_config / set_config 使用。
labelOptionalstring
人类可读标签(默认 / 英文)。
labelsOptionalobject
本地化标签,键为语言代码。
typeOptionalstring
text
字段类型:text / number / bool / select。
defaultOptionalany
默认值。
optionsOptionalstring[]
select 类型的选项列表。
插件内用 get_config / set_config 读写配置,命令行:
apd plugin config --id my-plugin list
apd plugin config --id my-plugin get clean_hour
apd plugin config --id my-plugin set clean_hour 3
apd plugin config --id my-plugin delete clean_hour定时循环事件
插件支持两种定时循环方式:
1. 声明 main 回调(推荐)
插件返回的 table 若包含 main 函数,系统会在 service 阶段自动将其作为后台守护进程启动,循环执行(默认间隔 1 秒,可在循环内自行 sleep 控制节奏):
return {
main = function()
while true do
-- 每分钟检查一次
if os.time() % 60 == 0 then
info("tick")
end
os.execute("sleep 30")
end
end,
}main 守护进程独立于 apd 启动流程运行,不会阻塞其他插件,也不随单个阶段调用退出。
2. start_daemon
在任意回调里手动启动后台循环:
return {
service = function()
start_daemon("main", 60) -- 每 60 秒调用一次本插件的 main
end,
main = function()
-- 定时任务逻辑
end,
}停止守护进程
停用插件(apd plugin disable <id>)后重启,守护进程不再启动;已运行的 daemon 可用 kill 终止。
手动测试定时循环:
apd plugin daemon <id> <function> <interval_secs>生命周期与状态
- 创建空文件
disable可停用插件,删除即可恢复 - 插件在
post-fs-data、post-mount、service、boot-completed阶段被apd自动执行 - 单个插件出错会被记录(写入日志),不影响其他插件
管理命令
apd plugin list # 列出插件(JSON,含元数据、action、配置项)
apd plugin install <zip> # 从 zip 安装
apd plugin uninstall <id> # 卸载
apd plugin enable <id> # 启用
apd plugin disable <id> # 停用
apd plugin run <id> <function> # 手动运行某个回调
apd plugin action <id> # 运行插件的 action 回调
apd plugin daemon <id> <function> <secs> # 以守护进程循环运行某回调
apd plugin config --id <id> ... # 查看/修改插件配置(list/get/set/delete)插件管理页面
插件管理入口位于 设置页顶部栏的插件按钮。页面提供:
- 启用 / 停用开关:切换插件状态(对应创建/删除
disable文件) - 运行:手动触发插件的
action回调(或清单声明的quick_action) - 配置:编辑插件声明的配置项
- 执行输出:查看插件的
last_output.log日志,支持导出与清除 - 卸载:删除插件
- 在线插件:浏览并下载官方插件仓库中的插件,详见 在线插件
在线插件
插件管理页面内置在线插件功能,可从 FolkPatch 官方插件仓库浏览并下载安装插件:
- 支持按名称 / 描述搜索插件
- 支持中英文描述自动切换
- 点击下载后通过通知栏查看进度,下载完成后在插件页面安装
注意
在线插件同样以 root 权限运行,下载前请确认插件来源与功能说明。
示例插件
FolkPatch 仓库 examples/plugins/ 目录内提供五个成品示例:
boot-script/:开机脚本插件,在指定启动阶段执行自定义命令,支持等待路径就绪与手动立即执行cache-cleaner/:定时清缓存插件,每天凌晨自动清理应用与系统缓存hello-plugin/:演示getprop、setprop、exec、文件读写等基础 APIfile-tick-test/:文件操作与定时循环测试插件log-rotator/:日志轮转插件
定时清缓存使用示例:
apd plugin install /path/to/cache-cleaner.zip # 或直接放入目录
apd plugin run cache-cleaner clean_now # 立即手动清理一次
apd plugin action cache-cleaner # 或点击管理端的「运行」按钮配置项在管理端点「配置」按钮编辑(清理时间、是否清理运行中应用缓存),或直接改 config.json。该插件声明了 action 和 main 回调:action 供手动触发,main 开机后作为后台守护进程自动运行。
完整示例:boot-script 开机脚本
以下是官方 boot-script 示例插件的完整源码,演示了清单声明、配置项、多阶段生命周期、quick_action 与 API 调用的综合运用:在用户选择的启动阶段执行自定义命令,支持等待指定路径就绪后再执行。
plugin.json
main.lua
{
"id": "boot-script",
"name": "Boot Script",
"author": "FolkPatch",
"version": "1.1.0",
"description": "Run custom commands at a chosen boot stage",
"descriptions": {
"zh": "在指定的启动阶段执行自定义命令",
"ja": "指定した起動段階でカスタムコマンドを実行"
},
"license": "MIT",
"quick_action": {
"function": "run_now",
"label": "Run now",
"labels": { "zh": "立即执行", "ja": "今すぐ実行" }
},
"config": [
{
"key": "stage",
"label": "Execution stage",
"labels": { "zh": "执行时机" },
"type": "select",
"options": ["post_fs_data", "service", "boot_completed"],
"default": "boot_completed"
},
{
"key": "wait_path",
"label": "Path to wait for (empty = off)",
"labels": { "zh": "等待就绪的路径(留空=关闭)" },
"type": "text",
"default": "/sdcard"
},
{
"key": "wait_time",
"label": "Wait timeout (seconds, 0 = off)",
"labels": { "zh": "等待超时(秒,0=关闭)" },
"type": "number",
"default": 0
},
{
"key": "commands",
"label": "Commands (one per line)",
"labels": { "zh": "命令列表(每行一条)" },
"type": "text",
"default": ""
}
]
}local M = {}
-- Wait up to N seconds for a path to become ready (e.g. /sdcard after unlock)
local function wait_for_path(path, timeout_sec)
local waited = 0
while waited < timeout_sec do
local r = exec("test", "-d", path)
if r.ok then
info("Path ready after " .. waited .. "s: " .. path)
return true
end
exec("sleep", "1")
waited = waited + 1
end
warn("Timeout waiting for path (" .. timeout_sec .. "s): " .. path)
return false
end
local function run_commands()
local cmds = get_config("commands")
if cmds == "" then
info("No commands configured")
return
end
for line in cmds:gmatch("[^\n]+") do
line = line:match("^%s*(.-)%s*$")
if line ~= "" and line:sub(1, 1) ~= "#" then
info("Running: " .. line)
local r = exec("sh", "-c", line)
if r.ok then
if r.stdout ~= "" then info(" stdout: " .. r.stdout) end
else
warn(" failed (code " .. r.code .. "): " .. r.stderr)
end
end
end
info("All commands executed")
end
local function try_run(stage)
local target = get_config("stage")
if target == "" then target = "boot_completed" end
if stage == target then
info("Stage [" .. stage .. "] matched, executing...")
-- If configured, wait for a path to be ready first
local wait_path = get_config("wait_path")
local wait_time = tonumber(get_config("wait_time")) or 0
if wait_path ~= "" and wait_time > 0 then
wait_for_path(wait_path, wait_time)
end
run_commands()
end
end
function M.post_fs_data()
try_run("post_fs_data")
end
function M.service()
try_run("service")
end
function M.boot_completed()
try_run("boot_completed")
end
-- Quick action: run manually regardless of stage
function M.run_now()
run_commands()
end
return M要点解读
- 清单中声明了 4 个配置项(执行阶段、等待路径、等待超时、命令列表),用户在管理端「配置」中编辑
quick_action声明了「立即执行」按钮,绑定run_now回调,可随时手动触发main.lua同时声明post_fs_data/service/boot_completed三个生命周期回调,通过try_run判断当前阶段是否与配置匹配,匹配时才执行命令- 使用了
exec、get_config、info、warn等 API
版权所有
版权归属:FolkPatch Team
