本节将全面讲解Lua的错误类型、错误处理机制、调试技巧以及在Android平台下的特殊处理方案。
-- 常见语法错误示例
local examples = {
"local x = ", -- 不完整的表达式
"if a then end else", -- 错误的if结构
"function() end", -- 匿名函数未赋值
"t = {1, 2, }", -- 末尾逗号
"break", -- 无效的break位置
"goto undefined", -- 未定义的标签
"x = nil + 1" -- 无效运算
}
-- 检测语法错误
function checkSyntax(code)
local func, err = load("return "..code)
if not func then
print("语法错误:", err) -- 输出类似:[string "return local x = "]:1: unexpected symbol near 'local'
end
end
注意事项:
load
或loadfile
函数时,总是检查返回值-- 常见运行时错误类型
local runtimeErrors = {
arithmetic = function() return 1 + "a" end, -- 算术错误
index = function() return t.undefined end, -- 索引nil值
call = function() local f = nil; f() end, -- 调用nil值
conversion = function() return "x" + 1 end, -- 类型转换
file = function() io.open("nonexistent.txt") end, -- 文件操作
memory = function() while true do table.insert({}, {}) end end, -- 内存溢出
custom = function() error("自定义错误") end -- 主动抛出
}
-- 捕获运行时错误
for name, func in pairs(runtimeErrors) do
local ok, err = pcall(func)
if not ok then
print(name.."错误:", err:match(":(.+)")) -- 提取错误信息
end
end
注意事项:
-- Android平台特有错误
local resourceErrors = {
asset = function()
local content = android.loadAsset("nonexistent.txt")
if not content then error("资源加载失败") end
end,
memory = function()
local hugeTable = {}
for i = 1, 1e8 do hugeTable[i] = i end -- 内存不足
end,
thread = function()
local Thread = luajava.bindClass("java.lang.Thread")
Thread():start():start() -- 非法线程状态
end
}
-- 处理资源错误
function handleResourceError(f)
local ok, err = xpcall(f, function(e)
return debug.traceback(e, 2) -- 带堆栈的错误
end)
if not ok then
print("资源错误:", err)
-- Android日志系统
android.util.Log.e("LUA_ERROR", err)
end
end
Android特别提示:
-- 基本保护调用
local function riskyOperation()
if math.random() > 0.5 then
error("操作失败")
end
return "成功"
end
-- pcall示例
local status, result = pcall(riskyOperation)
if status then
print("结果:", result)
else
print("错误:", result)
end
-- xpcall示例(带错误处理函数)
local function errorHandler(err)
local trace = debug.traceback("错误追踪:", 2)
return string.format("%s\n%s", err, trace)
end
local status, result = xpcall(riskyOperation, errorHandler)
最佳实践:
-- 多层调用栈错误传递
function level1()
level2()
end
function level2()
level3()
end
function level3()
error("深层错误")
end
-- 捕获调用栈
local ok, err = xpcall(level1, function(e)
return debug.traceback(e, 3) -- 获取3层堆栈
end)
print(err)
-- 输出类似:
-- [string "level3"]:1: 深层错误
-- stack traceback:
-- [string "level3"]: in function 'level3'
-- [string "level2"]: in function 'level2'
-- [string "level1"]: in function 'level1'
调试建议:
debug.traceback()
在协程中需要传入协程对象作为第一参数__traceback
元方法自定义堆栈格式-- 自定义错误对象
local Error = {}
Error.__index = Error
function Error.new(code, message, extra)
return setmetatable({
code = code,
message = message,
extra = extra or {},
timestamp = os.time(),
stack = debug.traceback("", 2)
}, Error)
end
function Error:__tostring()
return string.format("[%s] %s\n%s",
self.code, self.message, self.stack)
end
-- 使用示例
local function validateInput(input)
if not input then
error(Error.new("INVALID_INPUT", "输入不能为空", {
field = "username",
value = input
}))
end
end
local ok, err = pcall(validateInput, nil)
if not ok then
if getmetatable(err) == Error then
print("错误代码:", err.code)
print("错误详情:", err)
else
print("原生错误:", err)
end
end
设计原则:
-- 带恢复点的错误处理
function withRetry(fn, maxRetries, delay)
local retries = 0
while true do
local ok, result = pcall(fn)
if ok then return result end
retries = retries + 1
if retries >= maxRetries then
error(string.format("超过最大重试次数(%d): %s",
maxRetries, result))
end
print(string.format("重试 %d/%d: %s",
retries, maxRetries, result))
os.execute("sleep "..tonumber(delay))
end
end
-- 使用示例
local function unstableAPI()
if math.random() < 0.7 then
error("API请求超时")
end
return "数据"
end
local data = withRetry(unstableAPI, 3, 1) -- 最多重试3次,间隔1秒
重试策略:
-- 设置全局错误处理器
local originalTraceback = debug.traceback
debug.traceback = function(msg, level)
local trace = originalTraceback(msg, level)
-- 添加Android环境信息
local envInfo = string.format("\n[ANDROID_ENV]\nSDK: %s\nModel: %s",
android.os.Build.VERSION.SDK,
android.os.Build.MODEL)
return trace .. envInfo
end
-- 全局保护函数
function androidRunSafe(fn)
xpcall(fn, function(err)
local fullError = debug.traceback(err)
-- 记录到Android系统
android.util.Log.e("LUA_CRASH", fullError)
-- 显示用户友好提示
activity.runOnUiThread(function()
local AlertDialog = luajava.bindClass("android.app.AlertDialog")
AlertDialog.Builder(activity)
:setTitle("应用错误")
:setMessage("发生意外错误,已记录")
:setPositiveButton("确定", nil)
:show()
end)
end)
end
-- 使用示例
androidRunSafe(function()
require("module.undefined") -- 故意引发错误
end)
关键点:
-- 错误上报模块
local ErrorReporter = {
API_URL = "https://api.example.com/errors",
enabled = true,
context = nil
}
function ErrorReporter.init(context)
ErrorReporter.context = context
end
function ErrorReporter.report(err, level)
if not ErrorReporter.enabled then return end
local report = {
platform = "Android",
appVersion = ErrorReporter.context.packageManager
.getPackageInfo(ErrorReporter.context.packageName, 0).versionName,
error = tostring(err),
stack = debug.traceback("", level or 2),
deviceInfo = {
model = android.os.Build.MODEL,
sdk = android.os.Build.VERSION.SDK_INT,
memory = android.os.Debug.getNativeHeapAllocatedSize()
},
timestamp = os.date("%Y-%m-%d %H:%M:%S")
}
-- 异步上报
local Thread = luajava.bindClass("java.lang.Thread")
Thread{
run = function()
local HttpURLConnection = luajava.bindClass("java.net.HttpURLConnection")
local conn = luajava.newInstance("java.net.URL", ErrorReporter.API_URL)
:openConnection()
conn:setRequestMethod("POST")
conn:setDoOutput(true)
local out = conn:getOutputStream()
out:write(luajava.newInstance("org.json.JSONObject", report):toString():getBytes())
out:close()
conn:getResponseCode() -- 触发请求
end
}:start()
end
-- 自动注册全局错误处理
debug.sethook(function(event)
if event == "error" then
ErrorReporter.report(debug.getinfo(2, "S").short_src..":"..
debug.getinfo(2, "l").currentline)
end
end, "r")
上报策略:
-- 增强版堆栈追踪
function enhancedTraceback(err, level)
local trace = debug.traceback(err, level)
-- 添加局部变量信息
local i = 1
while true do
local name, value = debug.getlocal(level + 1, i)
if not name then break end
trace = trace .. string.format("\n local %s = %s",
name, tostring(value))
i = i + 1
end
-- 添加上值信息
local func = debug.getinfo(level + 1, "f").func
i = 1
while true do
local name, value = debug.getupvalue(func, i)
if not name then break end
trace = trace .. string.format("\n upvalue %s = %s",
name, tostring(value))
i = i + 1
end
return trace
end
-- 使用示例
local function buggyFunc()
local x = 10
local t = {1, 2, 3}
error("测试错误")
end
xpcall(buggyFunc, enhancedTraceback)
性能控制:
_ENV
等特殊上值需特殊处理android.util.Log
输出长文本-- 简单REPL调试器
function startDebugger(co, err)
co = co or coroutine.running()
print("进入调试模式,输入命令或Lua表达式:")
print("(bt)查看堆栈 (up n)上值 (loc n)局部变量 (eval)执行代码 (c)继续")
local level = 2
while true do
io.write("debug> ")
local input = io.read()
if input == "bt" then
print(debug.traceback(co, level))
elseif input:match("^up (%d+)") then
local n = tonumber(input:match("^up (%d+)"))
for i = 1, n do
local name, value = debug.getupvalue(debug.getinfo(level, "f").func, i)
if name then
print(string.format("%d: %s = %s", i, name, tostring(value)))
end
end
elseif input:match("^loc (%d+)") then
local n = tonumber(input:match("^loc (%d+)"))
for i = 1, n do
local name, value = debug.getlocal(level, i)
if name then
print(string.format("%d: %s = %s", i, name, tostring(value)))
end
end
elseif input == "eval" then
io.write("输入Lua代码> ")
local code = io.read()
local chunk = load("return "..code)
if chunk then
setfenv(chunk, getfenv(debug.getinfo(level, "f").func)
print(chunk())
else
print("语法错误")
end
elseif input == "c" then
break
else
print("未知命令")
end
end
end
-- 使用示例
xpcall(buggyFunc, function(err)
startDebugger(coroutine.running(), err)
end)
安全提示: