Android WebSocket长连接的实现

一、为什么需要 WebSocket

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。

举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用[“轮询”]:每隔一段时候,就发出一个询问,了解服务器有没有新的信息,最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,开发工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

二、 WebSocket的简介

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于[“服务器推送技术”]的一种。
bg2017051502.png
WebSocket的特点包括:

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。

  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

  3. 支持双向通信,实时性更强

  4. 数据格式比较轻量,性能开销小,通信高效。

  5. 可以发送文本,也可以发送二进制数据。

  6. 没有同源限制,客户端可以与任意服务器通信。

  7. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

ws://echo.websocket.org

bg2017051503.jpg

三、WebSocket 实现Android客户端与服务器的长连接

Android客户端选择已经成熟的框架,Java-WebSocket,GitHub地址:https://github.com/TooTallNate/Java-WebSocket

(一)引入Java-WebSocket

1、build.gradle中加入
  implementation "org.java-websocket:Java-WebSocket:1.5.1"
2、加入网络请求权限
     <uses-permission android:name="android.permission.INTERNET" />
3、新建客户端类

新建一个客户端类并继承WebSocketClient,需要实现它的四个抽象方法、构造函数和onSetSSLParameters方法

     public class JWebSocketClient extends WebSocketClient {@Overrideprotected void onSetSSLParameters(SSLParameters sslParameters) {
//        super.onSetSSLParameters(sslParameters);}public JWebSocketClient(URI serverUri) {super(serverUri, new Draft_6455());}@Overridepublic void onOpen(ServerHandshake handShakeData) {//在webSocket连接开启时调用}@Overridepublic void onMessage(String message) {//接收到消息时调用}@Overridepublic void onClose(int code, String reason, boolean remote) {//在连接断开时调用}@Overridepublic void onError(Exception ex) {//在连接出错时调用}
}

其中onOpen()方法在websocket连接开启时调用,onMessage()方法在接收到消息时调用,onClose()方法在连接断开时调用,onError()方法在连接出错时调用。构造方法中的new Draft_6455()代表使用的协议版本,这里可以不写或者写成这样即可。

4、建立websocket连接

建立连接只需要初始化此客户端再调用连接方法,需要注意的是WebSocketClient对象是不能重复使用的,所以不能重复初始化,其他地方只能调用当前这个Client。

URI uri = URI.create("ws://*******");
JWebSocketClient client = new JWebSocketClient(uri) {@Overridepublic void onMessage(String message) {//message就是接收到的消息Log.e("JWebSClientService", message);}
};

为了方便对接收到的消息进行处理,可以在这重写onMessage()方法。初始化客户端时需要传入websocket地址(测试地址:ws://echo.websocket.org),websocket协议地址大致是这样的,协议标识符是ws(如果加密,则为wss)

ws:// ip地址 : 端口号

连接时可以使用connect()方法或connectBlocking()方法,建议使用connectBlocking()方法,connectBlocking多出一个等待操作,会先连接再发送。

try {client.connectBlocking();
} catch (InterruptedException e) {e.printStackTrace();
}
5、发送消息

发送消息只需要调用send()方法,如下

if (client != null && client.isOpen()) {client.send("你好");
}
6、关闭socket连接

关闭连接调用close()方法,最后为了避免重复实例化WebSocketClient对象,关闭时一定要将对象置空。

/*** 断开连接*/
private void closeConnect() {try {if (null != client) {client.close();}} catch (Exception e) {e.printStackTrace();} finally {client = null;}
}

(二)WebSocket的封装

1、新建一个客户端类并继承WebSocketClient
public class JWebSocketClient extends WebSocketClient {@Overrideprotected void onSetSSLParameters(SSLParameters sslParameters) {
//        super.onSetSSLParameters(sslParameters);}public JWebSocketClient(URI serverUri) {super(serverUri, new Draft_6455());}@Overridepublic void onOpen(ServerHandshake handShakeData) {//在webSocket连接开启时调用}@Overridepublic void onMessage(String message) {//接收到消息时调用}@Overridepublic void onClose(int code, String reason, boolean remote) {//在连接断开时调用}@Overridepublic void onError(Exception ex) {//在连接出错时调用}
}
2、新建WebSocketEvent,用于传递消息事件
public class WebSocketEvent {private String message;public WebSocketEvent(String message) {this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
3、新建WebSocketService服务,用于消息管理和保持长连接状态
public class WebSocketService extends Service {private final static String TAG = WebSocketService.class.getSimpleName();public JWebSocketClient client;private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();private final static int GRAY_SERVICE_ID = 1001;private static final long CLOSE_RECON_TIME = 100;//连接断开或者连接错误立即重连//用于Activity和service通讯public class JWebSocketClientBinder extends Binder {public WebSocketService getService() {return WebSocketService.this;}}//灰色保活public static class GrayInnerService extends Service {@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {startForeground(GRAY_SERVICE_ID, new Notification());stopForeground(true);stopSelf();return super.onStartCommand(intent, flags, startId);}@Overridepublic IBinder onBind(Intent intent) {return null;}}@Overridepublic IBinder onBind(Intent intent) {LogUtil.i(TAG, "WebSocketService onBind");return mBinder;}@Overridepublic void onCreate() {super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {//初始化WebSocketinitSocketClient();mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测//设置service为前台服务,提高优先级if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {//Android4.3以下 ,隐藏Notification上的图标startForeground(GRAY_SERVICE_ID, new Notification());} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {//Android4.3 - Android8.0,隐藏Notification上的图标Intent innerIntent = new Intent(this, GrayInnerService.class);startService(innerIntent);startForeground(GRAY_SERVICE_ID, new Notification());} else {//Android8.0以上app启动后通知栏会出现一条"正在运行"的通知NotificationChannel channel = new NotificationChannel(NotificationUtil.channel_id, NotificationUtil.channel_name,NotificationManager.IMPORTANCE_HIGH);NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);if (manager != null) {manager.createNotificationChannel(channel);Notification notification = new Notification.Builder(getApplicationContext(), NotificationUtil.channel_id_tai_bang).build();startForeground(GRAY_SERVICE_ID, notification);}}return START_STICKY;}private void initSocketClient() {String url = BuildConfig.WS_PERFIX;URI uri = URI.create(url);client = new JWebSocketClient(uri) {@Overridepublic void onMessage(String message) {//message就是接收到的消息LogUtil.i(TAG, "WebSocketService收到的消息:" + message);EventBus.getDefault().post(new WebSocketEvent(message));}@Overridepublic void onOpen(ServerHandshake handShakeData) {//在webSocket连接开启时调用LogUtil.i(TAG, "WebSocket 连接成功");}@Overridepublic void onClose(int code, String reason, boolean remote) {//在连接断开时调用LogUtil.e(TAG, "onClose() 连接断开_reason:" + reason);mHandler.removeCallbacks(heartBeatRunnable);mHandler.postDelayed(heartBeatRunnable, CLOSE_RECON_TIME);//开启心跳检测}@Overridepublic void onError(Exception ex) {//在连接出错时调用LogUtil.e(TAG, "onError() 连接出错:" + ex.getMessage());mHandler.removeCallbacks(heartBeatRunnable);mHandler.postDelayed(heartBeatRunnable, CLOSE_RECON_TIME);//开启心跳检测}};connect();}/*** 连接WebSocket*/private void connect() {new Thread() {@Overridepublic void run() {try {//connectBlocking多出一个等待操作,会先连接再发送,否则未连接发送会报错client.connectBlocking();} catch (InterruptedException e) {e.printStackTrace();}}}.start();}/*** 发送消息*/public void sendMsg(String msg) {if (null != client) {LogUtil.i(TAG, "发送的消息:" + msg);try {client.send(msg);} catch (Exception e) {e.printStackTrace();}}}@Overridepublic boolean onUnbind(Intent intent) {LogUtil.e(TAG, "Service onUnbind");return super.onUnbind(intent);}@Overridepublic void onDestroy() {closeConnect();super.onDestroy();}/*** 断开连接*/public void closeConnect() {mHandler.removeCallbacks(heartBeatRunnable);try {if (null != client) {client.close();}} catch (Exception e) {e.printStackTrace();} finally {client = null;}}//    -------------------------------------WebSocket心跳检测------------------------------------------------private static final long HEART_BEAT_RATE = 10 * 1000;//每隔10秒进行一次对长连接的心跳检测private Handler mHandler = new Handler();private Runnable heartBeatRunnable = new Runnable() {@Overridepublic void run() {if (client != null) {if (client.isClosed()) {reconnectWs();LogUtil.e(TAG, "心跳包检测WebSocket连接状态:已关闭");} else if (client.isOpen()) {LogUtil.d(TAG, "心跳包检测WebSocket连接状态:已连接");} else {LogUtil.e(TAG, "心跳包检测WebSocket连接状态:已断开");}} else {//如果client已为空,重新初始化连接initSocketClient();LogUtil.e(TAG, "心跳包检测WebSocket连接状态:client已为空,重新初始化连接");}//每隔一定的时间,对长连接进行一次心跳检测mHandler.postDelayed(this, HEART_BEAT_RATE);}};/*** 开启重连*/private void reconnectWs() {mHandler.removeCallbacks(heartBeatRunnable);new Thread() {@Overridepublic void run() {try {LogUtil.e(TAG, "开启重连");client.reconnectBlocking();} catch (InterruptedException e) {e.printStackTrace();}}}.start();}
}
4、在Application中开启WebSocketService服务
    /*** 开启并绑定WebSocket服务*/public void startWebSocketService() {Intent bindIntent = new Intent(this, WebSocketService.class);startService(bindIntent);bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);}
5、通过WebSocketService向服务器发送消息
     if (NsApplication.getInstance().getWebSocketService() != null &&NsApplication.getInstance().getWebSocketService().client != null && NsApplication.getInstance().getWebSocketService().client.isOpen()) {JSONObject jsonObject = new JSONObject();NsApplication.getInstance().getWebSocketService().sendMsg(jsonObject );} else {LogUtil.e(TAG, "WebSocket连接已断开");}}
6、通过WebSocketService接收服务器发来的消息
    @Subscribe(threadMode = ThreadMode.MAIN)public void onMessageEvent(WebSocketEvent event) {if (event != null) {LogUtil.e(TAG, "接收消息内容:" + event.getMessage());}}
7、Application
public class NsApplication extends Application {private final static String TAG = NsApplication.class.getSimpleName();private static NsApplication instance;private static final String DEVICE_TOKEN = "device_token";//设备tokenpublic WebSocketService mWebSocketService;//    -------------------------------------WebSocket发送空消息心跳检测------------------------------------------------private static final long HEART_BEAT_RATE = 60 * 1000;//每隔1分钟发送空消息保持WebSocket长连接public static NsApplication getInstance() {if (instance == null) {instance = new NsApplication();}return instance;}private Handler mHandler = new Handler();private Runnable webSocketRunnable = new Runnable() {@Overridepublic void run() {if (mWebSocketService != null &&mWebSocketService.client != null && mWebSocketService.client.isOpen()) {try {JSONObject jsonObject = new JSONObject();jsonObject.put("from","");jsonObject.put("to", "");LogUtil.e(TAG, "JSONObject:" + jsonObject.toString());mWebSocketService.sendMsg(jsonObject.toString());} catch (JSONException e) {e.printStackTrace();}}//每隔一定的时间,对长连接进行一次心跳检测mHandler.postDelayed(this, HEART_BEAT_RATE);}};public WebSocketService getWebSocketService() {return mWebSocketService;}/*** 开启并绑定WebSocket服务*/public void startWebSocketService(String deviceToken) {Intent bindIntent = new Intent(this, WebSocketService.class);bindIntent.putExtra(DEVICE_TOKEN, deviceToken);startService(bindIntent);bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);mHandler.removeCallbacks(webSocketRunnable);mHandler.postDelayed(webSocketRunnable, HEART_BEAT_RATE);//开启心跳检测}private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {//服务与活动成功绑定mWebSocketService = ((WebSocketService.JWebSocketClientBinder) iBinder).getService();LogUtil.e(TAG, "WebSocket服务与Application成功绑定");}@Overridepublic void onServiceDisconnected(ComponentName componentName) {//服务与活动断开mWebSocketService = null;LogUtil.e(TAG, "WebSocket服务与Application成功断开");}};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/30715.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot引入外部依赖包

将需要引入的文件放置到与src同级别的目录下 如上&#xff0c;在src的同级&#xff0c;新建了一个lib目录&#xff0c;将jar包放置其中 在POM文件下&#xff0c;加入如下配置 <dependency><groupId>com.aliyun</groupId><artifactId>com.aliyun.filed…

搭建取图系统app源码开发,满足广泛应用需求

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 图片已成为信息传递的重要媒介&#xff0c;广泛应用于各个领域。为满足日益增长的图片需求&#xff0c;搭建一款高效的取图系统&#xff0c;可以为用户提供便捷、全面的…

windows服务器下jenkins c语言打包的一些经验share

前言 因为一些原因&#xff0c;需要从linux环境下的jenkins 打包c语言转移到使用windows环境下的jenkins打包c语言&#xff0c;从转移的过程中&#xff0c;发现了一些问题和解决方案&#xff0c;故在此和各位运维工程师分享一下。 一、windows 下的c语言编译环境配置 这边就…

中国最全的hive sql 函数集合(持续更新)

#6/20/24 增加greatest函数&#xff1a; select greatest(1,2,3,4,5,2) 结论&#xff1a;可以用hive presto spark得出正确的结果值 #6/20/24 增加last_value(cl1) ignore nulls over(order by ts ) as dt 函数&#xff1a; 有数据集&#xff1a; 1 1 1 2 2   3 3 …

模拟算法:代码世界的生活模拟器

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一. 模拟算法的总结 二. 模拟算法题目 2.1 替换所有的问号 2.2 提莫攻击 2.3 Z字形变换 2.4 外观数列 2.5 数青蛙 总结 前言 本篇详细介绍了模拟算法的使用&#xff0c;让…

自动化办公04 使用pyecharts制图

目录 一、柱状图 二、折线图 三、饼图 四、地图 1. 中国地图 2. 世界地图 3. 省会地图 五、词云 Pyecharts是一个用于数据可视化的Python库。它基于Echarts库&#xff0c;可以通过Python代码生成各种类型的图表&#xff0c;如折线图、柱状图、饼图、散点图等。 Pyecha…

【腾讯云智笔试题——分苹果时间复杂度和空间复杂度都是O(1)】

文章目录 题目描述解题思路&#xff1a;思路讲解&#xff1a; 题目描述 有m个苹果&#xff0c;n个小孩。每个小孩都有一个编号&#xff0c;小明的编号是。要尽量公平的分苹果&#xff0c;相邻编号的小孩分到的苹果数目差距不能大于1。 请问如何在满足相邻编号的小孩分到的苹果…

wsl2平台鸿蒙全仓docker编译环境快速创建方法

文章目录 1 文章适用范围&#xff1a;2 WSL环境安装3 镜像迁移非C盘4 Docker环境准备4.1 docker用户组和用户创建4.2 Docker环境配置4.2.1 Ubuntu下安装docker工具4.2.2 鸿蒙Docker环境安装4.2.3 鸿蒙全仓代码拉取编译 5 鸿蒙全仓代码的更新策略6 参考文献7 FAQ7.1 缺头文件xcr…

【0基础学爬虫】爬虫基础之自动化工具 Appium 的使用

大数据时代&#xff0c;各行各业对数据采集的需求日益增多&#xff0c;网络爬虫的运用也更为广泛&#xff0c;越来越多的人开始学习网络爬虫这项技术&#xff0c;K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章&#xff0c;为实现从易到难全方位覆盖&#xff0c;特设【0基础学…

CentOS 5(CentOS 6、Redhat 6)服务器配置VNC

一、配置服务器yum源 yum源&#xff08;本地、华为云、阿里云、网易&#xff09; 二、使用yum安装vnc服务 1、检查系统是否安装了vnc 和 vncserver&#xff0c; rpm -qa | grep vnc如果没有安装那就行自行下载安装&#xff08;我这里用yum安装了&#xff0c;vncserver安装需…

设计程序,实现高精度圆周率的计算和存储,使用线性表突破程序设计语言内置变量的数值和有效数字范围限制

一、使用线性表突破程序设计语言内置变量的数值和有效数字范围的限制&#xff0c;为了实现高精度圆周率的计算&#xff0c;先根据数学公式进行对PI高精度运算&#xff0c;如图1-1。根据这个数学公式 π2 0nn!2n1‼ 即 Rn1Rn*n2n1&#xff0c;R11&#xff0c;sum π2* n1∞Rn 来…

Redis学习|Redis基础知识、Redis五大数据类型、Redis三种特殊数据类型、Redis事务

Redis基础知识 redis默认有16个数据库&#xff0c;并且这个数量可以在conf配置文件中更改 默认使用的是第0个 可以使用 select 进行切换数据库! key *查看数据库所有的key 清除当前数据库 flushdb 清除全部数据库的内容FLUSHALL 为什么redis是6379!(了解一下即可!) Redis 是…

【算法】Graham 凸包扫描算法 ( 凸包概念 | 常用的凸包算法 | 角排序 | 叉积 | Python 代码示例 )

文章目录 一、Graham 凸包扫描算法1、凸包概念2、常用的凸包算法3、Graham 凸包扫描算法 二、Graham 算法前置知识点1、角排序2、叉积3、算法过程分析 三、代码示例1、完整代码示例2、执行结果 使用 Graham 算法绘制的凸包效果 : 博客代码下载 : https://download.csdn.net/d…

大模型之-Seq2Seq介绍

大模型之-Seq2Seq介绍 1. Seq2Seq 模型概述 Seq2Seq&#xff08;Sequence to Sequence&#xff09;模型是一种用于处理序列数据的深度学习模型&#xff0c;常用于机器翻译、文本摘要和对话系统等任务。它的核心思想是将一个输入序列转换成一个输出序列。 Seq2Seq模型由两个主…

企业微信,机器人定时提醒

场景&#xff1a; 每天定时发送文字&#xff0c;提醒群成员事情&#xff0c;可以用机器人代替 人工提醒。 1&#xff09;在企业微信&#xff0c;创建机器人 2&#xff09;在腾讯轻联&#xff0c;创建流程&#xff0c;选择定时任务&#xff0c;执行操作&#xff08;企业微信机…

未登录也能知道你是谁?浏览器指纹了解一下!

引言 大多数人都遇到过这种场景&#xff0c;我在某个网站上浏览过的信息&#xff0c;但我并未登录&#xff0c;可是到了另一个网站发现被推送了类似的广告&#xff0c;这是为什么呢&#xff1f; 本文将介绍一种浏览器指纹的概念&#xff0c;以及如何利用它来判断浏览者身份。…

FIddler+Proxifer 实现PC客户端抓包详细教程

文章目录 前言1、Proxifer下载和配置1.1、下载Proxifer1.2、Proxifier配置 2、FIddler下载和配置2.1、FIddler下载2.2、FIddler配置 3、三、为什么抓不到有些应用程序的HTTP(s)的包&#xff1f; 前言 一般在浏览器场景下&#xff0c;我们可以利用Fiddler很好的完成抓包&#x…

计算机网络学习3

文章目录 以太网的MAC帧格式虚拟局域网VLAN概述虚拟局域网VLAN的实现机制以太网的发展802.11无线局域网的组成无线局域网的物理层无线局域网的数据链路层---使用CSMA/CD协议802.11无线局域网的MAC帧 网络层网络层概述网际协议IP和4.2.1异构网络互联IPv4地址及其编址方法概述IPv…

webpack处理样式资源04--webpack入门学习

处理样式资源 本章节学习使用 Webpack 如何处理 Css、Less、Sass、Scss、Styl 样式资源 介绍 Webpack 本身是不能识别样式资源的&#xff0c;所以我们需要借助 Loader 来帮助 Webpack 解析样式资源 我们找 Loader 都应该去官方文档中找到对应的 Loader&#xff0c;然后使用…

osgearth提示“simple.earth: file not handled”

在用vcpkg编译完osg和osgearth后&#xff0c;为了验证osgearth编译是否正确&#xff0c;进行测试&#xff0c;模型加载代码如下&#xff1a; root->addChild(osgDB::readNodeFile("simple.earth")); 此时以为是simple.earth路径的问题&#xff0c;遂改为以下代码…