news 2026/9/4 8:57:36

【Android实战】全局单例WebSocket设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Android实战】全局单例WebSocket设计与实现

【Android+WebSocket】全局单例封装

文章目录

  • 【Android+WebSocket】全局单例封装
    • 摘要
    • 为什么需要全局单例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(),拿到的都是同一个实例
  • 自动处理连接、心跳、重连
  • 解耦,页面只需要关心消息

实战代码

这里以饿汉式为例(当然可以选择懒汉式、双重检查锁、静态内部类、枚举),先构造私有构造函数,初始化OkHttpClient

@GetterprivatestaticfinalSysWsManagerinstance=newSysWsManager();privateSysWsManager(){client=newOkHttpClient.Builder().connectTimeout(10,SECONDS)// 连接超时.writeTimeout(10,SECONDS)// 写入超时.readTimeout(10,SECONDS)// 读取超时.pingInterval(0,SECONDS)// 禁用OkHttp自带心跳,使用自定义心跳.retryOnConnectionFailure(true)// 连接失败自动重试(效果有限).build();}

初始化函数,只需要在继承Application的类中调用一次

publicvoidinit(Contextcontext){this.appContext=context.getApplicationContext();}

从0到1实现全局WebSocket管理器

WebSocket连接方法

@GetterprivateAtomicBooleanisConnected=newAtomicBoolean(false);publicCompletableFuture<Boolean>connect(StringunionUid){// 如果有心跳或处理正在重连,这里要先停止或取消// 如果已经连接,直接返回成功if(isConnected.get()&&webSocket!=null){returnCompletableFuture.completedFuture(true);}// 创建异步结果对象connectionFuture=newCompletableFuture<>();// 构建 WebSocket urlStringurl=NetworkApiConfig.WS_URL+unionUid;Requestrequest=newRequest.Builder().url(url).build();// 发起 WebSocket 连接webSocket=client.newWebSocket(request,newWebSocketListener(){@OverridepublicvoidonOpen(@NonNullWebSocketwebSocket,@NonNullResponseresponse){}@OverridepublicvoidonFailure(@NonNullWebSocketwebSocket,@NonNullThrowablet,Responseresponse){}@OverridepublicvoidonClosed(@NonNullWebSocketwebSocket,intcode,@NonNullStringreason){}@OverridepublicvoidonMessage(@NonNullWebSocketwebSocket,@NonNullStringtext){}});returnconnectionFuture;}

处理心跳指数退避,原子布尔值保证状态线程安全

privateScheduledFuture<?>heartbeatFuture;privateAtomicIntegermissedHeartbeats=newAtomicInteger(0);privatestaticfinalintMAX_MISSED_HEARTBEATS=2;

开始心跳

privatevoidstartHeartbeat(){// 先停止心跳stopHeartbeat();heartbeatFuture=scheduler.scheduleWithFixedDelay(()->{if(!isConnected.get()||webSocket==null){// 未连接->停止心跳stopHeartbeat();}intmissed=missedHeartbeats.incrementAndGet();if(missed>MAX_MISSED_HEARTBEATS){// n次未收到心跳,主动断开连接return;}booleansuccess=webSocket.send("ping");if(success){}else{// 主动断开连接}},HEARTBEAT_INTERVAL_SECONDS,HEARTBEAT_INTERVAL_SECONDS,SECONDS);}

停止心跳

privatevoidstopHeartbeat(){if(heartbeatFuture!=null&&!heartbeatFuture.isDone()){heartbeatFuture.cancel(false);heartbeatFuture=null;}missedHeartbeats.set(0);}

重点
指数退避,调度重连,优雅地解决连续访问后端导致浪费资源的问题
同时上限60秒,又不会造成用户长时间等待的窘迫

privatevoidscheduleReconnect(){if(reconnectAttempts>=MAX_RECONNECT_ATTEMPTS){// 已达到最大重连次数,停止重连// 停止重连逻辑return;}// 指数退避:2, 4, 8, 16, 32, 60(max)intdelay=BASE_RECONNECT_DELAY_SECONDS*(int)Math.pow(2,reconnectAttempts);delay=Math.min(delay,60);reconnectAttempts++;// 通知监听器ws连接状态notifyReconnecting(reconnectAttempts);reconnectFuture=scheduler.schedule(()->{if(!isManualDisconnect&&currentUnionUid!=null&&!isConnected.get()){connect(currentUnionUid);}},delay,SECONDS);}

发送消息

publicvoidsendMessage(Stringmessage){if(!isConnected.get()||webSocket==null){// 检查连接状态return;}booleansuccess=webSocket.send(message);if(success){// 自行提示/日志/提示框等待}else{}}

使用方式

全局(extends Application中的重写onCreate()方法中)
根据业务在获取到uid后进行连接操作

/* 全局WebSocket初始化 */SysWsManager.getInstance().init(this);SysWsManager.getInstance().connect(tokenVo.getLinkUnionUid()).thenAccept(isConnect->{if(isConnect){// 连接成功return;}// 连接失败});

发送消息

// 构建jsonJSONObjectjsonObject=newJSONObject();try{jsonObject.put("type",type);// jsonObject.put("", );// jsonObject.put("", );}catch(JSONExceptione){thrownewRuntimeException(e);}SysWsManager.getInstance().sendMessage(jsonObject.toString());

监听消息

SysWsManager.getInstance().addMessageListener(message->{if(message==null||"pong".equals(message)||"欢迎连接 WebSocket!".equals(message)){return;}try{Gsongson=newGson();JsonObjectjson=gson.fromJson(message,JsonObject.class);Stringtype=json.get("type").getAsString();switch(type){case"receive_new_friend_request"->handleReceiveNewFriendRequest(json);case"after_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:usesCleartextTraffic="true">

Service保活:如需进程保活,可将WebSocket放在前台Service中运行
内存泄漏:页面销毁时记得移除监听器
消息幂等性:后端应保证推送消息可重复消费,防止重复处理

通过本文的全局单例封装,你的Android应用可以拥有一个稳定、高效、易于维护的WebSocket长连接服务。有任何问题欢迎在评论区交流讨论~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 8:56:18

AI基建新瓶颈:数据中心电力成本、PUE与电价敏感性分析

各位做数据中心、云计算和 AI Infra 的朋友们&#xff0c;最近圈子里讨论最多的话题之一&#xff0c;除了模型效果&#xff0c;就是“电”了。我们常说算力是 AI 时代的水和电&#xff0c;但当 AI 基建真的开始大规模落地时&#xff0c;现实中的“电”却成了比芯片更难解决的问…

作者头像 李华
网站建设 2026/9/4 8:54:55

ArtyS7开发板上构建RISC-V SoC的全流程实践

简介&#xff1a;本资源是一套面向FPGA开发与RISC-V架构学习者的系统级芯片&#xff08;SoC&#xff09;实践项目&#xff0c;适用于电子工程、计算机体系结构方向的本科生、研究生及嵌入式开发者&#xff0c;解决RISC-V处理器核在真实硬件平台上的集成、综合与验证难题。项目基…

作者头像 李华
网站建设 2026/9/4 8:53:24

如何通过语音智能体提升数字员工的业务效率?

数字员工在现代企业中正日益展现出其优化业务流程的实际价值。利用语音智能体的支持&#xff0c;数字员工能够实现更加高效和灵活的客户服务&#xff0c;帮助企业降低运营成本。具体而言&#xff0c;数字员工能够快速响应客户咨询&#xff0c;减少人工干预、进而提升整体工作效…

作者头像 李华
网站建设 2026/9/4 8:53:18

FreeRTOS 中的 Hook 回调函数详解

在学习 FreeRTOS 时&#xff0c;经常会看到一些名字比较特殊的函数&#xff0c;例如&#xff1a;void vApplicationIdleHook(void); void vApplicationTickHook(void); void vApplicationMallocFailedHook(void); void vApplicationStackOverflowHook(TaskHandle_t xTask, char…

作者头像 李华
网站建设 2026/9/4 8:48:38

Dynamic Topic Evolution with Temporal Decay and Attention in Large Language Models

该文章提出了一种基于时序大语言模型的动态主题演化建模框架,通过融合时间衰减函数与注意力机制,有效捕捉主题随时间的生成、扩展与衰退过程,在多个真实语料实验中表现优于现有模型。 一、文章主要内容总结 研究背景:传统主题建模方法基于静态假设,无法捕捉主题随环境、政…

作者头像 李华
网站建设 2026/9/4 8:48:20

突破软件开发新瓶颈:从代码可读性到系统可理解性的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华