我在实时通知中追逐“Ghost” Bug的那一天
Source: Dev.to
那个根本不存在的…还是存在?
几个月前,我在开发一款带实时通知的移动应用。功能看起来很直白:每当电商应用中有新订单产生时,用户应立即收到推送通知。
在开发阶段一切都运行得很完美——我的设备上通知几毫秒内就弹出了。但当 beta 测试者开始使用该应用时,出现了奇怪的现象:有些用户收到了重复的通知。感觉像在追逐幽灵。系统怎么会表现得如此不一致?
该应用使用 Firebase Cloud Messaging (FCM) 进行推送通知。我的最初怀疑对象是后端、移动框架,甚至是用户的设备。我一步步排查:
- 检查后端日志——所有通知都已正确发送。
- 认为问题是平台特定且微妙的。
经过数小时的调试,我发现罪魁祸首是:不小心注册了多个 Firebase 监听器。
根本原因
在我的 Flutter 代码中大致是这样:
// Example listener registration (simplified)
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// Handle incoming notification
showNotification(message);
});这个监听器在 每次用户导航到首页时都会被注册。每次导航都会新增一个监听器,所以如果用户来回切换三次,同一条通知就会被触发三次。此外,竞争条件有时会导致监听器在应用完全初始化之前就触发,从而出现延迟或丢失通知的情况。
解决方案
- 在应用启动时仅注册一次监听器(例如在
main()或顶层 provider 中),并将其放在单一、集中的位置。 - 让
showNotification()具备幂等性,即使同一条消息到达多次,应用也只显示一次。
// Register once, e.g., in main()
void main() {
WidgetsFlutterBinding.ensureInitialized();
FirebaseMessaging.onMessage.listen(_handleMessage);
runApp(MyApp());
}
// Centralized handler
void _handleMessage(RemoteMessage message) {
showNotification(message);
}
// Idempotent notification display
final Set _displayedMessageIds = {};
void showNotification(RemoteMessage message) {
final String id = message.messageId ?? '';
if (id.isEmpty || _displayedMessageIds.contains(id)) return;
_displayedMessageIds.add(id);
// TODO: Show the notification using your preferred method
}收获
- 幽灵 bug 往往源于细微的生命周期问题。
- 在实时系统中,始终要考虑有多少监听器存在、它们何时触发,以及用户在应用中切换页面时会发生什么。
- 将异步事件处理集中化可以防止重复操作,并让调试变得更加轻松。
对使用通知、异步事件或实时系统的开发者来说:务必仔细思考监听器的注册方式和生命周期管理——这可以为你省下数小时追逐幽灵的时间。