热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

在Android中使用WebSocket实现消息通信的方法详解

前言 消息推送功能可以说移动APP不可缺少的功能之一,一般简单的推送我们可以使用第三方推送的SDK,比如极光推送、信鸽推送等,但是对于

前言

消息推送功能可以说移动APP不可缺少的功能之一,一般简单的推送我们可以使用第三方推送的SDK,比如极光推送、信鸽推送等,但是对于消息聊天这种及时性有要求的或者三方推送不满足业务需求的,我们就需要使用WebSocket实现消息推送功能。

基本流程

WebSocket是什么,这里就不做介绍了,我们这里使用的开源框架是https://github.com/TakahikoKawasaki/nv-websocket-client

基于开源协议我们封装实现WebSocket的连接、注册、心跳、消息分发、超时任务功能,基本流程如下:

连接功能

首先我们新建一个项目,在build.grade中添加配置

compile 'com.neovisionaries:nv-websocket-client:2.2'

新建websocket管理类WsManger

public class WsManager {
 
 private volatile static WsManager wsManger;
 
 private WsManager() {
 }
 
 public static WsManager getWsManger() {
 if (wsManger == null) {
 synchronized (WsManager.class) {
 if (wsManger == null) {
  wsManger = new WsManager();
 }
 }
 }
 return wsManger;
 }
 
 
}

接下来添加连接方法,我们将webSocket的状态分为三种,新建WsStatue枚举类对应起来

public enum WsStatus {
 
 /**
 * 连接成功
 */
 CONNECT_SUCCESS,
 /**
 * 连接失败
 */
 CONNECT_FAIL,
 /**
 * 正在连接
 */
 CONNECTING;
}

连接方法如下所示:

/**
 * 连接方法 这里要判断是否登录 此处省略
 */
public void connect() {
 //WEB_SOCKET_API 是连接的url地址,
 // CONNECT_TIMEOUT是连接的超时时间 这里是 5秒
 try {
 ws = new WebSocketFactory().createSocket(WEB_SOCKET_API, CONNECT_TIMEOUT)
 //设置帧队列最大值为5
 .setFrameQueueSize(5)
 //设置不允许服务端关闭连接却未发送关闭帧
 .setMissingCloseFrameAllowed(false)
 //添加回调监听
 .addListener(new WsListener())
 //异步连接
 .connectAsynchronously();
 } catch (IOException e) {
 e.printStackTrace();
 }
 setStatus(WsStatus.CONNECTING);
}

调用连接方法后 我们来看连接的回调 也就是WsListener

/**
 * websocket回调事件
 */
private class WsListener extends WebSocketAdapter {
 
 
 @Override
 public void onConnected(WebSocket websocket, Map> headers) throws Exception {
 Log.d(TAG, "onConnected: 连接成功");
 }
 
 @Override
 public void onConnectError(WebSocket websocket, WebSocketException exception) throws Exception {
 Log.d(TAG, "onConnectError: 连接失败");
 }
 
 @Override
 public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame,
  WebSocketFrame clientCloseFrame,
  boolean closedByServer) throws Exception {
 Log.d(TAG, "onDisconnected: 断开连接");
 
 }
 
 @Override
 public void onTextMessage(WebSocket websocket, String text) throws Exception {
 Log.d(TAG, "onTextMessage: 收到消息:" + text);
 }
}

下面我们调用连接方法

WsManager.getWsManger().connect();

运行项目我们可以看到如下打印:

 

此处我们要做的处理是,如果收到连接失败或者断开连接的回调 需要重新连接,我们重新调用一次连接方法即可,并且如果超过三次重连失败,我们在业务中可以通过调用接口来获取数据,避免数据丢失,此处细节省略。

协议封装

此处协议如下所示:

{
 "action":"",
 "requestChild":{
 "clientType":"",
 "id":""
 }
}

心跳、发送请求都属于客户端主动发送请求,对于请求结果我们分为成功和失败以及超时,发送超时我们是收不到服务器任何回复的,所以我们需要在发送之后将发送放在超时任务队列中,如果请求成功将任务从超时队列中移除,超时从超时队列中获取任务重新请求。

超时任务队列中回调有成功、失败、超时。

我们按照上述协议,新增对应实体类,采用Builder设计模式

public class Request {
 
 /**
 * 行为
 */
 private String action;
 
 /**
 * 请求体
 */
 private RequestChild req;
 
 
 /**
 * 请求次数
 */
 private transient int reqCount;
 
