我们的 React Native 应用只有一项不能失败的任务:知道卡车在哪里。
Truxo Tracker 是 truxo.ai 的司机端应用。司机接受货运订单后,应用需要在 Android 后台持续追踪位置——穿过得州乡村的信号盲区,熬过整夜,还要对抗 Android 系统“觉得自己更聪明”而杀掉进程的冲动。如果定位在后台悄悄死掉,调度员就会失去对一票 4 万美元货物的视野。如果应用在凌晨 3 点司机口袋里崩溃,定位追踪就彻底断了。
![]()
这个应用基于 React Native 和 Expo 构建。对我们做的绝大部分事情来说,这是一个很好的技术选型。但 Android 后台定位恰恰是抽象层最薄的地方。过去十个月里,我把大量时间花在了抽象层的另一侧:在 Crashlytics 里阅读 Java 堆栈、给 Kotlin 打补丁,还学到了许多我本不想知道的 JobScheduler 知识。
先给出一条整体时间线,再讲具体故事——包括其中一个修复如何引发了更严重的 Bug。
去年年底,Firebase Crashlytics 开始被这类原生崩溃刷屏:
Fatal Exception: java.lang.RuntimeException java.lang.NullPointerException at expo.modules.taskManager.TaskJobService.onStartJob at expo.modules.taskManager.TaskService.handleJob at LocationTaskConsumer.executeTaskWithLocationBundles at TaskService.executeTask
这不是 JavaScript 错误,也不是红屏错误。这是 expo-task-manager 内部的 Java NullPointerException——正是 Android 上唤醒 React Native 应用以传递后台定位更新的代码路径。
第一轮修复是在一月份,表面看起来都很正常,也确实是我们自己的问题。崩溃量随后明显下降。但有一类 NPE 仍然反复出现。在盯了足够多的 Crashlytics 堆栈之后,链条终于清晰了:这里有两个独立根本原因,而且都位于 JavaScript 桥接层之下。
让我不舒服的地方就在这里:其中一个原因是我自己的修复引入的。我以为补上了漏洞,结果却制造了一个更隐蔽的故障。这个 Bug 不总是崩溃,而是让任务管理器进入一个奇怪的状态,导致后续定位任务无法被正确唤醒。它在真实用户设备上比原来的崩溃更难复现,也难追踪得多。
最终,问题解决的关键是放弃把 expo-task-manager 当作黑盒,直接阅读它的原生源码,理解 JobScheduler 和 TaskService 的调度时序。我们也加了一层额外的守护机制:即使某个任务被系统杀死,应用也能在下次启动时主动检查并恢复定位服务。
这十个月教会我一件事:React Native 的跨平台抽象很强大,但当你依赖一个平台特有的系统能力时,你迟早要下潜到原生层。与其等到崩溃日志教你,不如提前准备好。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.