Flutter应用后台或闲置状态下接收Pusher/Socket消息的问题咨询
问题解决方案
一、根因说明
移动端Android/iOS系统都存在后台资源限制机制,应用退后台超过3-5分钟后,系统会主动回收非活跃的长连接资源,无论用Pusher还是Socket.io都无法完全规避该系统限制,后台期间的消息需配合系统推送服务补触达,前台恢复后需要主动重连恢复长连接。
你现有代码还有两个逻辑缺陷放大了该问题:
- 未监听应用前后台切换状态,前台恢复时没有主动检查连接状态
- disconnect方法直接关闭了全局StreamController,重连后无法继续接收事件
- connect方法仅在_pusher为null时初始化,已初始化过但断开的连接不会被重新激活
二、Pusher兼容双端修复方案
2.1 修改pusher.dart逻辑
class AppPusher { static final _appPusher = AppPusher._internal(); AppPusher._internal(); factory AppPusher() => _appPusher; final String _messaging = 'messaging'; final String _notifications = 'notifications'; PusherClient? _pusher; Channel? _channel; String? _channelName; // 不要在普通断开操作里关闭该控制器,全局复用 final _pusherData = StreamController<SocketResult>.broadcast(); Stream<SocketResult> get stream => _pusherData.stream; // 新增:获取当前连接状态 bool get isConnected => _pusher?.connectionState == ConnectionState.CONNECTED; void connect({required Me me}) { final token = _getToken(); // 已有实例且已连接,直接返回 if (_pusher != null && isConnected) return; // 已有实例但断开,先清理旧资源 if (_pusher != null) { _cleanOldResource(); } _pusher = PusherClient( AppSettings.pusher.key, PusherOptions( cluster: AppSettings.pusher.cluster, encrypted: true, auth: PusherAuth( AppSettings.pusher.url, headers: {'Authorization': 'Bearer $token'}, ), // 新增:开启活动检测,避免连接假死 activityTimeout: 30000, pongTimeout: 10000, ), autoConnect: true, enableLogging: AppSettings.pusher.enableLogging, ); // 新增:监听连接状态变化,断开自动重连 _pusher!.onConnectionStateChange((state) { if (state?.currentState == ConnectionState.DISCONNECTED) { // 延迟2秒重连,避免频繁重试 Future.delayed(const Duration(seconds: 2), () => connect(me: me)); } }); _channelName = 'private-user-${me.id}'; _channel = _pusher!.subscribe(_channelName!); _channel!.bind(_messaging, (event) { if (event?.data != null) { _pusherData.sink.add(SocketResult<SocketMessage>( type: ChannelType.message, me: me, data: SocketMessage.fromJson(jsonDecode(event.data!)), )); } }); _channel!.bind(_notifications, (event) { if (event?.data != null) { _pusherData.sink.add(SocketResult<SocketNotification>( type: ChannelType.notification, me: me, data: SocketNotification.fromJson(jsonDecode(event.data!)), )); } }); } // 新增:清理旧连接资源,不关闭StreamController void _cleanOldResource() { if (_pusher == null) return; if (_channel != null) { _channel!.unbind(_messaging); _channel!.unbind(_notifications); } if (_channelName != null) { _pusher!.unsubscribe(_channelName!); } _pusher!.disconnect(); _pusher = null; } // 仅在应用完全退出时调用该方法 void dispose() async { _cleanOldResource(); await _pusherData.close(); } String _getToken() { return locator<SharedPreferencesManager>() .getString(SharedPreferencesManager.accessToken) ?? ''; } }
2.2 修改app.dart逻辑,添加生命周期监听
// 混入WidgetsBindingObserver监听生命周期 class _AppState extends State<App> with WidgetsBindingObserver { late StreamSubscription _streamSubscription; final AppPusher _pusher = AppPusher(); void initPusher() { final me = context.read<MeProvider>().me!; _pusher.connect(me: me); // 避免重复监听,先取消旧订阅 _streamSubscription.cancel(); _streamSubscription = _pusher.stream.listen((event) { switch (event.type) { case ChannelType.message: context .read<ChatProvider>() .socketMessage(message: event.data, me: event.me); break; case ChannelType.notification: print('EVENT FROM NOTIFICATION: ${event.data}'); break; } }); } @override void initState() { super.initState(); // 注册生命周期监听 WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { initPusher(); }); } // 监听生命周期变化 @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); final me = context.read<MeProvider>().me; if (me == null) return; // 应用回到前台,检查连接状态,断开的话重新连接 if (state == AppLifecycleState.resumed) { if (!_pusher.isConnected) { initPusher(); } } } @override void dispose() { // 移除生命周期监听 WidgetsBinding.instance.removeObserver(this); _streamSubscription.cancel(); // 应用全局销毁时再调用dispose,普通页面销毁不要调用 _pusher.dispose(); super.dispose(); } }
2.3 双端系统适配
- Android端:在
AndroidManifest.xml中申请FOREGROUND_SERVICE权限,引导用户开启应用的「忽略电池优化」权限,避免后台被系统查杀 - iOS端:在Xcode中开启
Background Modes里的Remote notifications权限,后台期间的消息通过APNs推送触达用户
三、迁移至Socket.io方案
如果确认是Pusher SDK兼容问题,迁移Socket.io的逻辑和上述Pusher改造逻辑一致:
- 引入
socket_io_client依赖 - 替换Pusher初始化逻辑为Socket.io初始化,开启
autoConnect、reconnection配置 - 保留相同的生命周期监听、连接状态监听、事件转发逻辑即可,Socket.io自带的重连机制成熟度更高,适配成本很低。
内容的提问来源于stack exchange,提问作者user9662684
相关产品推荐
相关产品推荐