 /**
 * 超时的时间
 */
 private transient int timeOut;
 
 
 public Request() {
 }
 
 
 public Request(String action, int reqCount, int timeOut, RequestChild req) {
 this.action = action;
 this.req = req;
 this.reqCount = reqCount;
 this.timeOut = timeOut;
 }
 
 
 public static class Builder {
 //action 请求类型
 private String action;
 //请求子类数据 按照具体业务划分
 private RequestChild req;
 //请求次数 便于重试
 private int reqCount;
 //超时时间
 private int timeOut;
 
 public Builder action(String action) {
 this.action = action;
 return this;
 }
 
 
 public Builder req(RequestChild req) {
 this.req = req;
 return this;
 }
 
 
 public Builder reqCount(int reqCount) {
 this.reqCount = reqCount;
 return this;
 }
 
 public Builder timeOut(int timeOut) {
 this.timeOut = timeOut;
 return this;
 }
 
 public Request build() {
 return new Request(action, reqCount, timeOut, req);
 }
 
 }
}
 

public class RequestChild {
 
 /**
 * 设备类型
 */
 private String clientType;
 
 
 /**
 * 用于用户注册的id
 */
 private String id;
 
 public RequestChild(String clientType, String id) {
 this.clientType = clientType;
 this.id = id;
 }
 
 public RequestChild() {
 }
 
 
 public static class Builder {
 private String clientType;
 private String id;
 
 public RequestChild.Builder setClientType(String clientType) {
 this.clientType = clientType;
 return this;
 }
 
 
 public RequestChild.Builder setId(String id) {
 this.id = id;
 return this;
 }
 
 
 public RequestChild build() {
 return new RequestChild(clientType, id);
 }
 
 }
 
 
}

我们添加一个发送请求的方法如下:

/**
 * 发送请求
 *
 * @param request 请求体
 * @param reqCount 请求次数
 * @param requestListern 请求回调
 */
private void senRequest(Request request, final int reqCount, final RequestListern requestListern) {
 if (!isNetConnect()) {
 requestListern.requestFailed("网络未连接");
 return;
 }
 
}

请求回调如下所示

public interface RequestListern {
 
 /**
 * 请求成功
 */
 void requestSuccess();
 
 /**
 * 请求失败
 *
 * @param message 请求失败消息提示
 */
 void requestFailed(String message);
}

接着我们要把请求放在超时队列中,新建超时任务类,对应的分别是请求参数、请求回调、任务调度

public class TimeOutTask {
 
 
 /**
 * 请求主体
 */
 private Request request;
 
 /**
 * 通用返回
 */
 private RequestCallBack requestCallBack;
 
 /**
 * r任务
 */
 private ScheduledFuture scheduledFuture;
 
 
 public TimeOutTask(Request request,
  RequestCallBack requestCallBack,
  ScheduledFuture scheduledFuture) {
 this.request = request;
 this.requestCallBack = requestCallBack;
 this.scheduledFuture = scheduledFuture;
 }
 
 public ScheduledFuture getScheduledFuture() {
 return scheduledFuture;
 }
 
 public void setScheduledFuture(ScheduledFuture scheduledFuture) {
 this.scheduledFuture = scheduledFuture;
 }
 
 public Request getRequest() {
 return request;
 }
 
 public void setRequest(Request request) {
 this.request = request;
 }
 
 public RequestCallBack getRequestCallBack() {
 return requestCallBack;
 }
 
 public void setRequestCallBack(RequestCallBack requestCallBack) {
 this.requestCallBack = requestCallBack;
 }
 
}

RequestCallBack是超时任务的回调,只是比请求回调多了个超时,因为超时的处理机制是一样的,所以这里我们没必要将超时回调到请求中

public interface RequestCallBack {
 
 /**
 * 请求成功
 */
 void requestSuccess();
 
 /**
 * 请求失败
 *
 * @param request 请求体
 * @param message 请求失败的消息
 */
 void requestFailed(String message, Request request);
 
 /**
 * 请求超时
 *
 * @param request 请求体
 */
 void timeOut(Request request);
}
/**
 * 添加超时任务
 */
private ScheduledFuture enqueueTimeout(final Request request, final long timeout) {
 Log.d(TAG, " " + "enqueueTimeout: 添加超时任务类型为:" + request.getAction());
 return executor.schedule(new Runnable() {
 @Override
 public void run() {
 TimeOutTask timeoutTask = callbacks.remove(request.getAction());
 if (timeoutTask != null) {
 timeoutTask.getRequestCallBack().timeOut(timeoutTask.getRequest());
 }
 }
 }, timeout, TimeUnit.MILLISECONDS);
}

