错误恢复
了解如何在使用 expo-updates 库时利用内置错误恢复。
使用 expo-updates 的应用可以利用内置的错误恢复机制,作为防止意外发布损坏更新的额外保障。
🌐 Apps using expo-updates can take advantage of built-in error recovery behavior as an extra safeguard against accidentally publishing broken updates.
虽然我们无法充分强调在发布到生产环境之前在暂存环境中测试更新的重要性,但人类(甚至计算机)偶尔会犯错误,并且此处描述的错误恢复行为可以作为这种情况下的最后手段。
🌐 While we cannot stress enough the importance of testing updates in a staging environment before publishing to production, humans (and even computers) occasionally make mistakes, and the error recovery behavior described here can serve as a last resort in such cases.
警告 免责声明: 下面记录的行为可能会发生变化,不应依赖。发布更新前,请始终在接近生产环境的环境中仔细且彻底地测试你的代码。
救命!我把一个有问题的更新发布到生产环境了。我该怎么办?
🌐 Help! I published a broken update to production. What should I do?
首先,别慌。错误是会发生的;大多数情况下,一切都会没事的。
🌐 First of all, don't panic. Mistakes happen; most likely, everything will be fine.
重要的是要尽快发布一个包含修复的新更新(不过在你对修复完全有信心之前不要发布)。 expo-updates中的错误恢复机制将确保在大多数情况下,即使是已经下载了有问题更新的用户,也能够获取该修复。
🌐 The important thing is to publish a new update with a fix as soon as possible (though not before you are 100% confident in your fix). The error recovery mechanism in expo-updates will ensure that in most cases, even users who have already downloaded the broken update should be able to get the fix.
首先要尝试的是回滚到你知道可正常工作的较旧更新。**然而,这并不总是安全的;**例如,你的损坏更新可能已经以不向后兼容的方式修改了持久状态(例如存储在 AsyncStorage 或设备文件系统中的数据)。重要的是在一个尽可能模拟终端用户设备状态的预发布环境中进行测试,加载损坏的更新然后再回滚。
🌐 The first thing to try is rolling back to an older update that you know was working. However, this may not always be safe; your broken update may, for example, have modified persistent state (such as data stored in AsyncStorage or on the device's file system) in a non-backwards-compatible way. It's important to test in a staging environment that emulates an end user's device state as closely as possible to load the broken update and then roll back.
如果你能确定一个可以安全回滚的旧更新版本,你可以通过 EAS 仪表板 或 EAS CLI 使用 EAS Update 的 republish 选项来回滚。
🌐 If you can identify an older update that is safe to roll back to, you can do so using EAS Update's republish option from EAS dashboard or EAS CLI.
如果你无法确定一个可以安全回滚到的较旧更新,你将需要向前修复。虽然尽快推出修复是最好的,但你应该花时间确保你的修复是可靠的,并且要知道即使在此期间下载了有问题更新的用户,也应该能够下载你的修复版本。
🌐 If you cannot identify an older update that is safe to roll back to, you'll need to fix it forward. While it's best to roll out a fix as quickly as possible, you should take the time to ensure your fix is solid, and know that even users who download your broken update in the meantime should be able to download your fix.
如果你想了解有关其工作原理的更多详细信息,请继续阅读。
🌐 If you'd like more details about how this works, read on.
解释错误恢复流程
🌐 Explaining the error recovery flow
错误恢复流程旨在尽可能轻量化。它并不是一个完整的安全网,无法保护终端用户免受错误结果的影响;在许多情况下,用户仍然会看到程序崩溃。
🌐 The error recovery flow is intended to be as lightweight as possible. It is not a full safety net that protects your end users from the results of errors; in many cases, users will still see a crash.
其目的不是如此,而是为了防止更新“弄坏”你的应用(在应用能够检查更新之前启动时崩溃,使应用无法使用,直到卸载并重新安装),而是通过确保在尽可能多的情况下,应用有机会下载新的更新并自我修复。
🌐 Rather, the purpose is to prevent updates from "bricking" your app (causing a crash on launch before the app can check for updates, making the app unusable until uninstalled and reinstalled) by ensuring that in as many cases as possible, the app has the opportunity to download a new update and fix itself.
捕获错误
🌐 Catching an error
如果你的应用在执行 JS 时抛出致命错误,而这一错误发生在应用生命周期的早期阶段,可能会阻止应用下载更多更新,expo-updates 将捕获此错误。
🌐 If your app throws a fatal error when executing JS which is early enough in the app's lifecycle that it may prevent the app from being able to download further updates, expo-updates will catch this error.
如果在你的应用首次渲染和发生致命错误之间已经过去超过 10 秒,
expo-updates将完全不会捕获此错误,并且任何错误恢复代码都不会被触发。因此,我们强烈建议你的应用在启动后尽快检查更新,无论是自动检查还是手动检查,以确保在发生未来错误时能够推送修复。
如果 expo-updates 捕获了一个 JS 错误,接下来会发生什么取决于 React Native 是否已经触发了原生的“内容出现”事件(Android 上为 ReactMarkerConstants.CONTENT_APPEARED 或 iOS 上为 RCTContentDidAppearNotification)——大约在你的应用的第一个视图已经渲染到屏幕上的时候,对于这次特定的更新,无论是在此次启动还是之前的启动中。
🌐 If expo-updates catches a JS error, what will happen next depends on whether React Native has fired the native "content appeared" event (ReactMarkerConstants.CONTENT_APPEARED on Android or RCTContentDidAppearNotification on iOS) — approximately when your app's first view has been rendered on the screen, for this particular update, either on this launch or a previous one.
为什么要做这种区分? 在某些情况下,
expo-updates可能会尝试自动回滚到较旧(可用)的更新版本,但如果你的新更新以不向后兼容的方式修改了持久化状态,这可能是危险的。我们假设如果错误发生在第一个视图渲染之前,这类代码还未执行,因此回滚是安全的。在此之后,expo-updates只会向前修复,而不会执行回滚。
如果内容已经出现
🌐 If content has appeared
如果捕获到错误,并且“内容已显示”事件已经触发,或者在此设备上同一更新的过去启动中该事件曾经触发过,将发生以下情况:
🌐 If an error is caught and the "content appeared" event has already fired, OR if it has ever been fired on this device on a past launch of the same update, the following will happen:
- 将启动一个5秒计时器,并且(除非
EXUpdatesCheckOnLaunch/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH设置为NEVER)应用将检查是否有新更新,如果有的话会下载它。 - 如果没有新的更新、更新下载完成或计时器超时(以先发生者为准),应用将抛出原始错误并崩溃。
请注意,如果下载了新更新,它将在用户下次尝试打开应用时启动。
🌐 Note that if a new update is downloaded, it will launch when the user next tries to open the app.
如果内容还没有出现
🌐 If content has not appeared
如果在“内容显示”事件触发之前捕获到错误,并且这是此设备上当前更新首次启动,将会发生以下情况:
🌐 If an error is caught before the "content appeared" event has fired, and this is the first time the current update is being launched on this device, the following will happen:
- 此更新将在本地被标记为“失败”,并且不会在此设备上再次启动。
- 将启动一个5秒计时器,并且(除非
EXUpdatesCheckOnLaunch/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH设置为NEVER)应用将检查是否有新更新,如果有的话会下载它。 - 如果新的更新在计时器耗尽之前完成下载,应用将立即尝试重新加载自身并启动新下载的更新。
- 如果这个新下载的更新也引发致命错误,或者没有新的更新,或者计时器用完,应用将立即尝试通过回滚到较旧的更新(无论哪个最近成功启动)来重新加载。
- 如果这也失败,或者设备上没有可用的旧更新,则应用将抛出原始错误并崩溃。
错误堆栈跟踪
🌐 Error stacktraces
如果你的应用遇到致命的 JS 错误,并且错误恢复系统无法恢复,它将重新抛出原始异常导致崩溃。堆栈跟踪将类似于如下情况:
🌐 If your app encounters a fatal JS error, and the error recovery system cannot recover, it will re-throw the original exception to cause a crash. The stacktrace will look similar to this:
--------- beginning of crash AndroidRuntime: FATAL EXCEPTION: expo-updates-error-recovery AndroidRuntime: Process: com.myapp.MyApp, PID: 12498 AndroidRuntime: com.facebook.react.common.JavascriptException AndroidRuntime: AndroidRuntime: at com.facebook.react.modules.core.ExceptionsManagerModule.reportException(ExceptionsManagerModule.java:72) AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) AndroidRuntime: at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372) AndroidRuntime: at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:188) AndroidRuntime: at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method) AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:938) AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99) AndroidRuntime: at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27) AndroidRuntime: at android.os.Looper.loop(Looper.java:223) AndroidRuntime: at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:228) AndroidRuntime: at java.lang.Thread.run(Thread.java:923)
在 Android 上,原始异常的堆栈跟踪会被保留。根据你的崩溃报告服务,你可能需要也可能不需要在本地重现崩溃,以查看更多关于底层错误的信息。
🌐 On Android, the stacktrace of the original exception is preserved. Depending on your crash reporting service, you may or may not need to reproduce the crash locally to see more information about the underlying error.
Last Exception Backtrace: 0 CoreFoundation 0xf203feba4 __exceptionPreprocess + 220 (NSException.m:200) 1 libobjc.A.dylib 0xf201a1be7 objc_exception_throw + 60 (objc-exception.mm:565) 2 MyApp 0x10926b7ee -[EXUpdatesAppController throwException:] + 24 (EXUpdatesAppController.m:422) 3 MyApp 0x109280352 -[EXUpdatesErrorRecovery _crash] + 984 (EXUpdatesErrorRecovery.m:222) 4 MyApp 0x10927fa3d -[EXUpdatesErrorRecovery _runNextTask] + 148 (EXUpdatesErrorRecovery.m:0) 5 libdispatch.dylib 0x109bc1848 _dispatch_call_block_and_release + 32 (init.c:1517) 6 libdispatch.dylib 0x109bc2a2c _dispatch_client_callout + 20 (object.m:560) 7 libdispatch.dylib 0x109bc93a6 _dispatch_lane_serial_drain + 668 (inline_internal.h:2622) 8 libdispatch.dylib 0x109bca0bc _dispatch_lane_invoke + 392 (queue.c:3944) 9 libdispatch.dylib 0x109bd6472 _dispatch_workloop_worker_thread + 648 (queue.c:6732) 10 libsystem_pthread.dylib 0xf6da2845d _pthread_wqthread + 288 (pthread.c:2599) 11 libsystem_pthread.dylib 0xf6da2742f start_wqthread + 8
尽管看起来异常是从 expo-updates 抛出的,但这个堆栈跟踪通常表明 错误起源于 JavaScript。
🌐 Even though it appears the exception was thrown from expo-updates, this stacktrace generally indicates an error that originated in JavaScript.
不幸的是,Apple 的崩溃报告不包括异常信息,而这些信息可以详细说明底层错误及其在 JavaScript 中的位置。为了查看该信息并帮助你缩小问题范围,你可能需要在本地使用 Xcode 调试器或 macOS 控制台应用重现该崩溃。
🌐 Unfortunately, Apple's crash reporting does not include the exception message, which details the underlying error and its location in JavaScript. To see the message and help you narrow down the issue, you may need to reproduce the crash locally with the Xcode debugger or macOS Console app attached.