开发者问题收集

React Native 在生产中崩溃

2016-11-21
4148

我们使用 React Native 构建了一个应用,以改进我们之前的 Cordova 应用的用户体验和功能。

一切顺利。经过几个月的开发、QA、应用审核,然后我们将其发布到 App Store。它在我们尝试过的所有设备上都运行正常,从 iPhone 4s 到 iPhone 6s+,我们在 iOS 8.3(您可以通过 xCode 下载的最早的模拟器)到 10.0 上进行了测试。

发布后,许多用户开始报告应用在启动画面消失之前就崩溃了。我们之前在应用审核、测试或其他任何地方都没有见过这种行为。

我们在 xCode 中调查了“崩溃”,但显然它们没有出现,因为数百名用户都遇到了崩溃,而我们只能看到少数崩溃 - 这似乎与启动无关。

我们发布了集成了 Crashlytics 的更新版本,但这也无济于事。我们也没有收到针对此特定问题的 Crashlytics 错误,这意味着该问题可能在

之前发生过,您知道下一步该去哪里查找吗?我们真的不想恢复到旧版本并失去数月的工作成果。

当所有内容加载完毕时,该应用会使用大约 100MB 的内存,因此我认为这应该不是问题。所有设备上的所有 iOS 版本都存在这种情况。我们无法将错误仅隔离到特定用户。

3个回答

当似乎没有其他分析途径时,我会求助于日志记录这一简单的后备方法。

我之前在生产 iOS 应用中使用过以下技术。设置这项技术需要一点工作量,但一旦开始,它将对将来的许多其他问题非常有用。不仅仅是崩溃,还包括用户报告的任何其他奇怪行为,而这些行为您无法在测试环境中复制。

  1. 应用应该做的第一件事就是通过读取一些应该在上一次启动开始和结束时写入默认值的值来检查上一次启动是否成功(下一步将详细介绍)。如果上次启动不成功,则让用户选择以某种“安全模式”运行(这意味着什么取决于您的应用在启动时尝试执行的操作,但对我来说,这意味着不加载任何数据,或者除了显示没有任何数据相关项目的 UI 之外不执行任何其他操作;对于某些应用,它甚至可以加载完全不同的 UI,其中仅包含诊断工具或数据删除/重置工具)。
  2. 在确定上次启动正常(或这是有史以来的第一次启动)后,应用应该做的下一件事是尽快将某种“startupBegan”状态写入默认值,然后仅在完全完成启动后才写入某种“startupCompleted”状态(“完全完成启动”的含义取决于应用,但您确实希望确保 UI 此时完全响应,并显示所需的一切;有时这可能有点难以确定,因为有些东西直到启动画面消失后才会运行,等等);如果您找不到其他方法,我想您可以使用计时器触发此操作,但这样会比较难看 - 最好找到某种方法来确定启动何时真正完成)。这些值可用于确定启动是否已开始但未完成,并且是步骤 1(上面)用于确定上次启动是否成功的内容。
  3. 在应用程序中包含大量日志记录(我旧的自定义 Objective-C 版本如何执行此操作已在下面的“更新”部分中替换为更相关的 Swift 版本)。
  4. 使应用程序能够将日志文件通过电子邮件发送到您的支持电子邮件地址 - 这必须在应用程序的“安全模式”(以及正常模式下)下可用。在正常模式下,我将其做得相当模糊,以便在一切正常时不会被用户注意到(例如,位于“设置”或“关于”视图底部的按钮)。当用户提交支持请求时,我会告诉用户如何找到该按钮,而我确实需要这些日志。

这方面可能存在许多变化。包括仅在用户配置了设置后才启用日志记录。有时,当用户报告特定问题时,您可能必须在特定代码区域周围添加大量日志记录,然后在问题解决后再次将其删除(如果您担心日志记录的性能/存储问题)。

对于我的 (Objective-C) 应用程序,将我的代码写入默认启动状态的位置如下(可能有更适合您的应用程序的位置):

  • 在应用程序委托的 application:didFinishLaunchingWithOptions: 早期的“startupBegan”
  • 在视图控制器的 viewDidAppear 末尾的“startupCompleted”(不是 viewWillAppear !在发送这两个消息之间可能会出现很多问题)

更新:

iOS 中的登录比第一次撰写这篇文章时好了很多,所以我删除了笨重的自定义 Objective-C NSLog() 和 stdout 文件重定向指令,我最初在这篇文章中提到过。相反,我现在(使用 Swift)在全局级别(例如,在 AppDelegate 中)执行类似这样的操作:

let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app") (可选,您可以拥有多个记录器,它们将在每个日志行中标识自己。我在我的一个应用程序中还有另一个记录器,我将 Web 视图中的所有 JavaScript 日志记录重定向到该记录器,例如:

let jsLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "JS")

然后要实际写入日志,请使用类似以下内容:

logger.log("Some message being logs: \(some variable, privacy: .public)")

然后收集实际日志(例如,如果用户点击“诊断”中的“发送日志”按钮“设置” UI 的视图):

guard let logStore = try? OSLogStore(scope: .currentProcessIdentifier) else { return }
let oneHourAgo = logStore.position(date: Date().addingTimeInterval(-3600))
guard let allEntries = try? logStore.getEntries(at: oneHourAgo) else { return }
let filteredEntries = allEntries.compactMap { $0 as? OSLogEntryLog }.filter { $0.subsystem == Bundle.main.bundleIdentifier! }
                
var logText = ""
                
for entry in filteredEntries {
    logText.append(contentsOf: "\(entry.date): \(entry.composedMessage)\n")
}
                
let logData = Data(logText.utf8)

然后将 logData 附加到准备发送给支持人员的电子邮件中(例如,使用 MFMailComposeViewController 。)

Son of a Beach
2016-11-24

由于我们与用户之间的沟通不畅,该问题花了很长时间才解决。应用程序实际上 没有崩溃 ,只是没有启动(在某些用户看来也是如此)。

在发现该问题后,我们意识到其中一个事件没有触发(隐藏扩展启动画面的事件),这就是用户陷入困境的地方。我们使用的其中一个库没有正确处理错误情况,这让我们的工作变得更加困难。我很幸运在测试时进入了该状态,然后我可以从那里继续。

我更新了代码来处理该情况,现在问题已解决。

ewooycom
2016-11-30

我遇到过这个问题,我的情况可能非常具体,但我还是会分享我的经验。

这里的重点是应用程序仅在生产环境中崩溃,因此它仅在生产环境中触发,从而搞乱了构建。在我们的案例中,罪魁祸首是 React 的依赖项 之一,它会进行最小化。

需要注意的事项:

  1. 密切关注依赖项的任何更新
  2. 使用崩溃日志
tropicalfish
2017-03-09