超时任务的方法是通过任务调度定时调用,请求成功后我们会把超时任务移除,当到了超时时间时,任务还存在就说明任务超时了。

每次的任务我们以action为键值存在hashMap中

private Map callbacks = new HashMap<>();

将任务放入超时任务代码如下所示:

final ScheduledFuture timeoutTask = enqueueTimeout(request, request.getTimeOut());
 
final RequestCallBack requestCallBack = new RequestCallBack() {
 @Override
 public void requestSuccess() {
 requestListern.requestSuccess();
 }
 
 @Override
 public void requestFailed(String message, Request request) {
 requestListern.requestFailed(message);
 }
 
 @Override
 public void timeOut(Request request) {
 timeOutHanlder(request);
 }
};
callbacks.put(request.getAction(),
 new CallbackWrapper(request, requestCallBack, timeoutTask));

一般而言,任务超时都是由于连接原因导致,所以我们这里可以尝试重试一次,如果还是超时,通过 timeOutHanlder(request);方法 进行重新连接,重连代码和连接代码一样,这里就省略了,做好这步操作,我们就可以发送消息了。

/**
 * 超时任务
 */
private void timeOutHanlder(Request requset) {
 setStatus(WsStatus.CONNECT_FAIL);
 //这里假装有重连
 Log.d(TAG, "timeOutHanlder: 请求超时 准备重连");
}

到这里我们的流程基本可以走通了。

心跳

首先我们要了解下心跳的作用是什么,心跳是在连接成功后,通过固定的间隔时间向服务器发送询问,当前是否还在线,有很多人说心跳失败我们就重连,成功就继续心跳,但是这里要注意的是,我们一般是收不到心跳失败回调的,心跳也是向服务器发送数据,所以我们要将所有的主动请求都放在超时任务队列中,

所以对websocket来说 请求结果有三种:成功、失败、超时,对于用户 只有成功、失败即可。

至于心跳、注册等请求发送的数据是什么,这就得看我们与服务端定的协议是什么样了,通常来说 分为action 和 requestBody,协议格式我们再第二步已经封装好了,这里我们以心跳任务为例验证上面的封装。

/**
 * 心跳
 */
void keepAlive() {
 
 Request request = new Request.Builder()
 .reqCount(0)
 .timeOut(REQUEST_TIMEOUT)
 .action(ACTION_KEEPALIVE).build();
 
 WsManager.getWsManger().senRequest(request, request.getReqCount() + 1, new RequestListern() {
 @Override
 public void requestSuccess() {
 Log.d(TAG, "requestSuccess: 心跳发送成功了");
 }
 
 @Override
 public void requestFailed(String message) {
 }
 });
}

我们每间隔10s中开启一次心跳任务

/**
 * 开始心跳
 */
public void startKeepAlive() {
 mHandler.postDelayed(mKeepAliveTask, HEART_BEAT_RATE);
}
/**
 * 心跳任务
 */
private Runnable mKeepAliveTask = new Runnable() {
 
 @Override
 public void run() {
 keepAlive();
 mHandler.removeCallbacks(mKeepAliveTask);
 mHandler.postDelayed(mKeepAliveTask, HEART_BEAT_RATE);
 }
};

为了便于操作演示,在主页面上加个按钮 ,点击按钮调用startKeepAlive方法,运行如下所示: 

我们可以看到心跳返回的statue是300 不成功,5秒之后走到了请求超时的方法中,所以如果状态返回成功的话,我们需要回调给调用者

/**
 * 处理 任务回调
 *
 * @param action 请求类型
 */
void disPatchCallbackWarp(String action, boolean isSuccess) {
 CallbackWrapper callBackWarp = callbacks.remove(action);
 if (callBackWarp == null) {
 Logger.d(TAG+" "+ "disPatchCallbackWarp: 任务队列为空");
 } else {
 callBackWarp.getScheduledFuture().cancel(true);
 if (isSuccess) {
 callBackWarp.getRequestCallBack().requestSuccess();
 } else {
 callBackWarp.getRequestCallBack().requestFailed("", new Request());
 }
 
 }
}

这样调用者才知道成功或失败。

