You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.ts PWA生成APK后,设备休眠时保持WebSocket连接

问题描述

我正在开发一个React.ts PWA项目,通过Bubblewrap库生成APK适配Android设备。项目已集成SignalR,收到HUB更新时会更新Redux数据并发送通知,但设备进入休眠模式时WebSocket连接会断开,需要实现持续实时通信并能通知用户。

export const SignalRWrapper = ({ children }: Props) => {
  const dispatch = useAppDispatch();
  const userRedux = useAppSelector((state) => state.user);

  useEffect(() => {
    if(Notification.permission === 'default')
    {
      Notification.requestPermission();
    }

    SignalRConnection.on("tatus", (planningReadDto: PlanningReadDto) => {
        dispatch(updateBooking(planning));
        
        if(Notification.permission === 'granted' && userRedux.user && isMyTruck(planningReadDto.currentStatus, userRedux.user?.habilitations))
        {
          showNotification(planning.name, planningReadDto.currentStatus);
        }
    });


    SignalRConnection.onclose(()=>{
      SignalRConnection.invoke("KeepAlive");
    })

    setInterval(() => {
      SignalRConnection.invoke("KeepAlive");
    }, 3000);

  }, [userRedux.user]);

  function showNotification(name: string, currentStatus : TruckStatus) {
    navigator.serviceWorker.ready.then((registration) => {
      registration.showNotification(`${name} : Nouveau statut`,{
        body: `${name} passe au statut ${getNameStatus[currentStatus]}`
      })
    })
  }
解决方案

1. 修复SignalR重连逻辑

现有onclose回调直接调用invoke("KeepAlive")无效——连接断开后无法执行invoke操作,必须重新发起连接请求。改用指数退避重连策略,避免频繁重试耗尽系统资源:

SignalRConnection.onclose(async (error) => {
  console.log('SignalR连接断开,启动重连:', error);
  let retryDelay = 1000;
  while (true) {
    try {
      await SignalRConnection.start();
      console.log('SignalR重连成功');
      break;
    } catch (retryError) {
      console.error('重连失败,等待重试:', retryError);
      await new Promise(resolve => setTimeout(resolve, retryDelay));
      retryDelay = Math.min(retryDelay * 2, 30000); // 最大延迟限制为30秒
    }
  }
});

同时移除手动设置的3秒KeepAlive定时器——SignalR自身内置心跳机制(默认30秒一次),重复调用会造成冗余请求。

2. 利用Android后台能力维持连接

设备休眠时,系统会切断普通WebSocket连接,需结合PWA后台同步和Android权限实现唤醒:

  • 修改Bubblewrap的twa-manifest.json,添加必要权限:
    "permissions": [
      "WAKE_LOCK",
      "ACCESS_NETWORK_STATE"
    ]
    
  • 在Service Worker中注册后台同步事件,定期检查并恢复SignalR连接:
    self.addEventListener('sync', (event) => {
      if (event.tag === 'signalr-reconnect') {
        event.waitUntil(
          new Promise(async (resolve) => {
            try {
              // 重新初始化或启动SignalR连接
              await signalRConnection.start();
              resolve();
            } catch (e) {
              // 重连失败,交由下次同步任务处理
              resolve();
            }
          })
        );
      }
    });
    
  • 前端在连接断开时触发后台同步请求:
    if ('serviceWorker' in navigator && 'SyncManager' in window) {
      navigator.serviceWorker.ready.then(reg => {
        reg.sync.register('signalr-reconnect');
      });
    }
    

3. 改用FCM推送实现可靠通知

设备休眠时WebSocket无法稳定维持,最可靠的方案是替换SignalR实时推送为Firebase Cloud Messaging(FCM):

  • 后端不再通过SignalR推送,而是在事件触发时向FCM服务器发送消息
  • Service Worker监听FCM推送事件,展示通知并同步更新前端Redux:
    self.addEventListener('push', (event) => {
      const data = event.data?.json() || {};
      const notificationOptions = {
        body: data.body,
        icon: '/icons/icon-192x192.png',
        requireInteraction: true // 通知保持至用户手动关闭
      };
    
      event.waitUntil(
        self.registration.showNotification(data.title, notificationOptions)
      );
    
      // 向前端发送消息更新Redux
      self.clients.matchAll({ type: 'window' }).then(clients => {
        clients.forEach(client => {
          client.postMessage({
            type: 'UPDATE_BOOKING',
            payload: data.planning
          });
        });
      });
    });
    
  • 前端监听Service Worker消息,更新Redux状态:
    useEffect(() => {
      const handleServiceWorkerMessage = (event: MessageEvent) => {
        if (event.data.type === 'UPDATE_BOOKING') {
          dispatch(updateBooking(event.data.payload));
        }
      };
    
      navigator.serviceWorker.addEventListener('message', handleServiceWorkerMessage);
      return () => {
        navigator.serviceWorker.removeEventListener('message', handleServiceWorkerMessage);
      };
    }, [dispatch]);
    

4. 优化通知权限与展示逻辑

完善通知权限请求流程,确保授权后通知能正常触发:

useEffect(() => {
  const requestNotificationPermission = async () => {
    if (Notification.permission === 'default') {
      const permission = await Notification.requestPermission();
      if (permission !== 'granted') {
        console.warn('用户拒绝通知权限,将无法接收实时提醒');
      }
    }
  };
  requestNotificationPermission();
}, []);

同时优化通知展示函数,增加错误捕获:

async function showNotification(name: string, currentStatus: TruckStatus) {
  try {
    const registration = await navigator.serviceWorker.ready;
    await registration.showNotification(`${name} : Nouveau statut`, {
      body: `${name} passe au statut ${getNameStatus[currentStatus]}`,
      requireInteraction: true,
    });
  } catch (error) {
    console.error('通知展示失败:', error);
  }
}

内容的提问来源于stack exchange,提问作者Tarik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 11:22:48