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

前言

消息推送功能可以说移动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<String, List<String>> 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<String, CallbackWrapper> 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实现消息通信内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • PHP实现websocket通信的方法示例

    本文实例讲述了PHP实现websocket通信的方法.分享给大家供大家参考,具体如下: 执行方法: 首先先修改server.php与index.html的ip 通过命令行执行 [php路径]\php.exe "[文件路径]\server.php" 然后通过浏览器打开index.html server.php <?php include 'websocket.class.php'; $config=array( 'address'=>'192.168.0.200', 'por

  • WebSocket的通信过程与实现方法详解

    什么是 WebSocket ? WebSocket 是一种标准协议,用于在客户端和服务端之间进行双向数据传输.但它跟 HTTP 没什么关系,它是基于 TCP 的一种独立实现. 以前客户端想知道服务端的处理进度,要不停地使用 Ajax 进行轮询,让浏览器隔个几秒就向服务器发一次请求,这对服务器压力较大.另外一种轮询就是采用 long poll 的方式,这就跟打电话差不多,没收到消息就一直不挂电话,也就是说,客户端发起连接后,如果没消息,就一直不返回 Response 给客户端,连接阶段一直是阻塞的

  • 使用 Spring Boot 实现 WebSocket实时通信

    在开发 Web 应用程序时,我们有时需要将服务端事件推送到连接的客户端.但 HTTP 并不能做到.客户端打开与服务端的连接并请求数据,但服务端不能打开与客户端的连接并推送数据. 为了解决这个限制,我们可以建立了一个轮询模式,网页会间隔地轮询服务器以获取新事件.但这种模式不太理想,因为它增加了 HTTP 开销,速度也只能达到与轮询的速率一样快,并且给服务器增加了不必要的负载. 幸运的是,HTML5 WebSocket 出现了.WebSocket 协议允许浏览器与 Web 服务器之间进行低开销的交互

  • WebSocket+node.js创建即时通信的Web聊天服务器

    本文实例node.js创建即时通信的Web聊天服务器,供大家参考,具体内容如下 1.使用nodejs-websocket  nodejs-websocket是基于node.js编写的一个后端实现websocket协议的库,  连接:https://github.com/sitegui/nodejs-websocket.  (1)安装  在项目目录下通过npm安装:npm install nodejs-websocket  (2)创建服务器 //引入nodejs-websocket var ws

  • Spring Boot 开发私有即时通信系统(WebSocket)

    1/ 概述 利用Spring Boot作为基础框架,Spring Security作为安全框架,WebSocket作为通信框架,实现点对点聊天和群聊天. 2/ 所需依赖 Spring Boot 版本 1.5.3,使用MongoDB存储数据(非必须),Maven依赖如下: <properties> <java.version>1.8</java.version> <thymeleaf.version>3.0.0.RELEASE</thymeleaf.ve

  • .NET实现WebSocket服务端即时通信实例

    即时通信常用手段 1.第三方平台 谷歌.腾讯 环信等多如牛毛,其中谷歌即时通信是免费的,但免费就是免费的并不好用.其他的一些第三方一般收费的,使用要则限流(1s/限制x条消息)要么则限制用户数. 但稳定性什么都还不错,又能将服务压力甩出 2.System.Net.Sockets.Socket,也能写一套较好的服务器端.在.NET 4.5之前用较多,使用起来麻烦.需要对数据包进行解析等操作(但貌似网上有对超长包的处理方法) 3.System.Net.WebSockets.WebSocket,这个,

  • 使用spring的websocket创建通信服务的示例代码

    基于socket通信,spring也有自己的socket通信服务:websocket,这次就介绍如何在spring项目中使用websocket进行通信交互. 后台:spring boot:前台:angularjs 后台建立服务 首先我们先建立起后台的服务,以实现进行socket连接. 1.引入websocket依赖 建立好一个maven项目之后,我们需要在xml中引入websocket的相关 依赖: <dependencies> <!--webSocket--> <depen

  • Python通过websocket与js客户端通信示例分析

    具体的 websocket 介绍可见 http://zh.wikipedia.org/wiki/WebSocket 这里,介绍如何使用 Python 与前端 js 进行通信. websocket 使用 HTTP 协议完成握手之后,不通过 HTTP 直接进行 websocket 通信. 于是,使用 websocket 大致两个步骤:使用 HTTP 握手,通信. js 处理 websocket 要使用 ws 模块: Python 处理则使用 socket 模块建立 TCP 连接即可,比一般的 soc

  • C# websocket及时通信协议的实现方法示例

    传统"长轮询"实现Web端即时通讯的问题 WebSocket出现之前,Web端为了实现即时通讯,所用的技术都是Ajax轮询(polling).轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客服端的浏览器.这种传统的HTTP request 的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽. 而比较

  • android利用websocket协议与服务器通信

    最近做一个项目,需求中需要服务器主动推送消息到客户端.这样的话一般的http连接就不能使用了.博主问了个朋友,向我推荐websocket协议,特此测试了一下,发现效果很好. android本身没有websocket的库,需要自己下载 ,下载地址. 客户端代码: 界面布局自己写,很简单的两个button package com.example.test; import com.example.test.R; import android.app.Activity; import android.o

随机推荐