发送其他消息与心跳一样,只是请求参数不同而已,修改Request参数即可。这样我们根据协议和业务就实现一个比较规范的webSocket消息推送流程了。

 到此这篇关于在Android中使用WebSocket实现消息通信的方法详解的文章就介绍到这了,更多相关Android使用WebSocket实现消息通信内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 如何在任意浏览器中轻松安装并使用VSCode——Codeserver简易指南
    code-server 是一款强大的工具,允许用户在任何服务器上部署 VSCode,并通过浏览器进行访问和使用。这一解决方案不仅简化了开发环境的搭建过程,还提供了高度灵活的工作方式。用户只需访问 GitHub 上的官方仓库(GitHub-coder/code-server),即可获取详细的安装和配置指南,快速启动并运行 code-server。无论是个人开发者还是团队协作,code-server 都能提供高效、便捷的代码编辑体验。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • REST与RPC:选择哪种API架构风格?
    在探讨REST与RPC这两种API架构风格的选择时,本文首先介绍了RPC(远程过程调用)的概念。RPC允许客户端通过网络调用远程服务器上的函数或方法,从而实现分布式系统的功能调用。相比之下,REST(Representational State Transfer)则基于资源的交互模型,通过HTTP协议进行数据传输和操作。本文将详细分析两种架构风格的特点、适用场景及其优缺点,帮助开发者根据具体需求做出合适的选择。 ... [详细]
  • 在 CentOS 6.5 系统上部署 VNC 服务器的详细步骤与配置指南
    在 CentOS 6.5 系统上部署 VNC 服务器时,首先需要确认 VNC 服务是否已安装。通常情况下,VNC 服务默认未安装。可以通过运行特定的查询命令来检查其安装状态。如果查询结果为空,则表明 VNC 服务尚未安装,需进行手动安装。此外,建议在安装前确保系统的软件包管理器已更新至最新版本,以避免兼容性问题。 ... [详细]
  • 如何高效利用Hackbar插件提升网页调试效率
    通过合理利用Hackbar插件,可以显著提升网页调试的效率。本文介绍了如何获取并使用未包含收费功能的2.1.3版本,以确保在不升级到最新2.2.2版本的情况下,依然能够高效进行网页调试。此外,文章还提供了详细的使用技巧和常见问题解决方案,帮助开发者更好地掌握这一工具。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • CSS3 @font-face 字体应用技术解析与实践
    在Web前端开发中,HTML教程和CSS3的结合使得网页设计更加多样化。长期以来,Web设计师受限于“web-safe”字体的选择。然而,CSS3中的`@font-face`规则允许从服务器端加载自定义字体,极大地丰富了网页的视觉效果。通过这一技术,设计师可以自由选择和使用各种字体,提升用户体验和页面美观度。本文将深入解析`@font-face`的实现原理,并提供实际应用案例,帮助开发者更好地掌握这一强大工具。 ... [详细]
  • 本文介绍了 Vue 开发的入门指南,重点讲解了开发环境的配置与项目的基本搭建。推荐使用 WebStorm 作为 IDE,其下载地址为 。安装时请选择适合您操作系统的版本,并通过 获取激活码。WebStorm 是前端开发者的理想选择,提供了丰富的功能和强大的代码编辑能力。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 每日前端实战:148# 视频教程展示纯 CSS 实现按钮两侧滑入装饰元素的悬停效果
    通过点击页面右侧的“预览”按钮,您可以直接在当前页面查看效果,或点击链接进入全屏预览模式。该视频教程展示了如何使用纯 CSS 实现按钮两侧滑入装饰元素的悬停效果。视频内容具有互动性,观众可以实时调整代码并观察变化。访问以下链接体验完整效果:https://codepen.io/comehope/pen/yRyOZr。 ... [详细]
  • 如何高效地安装并配置 PostgreSQL 数据库系统?本文将详细介绍从下载到安装、配置环境变量、初始化数据库、以及优化性能的全过程,帮助读者快速掌握 PostgreSQL 的核心操作与最佳实践。文章还涵盖了常见问题的解决方案,确保用户在部署过程中能够顺利解决遇到的各种挑战。 ... [详细]
  • 本文介绍了如何在 Windows 系统上利用 Docker 构建一个包含 NGINX、PHP、MySQL、Redis 和 Elasticsearch 的集成开发环境。通过详细的步骤说明,帮助开发者快速搭建和配置这一复杂的技术栈,提升开发效率和环境一致性。 ... [详细]
author-avatar
圆季
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有