【AndroidWebSocket】全局单例封装文章目录【AndroidWebSocket】全局单例封装摘要为什么需要全局单例WebSocket核心设计思路什么是单例WebSocketManager实战代码从0到1实现全局WebSocket管理器使用方式总结注意事项摘要在Android开发中WebSocket是實現实时通信的常用方案。然而如果每个页面都独立建立WebSocket连接会导致连接数失控、状态同步困难、资源浪费等一系列问题。本文深入分析了不使用单例模式的弊端并从零开始封装一个全局唯一的WebSocket管理器。该管理器基于OkHttpClient实现采用饿汉式单例保证全局唯一性通过原子类保证线程安全实现了自定义心跳保活机制、指数退避自动重连策略以及观察者模式的消息分发。文章提供了完整的代码实现和使用示例帮助开发者快速构建稳定、高效、易维护的WebSocket长连接服务。Abstract: When using WebSocket across multiple pages in Android applications, developers often encounter issues such as uncontrolled connection counts, difficult state synchronization, and resource waste. This article implements a global singleton WebSocket manager from scratch, adopting the eager initialization singleton pattern with OkHttpClient as the connection foundation. It fully implements core features including heartbeat keep-alive, exponential backoff reconnection, and message listening. The solution helps developers build stable and efficient long-connection services for their Android apps.为什么需要全局单例WebSocket在开始编写代码之前我们先来分析一下不使用单例模式可能带来的问题连接数失控如果每个页面都独立建立WebSocket连接每当打开新页面时都需要重新建立连接并添加监听这会给服务器带来巨大压力。状态同步困难不同页面之间的WebSocket状态难以保持一致。例如页面1已经检测到连接断开而页面2仍在尝试向后端发送消息又如多个页面同时监听同一字段如消息角标容易导致角标异常递增和消息重复处理。资源严重浪费每个独立的WebSocket连接都需要经过HTTP握手升级过程这会消耗大量的网络资源和内存降低应用整体性能。核心设计思路什么是单例单例模式确保一个类仅有一个实例只需要一次初始化并提供一个全局访问点。WebSocketManager我们将创建一个WebSocketManager类或对象它具备以下能力:全局只初始化一次不管在哪里调用getInstance()拿到的都是同一个实例自动处理连接、心跳、重连解耦页面只需要关心消息实战代码这里以饿汉式为例当然可以选择懒汉式、双重检查锁、静态内部类、枚举先构造私有构造函数初始化OkHttpClientGetterprivatestaticfinalSysWsManagerinstancenewSysWsManager();privateSysWsManager(){clientnewOkHttpClient.Builder().connectTimeout(10,SECONDS)// 连接超时.writeTimeout(10,SECONDS)// 写入超时.readTimeout(10,SECONDS)// 读取超时.pingInterval(0,SECONDS)// 禁用OkHttp自带心跳使用自定义心跳.retryOnConnectionFailure(true)// 连接失败自动重试(效果有限).build();}初始化函数只需要在继承Application的类中调用一次publicvoidinit(Contextcontext){this.appContextcontext.getApplicationContext();}从0到1实现全局WebSocket管理器WebSocket连接方法GetterprivateAtomicBooleanisConnectednewAtomicBoolean(false);publicCompletableFutureBooleanconnect(StringunionUid){// 如果有心跳或处理正在重连这里要先停止或取消// 如果已经连接直接返回成功if(isConnected.get()webSocket!null){returnCompletableFuture.completedFuture(true);}// 创建异步结果对象connectionFuturenewCompletableFuture();// 构建 WebSocket urlStringurlNetworkApiConfig.WS_URLunionUid;RequestrequestnewRequest.Builder().url(url).build();// 发起 WebSocket 连接webSocketclient.newWebSocket(request,newWebSocketListener(){OverridepublicvoidonOpen(NonNullWebSocketwebSocket,NonNullResponseresponse){}OverridepublicvoidonFailure(NonNullWebSocketwebSocket,NonNullThrowablet,Responseresponse){}OverridepublicvoidonClosed(NonNullWebSocketwebSocket,intcode,NonNullStringreason){}OverridepublicvoidonMessage(NonNullWebSocketwebSocket,NonNullStringtext){}});returnconnectionFuture;}处理心跳指数退避原子布尔值保证状态线程安全privateScheduledFuture?heartbeatFuture;privateAtomicIntegermissedHeartbeatsnewAtomicInteger(0);privatestaticfinalintMAX_MISSED_HEARTBEATS2;开始心跳privatevoidstartHeartbeat(){// 先停止心跳stopHeartbeat();heartbeatFuturescheduler.scheduleWithFixedDelay(()-{if(!isConnected.get()||webSocketnull){// 未连接-停止心跳stopHeartbeat();}intmissedmissedHeartbeats.incrementAndGet();if(missedMAX_MISSED_HEARTBEATS){// n次未收到心跳主动断开连接return;}booleansuccesswebSocket.send(ping);if(success){}else{// 主动断开连接}},HEARTBEAT_INTERVAL_SECONDS,HEARTBEAT_INTERVAL_SECONDS,SECONDS);}停止心跳privatevoidstopHeartbeat(){if(heartbeatFuture!null!heartbeatFuture.isDone()){heartbeatFuture.cancel(false);heartbeatFuturenull;}missedHeartbeats.set(0);}重点指数退避调度重连优雅地解决连续访问后端导致浪费资源的问题同时上限60秒又不会造成用户长时间等待的窘迫privatevoidscheduleReconnect(){if(reconnectAttemptsMAX_RECONNECT_ATTEMPTS){// 已达到最大重连次数停止重连// 停止重连逻辑return;}// 指数退避2, 4, 8, 16, 32, 60(max)intdelayBASE_RECONNECT_DELAY_SECONDS*(int)Math.pow(2,reconnectAttempts);delayMath.min(delay,60);reconnectAttempts;// 通知监听器ws连接状态notifyReconnecting(reconnectAttempts);reconnectFuturescheduler.schedule(()-{if(!isManualDisconnectcurrentUnionUid!null!isConnected.get()){connect(currentUnionUid);}},delay,SECONDS);}发送消息publicvoidsendMessage(Stringmessage){if(!isConnected.get()||webSocketnull){// 检查连接状态return;}booleansuccesswebSocket.send(message);if(success){// 自行提示/日志/提示框等待}else{}}使用方式全局(extends Application中的重写onCreate()方法中)根据业务在获取到uid后进行连接操作/* 全局WebSocket初始化 */SysWsManager.getInstance().init(this);SysWsManager.getInstance().connect(tokenVo.getLinkUnionUid()).thenAccept(isConnect-{if(isConnect){// 连接成功return;}// 连接失败});发送消息// 构建jsonJSONObjectjsonObjectnewJSONObject();try{jsonObject.put(type,type);// jsonObject.put(, );// jsonObject.put(, );}catch(JSONExceptione){thrownewRuntimeException(e);}SysWsManager.getInstance().sendMessage(jsonObject.toString());监听消息SysWsManager.getInstance().addMessageListener(message-{if(messagenull||pong.equals(message)||欢迎连接 WebSocket.equals(message)){return;}try{GsongsonnewGson();JsonObjectjsongson.fromJson(message,JsonObject.class);Stringtypejson.get(type).getAsString();switch(type){casereceive_new_friend_request-handleReceiveNewFriendRequest(json);caseafter_agree-handleAfterAgree(json);default-;}}catch(JsonParseExceptione){Log.e(TAG,JSON 解析失败原始消息: message,e);}catch(Exceptione){Log.e(TAG,处理消息时出错: e.getMessage(),e);}});总结通过以上实现我们的全局单例WebSocket管理器具有以下优势特性解决问题实现方式全局唯一连接连接数失控、资源浪费饿汉式单例模式自动重连机制网络波动导致连接中断指数退避算法最大间隔60秒心跳保活长连接被中间路由断开定时发送ping检测pong响应状态同步多页面连接状态不一致AtomicBoolean原子变量线程安全多线程并发访问问题ScheduledExecutorService 原子类解耦设计页面与WebSocket强耦合观察者模式监听器注意事项Android 9.0及以上需要添加网络明文配置!-- AndroidManifest.xml --applicationandroid:usesCleartextTraffictrueService保活如需进程保活可将WebSocket放在前台Service中运行内存泄漏页面销毁时记得移除监听器消息幂等性后端应保证推送消息可重复消费防止重复处理通过本文的全局单例封装你的Android应用可以拥有一个稳定、高效、易于维护的WebSocket长连接服务。有任何问题欢迎在评论区交流讨论~