错误恢复
编辑页面
了解如何在使用 expo-updates 库时利用内置的错误恢复功能。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
使用 expo-updates 的应用可以利用内置的错误恢复行为,作为防止意外发布损坏更新的额外保障。
虽然我们再怎么强调在发布到生产环境之前先在暂存环境中测试更新的重要性都不为过,但人类(甚至计算机)偶尔也会犯错,而此处描述的错误恢复行为可以在这种情况下作为最后的补救手段。
免责声明: 下文记录的行为可能会发生变化,不应依赖。请始终在类似生产的环境中仔细且全面地测试你的代码,然后再发布更新。
糟糕!我把一个损坏的更新发布到了生产环境。我该怎么办?
首先,不要惊慌。出错在所难免;大概率一切都会没问题。
最重要的是,尽快发布一个包含修复的新更新(不过前提是你已经 100% 确信你的修复是正确的)。 expo-updates 中的错误恢复机制将确保在大多数情况下,即使是已经下载了损坏更新的用户,也应该能够获取到修复。
首先可以尝试回滚到你已知可正常工作的旧更新。不过,这并不总是安全的; 例如,你的损坏更新可能以一种不向后兼容的方式修改了持久化状态(例如存储在 AsyncStorage 中或设备文件系统中的数据)。因此,重要的是在一个尽可能模拟终端用户设备状态的暂存环境中进行测试,先加载损坏更新,然后再回滚。
如果你能识别出一个可以安全回滚到的旧更新,可以通过 EAS 控制台 或 EAS CLI 中 EAS Update 的 republish 选项来完成。
如果你无法识别出一个可以安全回滚到的旧更新,就需要向前修复。虽然最好尽快推出修复,但你应该花时间确保修复足够稳妥,并且要知道,即使是在此期间下载了损坏更新的用户,也应该能够下载到你的修复。
如果你想了解更多它是如何工作的,请继续阅读。
解释错误恢复流程
错误恢复流程的设计目标是尽可能轻量。它并不是一个完整的安全网,不能保护终端用户免受错误结果的影响;在很多情况下,用户仍然会看到崩溃。
相反,它的目的是通过尽可能多地确保应用有机会下载新更新并自我修复,来防止更新把你的应用“卡死”(即在应用来不及检查更新之前就启动崩溃,导致应用在卸载并重新安装前都无法使用)。
捕获错误
如果你的应用在执行 JS 时抛出一个致命错误,而且这个错误发生得足够早,早到可能阻止应用下载后续更新,那么 expo-updates 会捕获这个错误。
如果从应用首次渲染到抛出致命错误之间已经过去了超过 10 秒,
expo-updates将完全不会捕获这个错误,也不会触发任何错误恢复代码。因此,我们强烈建议你的应用在启动后尽快检查更新,无论是自动还是手动检查,以确保在未来出现错误时你能够推送修复。
如果 expo-updates 捕获到一个 JS 错误,接下来会发生什么取决于 React Native 是否已经触发了原生的“内容已出现”事件(Android 上的 ReactMarkerConstants.CONTENT_APPEARED 或 iOS 上的 RCTContentDidAppearNotification)— 也就是针对这个特定更新,在本次启动或之前某次启动中,你的应用首个视图是否已经渲染到屏幕上。
为什么要区分这一点? 在某些情况下,
expo-updates可能会尝试自动回滚到旧的(可工作)更新,但如果你的新更新以一种不向后兼容的方式修改了持久化状态,这可能很危险。我们的假设是:如果错误发生在首个视图渲染之前,那么不会有任何此类代码被执行,因此回滚是安全的。在这之后,expo-updates将只会向前修复,而不会回滚。
如果内容已出现
如果错误被捕获且“内容已出现”事件已经触发,或者它曾经在该设备上于同一更新的过去启动中触发过,那么将发生以下情况:
- 将启动一个 5 秒计时器,并且(除非
EXUpdatesCheckOnLaunch/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH被设置为NEVER)应用将检查是否有新更新,并在有新更新时下载它。 - 如果没有新更新、更新完成下载,或者计时器超时(以先发生者为准),应用将抛出原始错误并崩溃。
请注意,如果下载了新更新,它会在用户下次尝试打开应用时启动。
如果内容尚未出现
如果错误在“内容已出现”事件触发之前就被捕获,并且这是当前更新第一次在该设备上启动,那么将发生以下情况:
- 该更新将在本地被标记为“失败”,并且不会再在该设备上启动。
- 将启动一个 5 秒计时器,并且(除非
EXUpdatesCheckOnLaunch/expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH被设置为NEVER)应用将检查是否有新更新,并在有新更新时下载它。 - 如果新更新在计时器结束前完成下载,应用将立即尝试重新加载自身,并启动刚下载的新更新。
- 如果这个刚下载的新更新也抛出了致命错误,或者没有新更新,或者计时器超时,应用将立即尝试通过回滚到一个旧更新来重新加载,以最近一次成功启动的更新为准。
- 如果这也失败了,或者设备上没有可用的旧更新,应用将抛出原始错误并崩溃。
错误堆栈跟踪
如果你的应用遇到一个致命的 JS 错误,并且错误恢复系统无法恢复,它会重新抛出原始异常以导致崩溃。堆栈跟踪看起来会类似这样:
--------- 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 上,原始异常的堆栈跟踪会被保留。根据你的崩溃报告服务,你可能需要也可能不需要在本地复现崩溃,才能看到底层错误的更多信息。
最后的异常回溯: 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。
不幸的是,Apple 的崩溃报告不包含异常消息,而该消息会详细说明底层错误及其在 JavaScript 中的位置。要查看该消息并帮助你缩小问题范围,你可能需要在连接着 Xcode 调试器或 macOS Console 应用的情况下,本地复现崩溃。