2013 年 2 月 27 日
周三常见问题解答:Corona 运行时错误
今天是周三,又到了常见问题解答(FAQ)环节。以下是一些关于 Corona 运行时错误的常见问题解答。
1. 我的应用程序之前运行良好,但现在我看到一个弹出窗口,提示发现错误并且我的应用程序退出。发生了什么变化?
从 Android 上的 build 2013.1030 开始,运行时错误现在会显示一个弹出窗口,提醒您出现问题并显示有关错误的信息。以前,运行时错误仅显示在“adb logcat”窗口中,并且可能未被检测到。我们添加了弹出窗口,以便我们可以处理在删除默认 Android 权限后可能发生的运行时错误。目前,iOS 版本不支持此功能。
2. 我不记得以前收到过这些错误。这些是真正的错误吗?
是的,如果您收到运行时错误,那是因为发生了异常,并且您的程序未能执行错误之后的代码。您的应用程序可能一直存在错误,但要么您没有查找它们,要么它们只是偶尔发生一次,要么它们只发生在您未测试的设备上。
通常,这些错误是由于 API 返回 **nil** 值引起的,并且该值稍后被用来执行某些操作。一个例子是尝试删除一个 Corona 对象,而该对象的指针是 **nil**。另一个例子是显示一个文本字符串,该字符串将一个字符串与一个返回的 **nil** 值连接在一起。一般原因是编程错误,其中变量/对象在尝试使用它的函数中“超出范围”,或者在调用函数后没有检查 **nil** 值。
这是一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
local touchRect = display.newRoundedRect( 0, 50, 70, 30, 10 ) touchRect.x = display.contentCenterX local count1 = 0 local count2 = 0 local count3 = 0 function touchRect:touch( event ) if event.phase == "ended" then count1 = count1 + 1 count2 = count2 + 1 count3 = count3 + 1 text1.text = "Count1 = " .. count1 text2.text = "Count2 = " .. count2 -- << 这里发生错误 text3.text = "Count3 = " .. count3 end return true end text1 = display.newText( "Count1 = 0", 10, 100, native.systemFont, 20 ) local text2 = display.newText( "Count2 = 0", 10, 140, native.systemFont, 20 ) text3 = display.newText( "Count3 = 0", 10, 170, native.systemFont, 20 ) touchRect:addEventListener( "touch" ) |
点击白色矩形会导致所有三个计数器递增一并显示更新的计数。当设置 **“text2.text”** 时,会存在一个导致错误的 bug。错误是:**“attempt to index global ‘text2’ (a nil value)”**,因为 **text2** 被定义为局部变量,并且在 **touch** 函数之后定义。它对函数来说超出范围,并且是 **nil**。
如果您在模拟器中运行上面的代码并点击矩形,您将看到第一个计数器递增,但另外两个计数器没有。原因是代码块在尝试设置 **text2.text** 之后立即中止 - 其余代码永远不会执行。您可以继续点击矩形,第一个计数器会不断递增。对于使用该应用程序的人来说,如果他们不知道其他两个计数器也应该递增,那么看起来程序运行良好。
我们对 Android 代码所做的更改会检测到此运行时错误,并显示一个弹出窗口,其中包含错误的文件和行号。您可能会认为您的应用之前运行良好,但如果您的代码存在运行时错误,则错误之后的那部分代码实际上并没有执行。应该注意的是,类似这样的错误将在所有平台上被检测到,并显示在控制台上。您必须打开一个控制台窗口才能看到它(iOS 的 Xcode 或 Android 的“adb logcat”)。
上述错误可以通过在函数定义之前定义 text2 或删除定义 text2 时使用的“local”来轻松修复。
1 |
text2 = display.newText( "Count2 = 0", 10, 140, native.systemFont, 20 ) |
3. 当我将应用发布到 Google Play 时,如何避免弹出窗口?
目前,有两种方法可以避免弹出窗口:1)查找并修复所有错误(如上所示),或 2)在您知道可能导致问题的代码区域添加 pcall 语句。
我们理解,在一个应用中找到并消除所有错误是不可能的,因此我们计划添加一个运行时监听器,它将捕获错误并允许程序决定如何处理它们。如果您为 App Store 构建应用,并且用户遇到错误,您可以记录错误并将其发送回您的服务器进行评估,或者直接忽略它。此功能目前不可用,但应该很快会在未来的每日构建中出现。
4. 有没有办法自己捕获错误,而不是让它退出应用或向用户显示弹出窗口?
除了等待包含新运行时错误监听器的每日构建外,您还可以将代码片段包装在 Lua pcall 语句中以捕获错误。 pcall (受保护调用) 可用于调用任何函数或方法,调用将返回执行状态。如果调用执行正确,则返回 true,如果因运行时错误而失败,则返回 false。由于它会捕获运行时错误,因此可以避免弹出窗口,并且不会退出应用。使用 pcall 会产生轻微的性能开销,因此仅应在需要处理可能发生在应用控制之外的错误(例如,网络调用)时使用。
pcall 的语法是
1 |
pcall( f [, ...] ) |
其中 f 是要调用的函数,
而 … 是函数的参数。
为了本练习,我将设置 text2 包装在一个函数(setText2)中,并使用 pcall 调用它。调用的结果存储在 status 中并显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
local touchRect = display.newRoundedRect( 0, 50, 70, 30, 10 ) touchRect.x = display.contentCenterX local count1 = 0 local count2 = 0 local count3 = 0 local function setText2( value ) text2.text = "Count2 = " .. value end function touchRect:touch( event ) if event.phase == "ended" then count1 = count1 + 1 count2 = count2 + 1 count3 = count3 + 1 text1.text = "Count1 = " .. count1 local status = pcall( setText2, count2 ) print( "pcall status is ", status ) text3.text = "Count3 = " .. count3 end return true end text1 = display.newText( "Count1 = 0", 10, 100, native.systemFont, 20 ) local text2 = display.newText( "Count2 = 0", 10, 140, native.systemFont, 20 ) text3 = display.newText( "Count3 = 0", 10, 170, native.systemFont, 20 ) touchRect:addEventListener( "touch" ) |
当您运行此修改后的代码时,pcall 状态为 false,表示发生了运行时错误。由于计数器 1 和 3 都会递增,这表明 pcall 捕获了运行时错误,并允许其余代码执行。
只有在您预计代码可能会失败并需要防范它或测试失败条件时,pcall 才有用。
5. 为什么错误弹出窗口只出现在 Android 构建中?
我们首先在 Android 中进行了更改,因为我们需要一种方法来处理在 Android 构建中删除默认权限时可能发生的错误。我们认为检测和捕获错误对我们的开发人员来说是一件有用的事情,并计划在即将推出的每日构建中将此功能添加到 iOS 以及 Mac 和 Windows 模拟器中。
在当前版本和每日构建中,我们确实提供了运行时错误信息,可以帮助您查找应用中的错误。如果您进行开发人员构建,您将在控制台中看到文件、行号和错误类型。发布构建(对于 App Store)只会显示遇到的错误类型。
另外,如果您使用的是 Mac 模拟器,请务必启动 Corona Terminal 而不是 Corona 模拟器。Corona Terminal 会加载一个控制台窗口,然后启动 Corona 模拟器。控制台窗口会显示打印、警告和错误消息。
这就是今天的常见问题解答。我希望您喜欢这篇文章,甚至学到了一些东西。
Vladimir
发布于 2 月 27 日 12:14“如果您为 App Store 构建应用,并且用户遇到错误,您可以记录错误并将其发送回您的服务器进行评估,或者直接忽略它。此功能目前不可用,但应该很快会在未来的每日构建中出现”
此功能仅适用于 iOS,还是也会有 Android 版本?
Jeff
发布于 2 月 28 日 09:57> 如果您进行开发人员构建,您将在控制台中看到文件、行号和错误类型。发布构建(对于 App Store)只会显示遇到的错误类型。
是否可以(可选地)在 App Store 发布版本中包含更多调试信息?目前,我们将大部分代码包装在 xpcall 中,并将所有未处理的异常发布到我们的服务器,我们在那里记录错误(以及设备类型、版本等)。我们将 debug.traceback() 与错误一起包含,但使用这样的数据来确定罪魁祸首有点像侦探游戏
?:0: 尝试对 upvalue ‘?’ (一个 nil 值) 执行算术运算
堆栈回溯
?: 在函数中
?: 在函数 ‘destroy’ 中
?: 在函数 ‘?’ 中
?: 在函数中
很多时候我们都能够找到罪魁祸首,但是如果在发布版本中包含开发人员调试数据以捕获一个讨厌的错误,那将太棒了。
谢谢
Alex
发布于 2 月 28 日 11:17我读了这篇常见问题解答,并试图让自己不去想,在没有通知用户的情况下将其实现到每日构建中是多么荒谬。然而,我没有成功。我整天都在追逐与此相关的问题,而就在一周前,我的 beta 测试人员在我的构建中没有遇到任何问题。
我意识到无错误代码是防止上述情况破坏我的代码和我的 beta 测试人员良好意愿的最佳方法,但正如您上面所说,在一个应用中找到并消除所有错误几乎是不可能的,这就是为什么存在 beta 测试的原因。
我的问题是,在没有运行时错误监听器的情况下,这有什么好处?我的应用之前运行良好,即使它(显然)不时抛出运行时错误,但仍然运行稳定。上述情况导致我做了一些严重的代码重新工作,这很好,因为显然存在错误。然而,代码之前很好,并且应用按我的预期运行。这种实现旨在实现什么可能的结果?
Tom Newman
发布于 3 月 3 日 16:17Alex,
每日构建始终是“正在进行的工作”。它们包含我们向用户公开使用和评论的错误修复和新功能。我们并不总是做得对,有时需要多次迭代才能微调我们添加的功能。话虽如此,我们也不建议您在没有经过充分测试和调试的情况下使用每日构建发布您的产品。每日构建说明确实指出了该版本所做的更改。这些说明并不打算成为完整的文档,而是对所更改内容的快速了解。
至于我们在 Android 上弹出一个消息框所做的更改,我们认为这会对我们的用户有所帮助。很多时候会发生运行时错误,而程序员没有意识到发生了错误,但注意到应用中某些东西无法正常工作。在我看来,让应用抛出运行时错误并不是“稳定”。运行时错误会导致代码块中止,这意味着您的应用没有按照您的预期工作。有时这些运行时错误可能不会被注意到,但有时它可能会改变应用的流程,甚至导致崩溃。再说一遍,这不是一种“稳定”的体验。
添加“错误监听器”是在我们发布每日构建 (1030) 并从用户那里收到一些反馈后出现的。这是我们正在努力解决的问题,并计划尽快实施。我们还提到了将 pcall 用于可能生成运行时错误的代码区域。消除运行时错误的根本方法是首先测试并消除运行时错误的原因,而不是尝试捕获和忽略错误。
显示运行时错误的弹出消息的好处在于测试。大多数应用程序都会发布给 Beta 测试人员,以便他们报告发现的错误。过去,测试人员可能不会注意到无声的运行时错误,但现在他们可以获得信息并报告哪里出了问题。是否应该为发布版本抑制弹出错误是我们内部讨论过的问题。双方都有充分的理由,我们认为添加一个“错误监听器”可能是让程序员决定如何处理它的最佳解决方案。
几年前,我们每三到四个月发布一次新版本,而且没有每日构建。我们认为最好在发布版本之前将错误修复和新功能交到我们的开发人员手中,因此我们决定向开发人员提供我们的内部版本,供他们使用并提供关于我们做得对与错的反馈。由于每日构建过程,您不能总是依赖每个每日构建作为生产就绪的发布版本。
Alex
发布于 2 月 28 日 11:27顺便说一下,我真的很想知道这样做的好处,这样我才能利用它。我不是在抱怨,我真的很想知道这种情况的积极之处。
Bohumil
发布于 3 月 1 日 22:38我也看不到任何好处。我很高兴终于删除了默认的 Android 权限。我以为我们可以简单地重新构建我们运行正常的 Android 应用程序,客户也会很高兴。但这不可能。一些复杂的场景和某些硬件总是会出现问题。我们的应用程序之前运行良好,但现在我们应该花费数周的时间来处理所有应用程序。我不确定为什么会发生这种情况以及原因是什么…
Daniel Williams
发布于 3 月 4 日 09:32我理解你们试图为开发人员提供更多信息的方向,但我不同意这种方法。开发人员应该可以选择启用或禁用弹出窗口,而不是强制弹出窗口。我猜大多数使用 Corona 的人都是独立开发者,我们并不总是有机会使用所有不同的设备。
对于未来的构建版本,您能否让我们在 build.settings 或 config.lua 文件中选择启用/禁用弹出窗口?
另外,这并不是一篇批评 Corona 的文章,只是一些反馈。我仍然热爱你们提供给我们的东西。