了解如何在使用 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.
重要的是尽快发布包含修复程序的新更新(尽管在你对修复程序 100% 有信心之前)。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.
如果你可以识别可以安全回滚的旧更新,则可以使用 Expo 仪表板 或 EAS 命令行接口 中的 EAS 更新的 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 Expo 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.
相反,目的是通过确保在尽可能多的情况下,应用有机会防止对应用进行 "bricking" 更新(在应用检查更新之前导致启动时崩溃,使应用在卸载并重新安装之前无法使用) 下载新的更新并自行修复。
¥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
根本不会捕获此错误,并且不会触发任何错误恢复代码。因此,我们强烈建议你的应用在启动后立即检查更新,无论是自动还是手动,以确保你可以在将来出现错误时推出修复程序。¥If more than 10 seconds have elapsed between your app's first render and the time a fatal error is thrown,
expo-updates
will not catch this error at all and none of the error recovery code will be triggered. Therefore, we highly recommend that your app check for updates very shortly after launching, whether automatically or manually to ensure you can push out fixes in the event of a future error.
如果 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
只会向前修复,不会回滚。¥Why this distinction? In some cases
expo-updates
may try to automatically roll back to an older (working) update, but this can be dangerous if your new update has modified persistent state in a non-backwards compatible way. We assume that if the error occurs before the first view has rendered, no such code has been able to execute, and so rolling back is safe. After this pointexpo-updates
will only fix forward and will not roll back.
¥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
)应用将检查是否有新更新,如果有则下载。
¥A 5 second timer will be started, and (unless EXUpdatesCheckOnLaunch
/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH
is set to NEVER
) the app will check for a new update and download it if there is one.
如果没有新的更新、更新下载完成或计时器超时(以先发生者为准),应用将抛出原始错误并崩溃。
¥If there is no new update, the update finishes downloading, or the timer runs out (whichever happens first), the app will throw the original error and crash.
请注意,如果下载了新更新,它将在用户下次尝试打开应用时启动。
¥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:
该更新将在本地标记为 "failed",并且不会在此设备上再次启动。
¥The update will be marked as "failed" locally and will not be launched again on this device.
将启动 5 秒计时器,并且(除非 EXUpdatesCheckOnLaunch
/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH
设置为 NEVER
)应用将检查是否有新更新,如果有则下载。
¥A 5 second timer will be started, and (unless EXUpdatesCheckOnLaunch
/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH
is set to NEVER
) the app will check for a new update and download it if there is one.
如果新的更新在计时器耗尽之前完成下载,应用将立即尝试重新加载自身并启动新下载的更新。
¥If a new update finishes downloading before the timer runs out, the app will immediately try to reload itself and launch the newly downloaded update.
如果这个新下载的更新也引发致命错误,或者没有新的更新,或者计时器用完,应用将立即尝试通过回滚到较旧的更新(无论哪个最近成功启动)来重新加载。
¥If this newly downloaded update also throws a fatal error, or there is no new update, or the timer runs out, the app will immediately try to reload by rolling back to an older update, whichever one was most recently launched successfully.
如果这也失败,或者设备上没有可用的旧更新,则应用将抛出原始错误并崩溃。
¥If this also fails, or there is no older update available on the device, the app will throw the original error and crash.
¥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.