From e00163e2d904fb77517c5fc7ebec14ef9ec718a7 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 30 Jun 2026 17:57:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(client):=20statsStream/statusStream=20?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E4=B8=BA=E5=85=B1=E4=BA=AB=E5=B9=BF=E6=92=AD?= =?UTF-8?q?=E6=B5=81,=E4=BF=AE=E5=BB=B6=E8=BF=9F=E4=B8=8D=E5=9B=9E?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:VpnNativeBridge.statsStream getter 每次访问都 receiveBroadcastStream(),多订阅者 (连接页速度 + ConnectionController._onStats 回写 urltest 延迟)各起一条原生订阅;但 EventChannel 原生侧只留一个 sink(StatsStreamHandler.onListen 覆盖),后订阅者赢、先订阅者 变哑。速度订阅赢了 sink → _onStats 收不到 → urltest 实测(已确认到达 native:reality-out=595) 永不回写 node.ping → 连接页延迟一直 —。 修复:receiveBroadcastStream() 只调一次、缓存成 asBroadcastStream() 共享流,所有 Dart 订阅者复用同一条原生订阅。 Co-Authored-By: Claude Opus 4.8 --- client/lib/bridge/vpn_bridge.dart | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/client/lib/bridge/vpn_bridge.dart b/client/lib/bridge/vpn_bridge.dart index 480c588..804fbe8 100644 --- a/client/lib/bridge/vpn_bridge.dart +++ b/client/lib/bridge/vpn_bridge.dart @@ -223,18 +223,28 @@ class VpnNativeBridge implements VpnBridge { Future setKillSwitch({required bool on}) => _method.invokeMethod('setKillSwitch', on); - @override - Stream get statusStream => _statusRaw + // ⚠️ EventChannel 原生侧只保留「一个」sink:每次 receiveBroadcastStream() + listen 都会 + // 触发 onListen 覆盖原生 sink → 多个订阅者(连接页速度 + ConnectionController 回写延迟) + // 互相抢,后订阅的赢、先订阅的流变哑。必须只调一次 receiveBroadcastStream()、缓存成共享 + // 广播流,所有 Dart 订阅者复用同一条原生订阅。 + late final Stream _statusStream = _statusRaw .receiveBroadcastStream() - .map((e) => VpnStatus.fromString(e?.toString() ?? 'error')); + .map((e) => VpnStatus.fromString(e?.toString() ?? 'error')) + .asBroadcastStream(); - @override - Stream get statsStream => _statsRaw + late final Stream _statsStream = _statsRaw .receiveBroadcastStream() .where((e) => e is Map) .map((e) => VpnStatsEvent.fromNativeMap( Map.from(e as Map), - )); + )) + .asBroadcastStream(); + + @override + Stream get statusStream => _statusStream; + + @override + Stream get statsStream => _statsStream; @override void dispose() {