Android WebSocket长连接的实现

news2024/11/17 17:40:50

一、为什么需要 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 {

    @Override
    protected void onSetSSLParameters(SSLParameters sslParameters) {
//        super.onSetSSLParameters(sslParameters);
    }

    public JWebSocketClient(URI serverUri) {
        super(serverUri, new Draft_6455());
    }

    @Override
    public void onOpen(ServerHandshake handShakeData) {//在webSocket连接开启时调用
    }

    @Override
    public void onMessage(String message) {//接收到消息时调用
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {//在连接断开时调用
    }

    @Override
    public 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) {
    @Override
    public 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 {

    @Override
    protected void onSetSSLParameters(SSLParameters sslParameters) {
//        super.onSetSSLParameters(sslParameters);
    }

    public JWebSocketClient(URI serverUri) {
        super(serverUri, new Draft_6455());
    }

    @Override
    public void onOpen(ServerHandshake handShakeData) {//在webSocket连接开启时调用
    }

    @Override
    public void onMessage(String message) {//接收到消息时调用
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {//在连接断开时调用
    }

    @Override
    public 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 {

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(GRAY_SERVICE_ID, new Notification());
            stopForeground(true);
            stopSelf();
            return super.onStartCommand(intent, flags, startId);
        }

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        LogUtil.i(TAG, "WebSocketService onBind");
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
            //初始化WebSocket
            initSocketClient();
            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) {
            @Override
            public void onMessage(String message) {
                //message就是接收到的消息
                LogUtil.i(TAG, "WebSocketService收到的消息:" + message);

                EventBus.getDefault().post(new WebSocketEvent(message));
        }

            @Override
            public void onOpen(ServerHandshake handShakeData) {//在webSocket连接开启时调用
                LogUtil.i(TAG, "WebSocket 连接成功");
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {//在连接断开时调用
                LogUtil.e(TAG, "onClose() 连接断开_reason:" + reason);

                mHandler.removeCallbacks(heartBeatRunnable);
                mHandler.postDelayed(heartBeatRunnable, CLOSE_RECON_TIME);//开启心跳检测
            }

            @Override
            public 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() {
            @Override
            public 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();
            }
        }
    }

    @Override
    public boolean onUnbind(Intent intent) {
        LogUtil.e(TAG, "Service onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public 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() {
        @Override
        public 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() {
            @Override
            public 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";//设备token
    public 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() {
        @Override
        public 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() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //服务与活动成功绑定
            mWebSocketService = ((WebSocketService.JWebSocketClientBinder) iBinder).getService();
            LogUtil.e(TAG, "WebSocket服务与Application成功绑定");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            //服务与活动断开
            mWebSocketService = null;
            LogUtil.e(TAG, "WebSocket服务与Application成功断开");
        }
    };
}

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

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

相关文章

程序员职业素养

程序员应该具备的职业素养 一、专业精神1.1、专业精神在程序员职业生涯中的重要性1.2、追求技术的过程1.3、专业精神对团队和项目的影响1.4、专业精神在个人职业发展中的意义 二、沟通能力2.1 沟通能力在程序员职业生涯中的重要性2.2 沟通能力的要素2.2.1. 有效的口头和书面表达…

UPS负载箱的使用注意事项有哪些?

UPS负载箱是用于模拟电网中各种负载的设备&#xff0c;广泛应用于电力系统、通信系统、数据中心等领域。为了保证UPS负载箱的正常运行和使用安全&#xff0c;在使用过程中需要注意以下几点&#xff1a; 1. 选择合适的负载箱&#xff1a;根据实际需求选择合适的负载箱&#xff0…

DevExpress WPF中文教程:Grid - 如何完成列和编辑器配置(设计时)?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

数据密集型企业是如何选择替代FTP传输文件的系统的?

数据密集型企业是指其发展和运行高度依赖于数据、算法和算力的闭环优化体系的企业。这类企业拥有规模化知识创造者、更广泛的智能工具以及更丰裕的数据要素资源。 毋庸置疑&#xff0c;数据对于数据密集型企业来说是最关键、最核心的资产&#xff0c;但数据密集型企业同样也面临…

航天科技集团与SPACEX公司的思考与分析

近期&#xff0c;中国航天科技集团正式发文与SPACEX对标的认识结果&#xff0c;包括发展理念上、科研生产模式上、关键核心技术上、质量效率效益上存在明显差距与不足。真诚的态度&#xff0c;赢得了社会上的广泛关注和积极评价。真心为老东家能够保持这份清醒而高兴。 从对标管…

WPF学习(1)--类与类的继承

在面向对象编程中&#xff0c;继承是一种机制&#xff0c;允许一个类&#xff08;称为子类或派生类&#xff09;从另一个类&#xff08;称为父类或基类&#xff09;继承属性和方法。继承使我们能够创建一个通用类&#xff0c;然后根据需要扩展或修改它以创建更具体的类。以下是…

关于pip的15个使用小技巧

认识pip 众所周知&#xff0c;pip可以对python的第三方库进行安装、更新、卸载等操作&#xff0c;十分方便。 pip的全称&#xff1a;package installer for python&#xff0c;也就是Python包管理工具。 可能有些人用了很久pip&#xff0c;但还不清楚包管理工具是个啥。 我…

气膜建筑一平多少钱—轻空间

气膜建筑是一种以膜材为主要结构材料&#xff0c;通过空气压力支撑的建筑形式。它广泛应用于体育场馆、展览馆、仓库及临时活动场所等多种领域&#xff0c;具有快速搭建、灵活使用、节能环保等诸多优势。 气膜建筑的成本构成 气膜建筑的成本主要由以下几个部分构成&#xff1a;…

爬虫相关面试题

一&#xff0c;如何抓取一个网站&#xff1f; 1&#xff0c;去百度和谷歌搜一下这个网站有没有分享要爬取数据的API 2, 看看电脑网页有没有所需要的数据&#xff0c;写代码测试调查好不好拿&#xff0c;如果好拿直接开始爬取 3&#xff0c;看看有没有电脑能打开的手机网页&a…

新加坡裸机云多IP服务器为何适合跨境外贸业务

新加坡裸机云多IP服务器在跨境外贸业务中展现出了卓越的适配性&#xff0c;其独特优势为外贸企业提供了强大的支持。以下将详细阐述为何新加坡裸机云多IP服务器是跨境外贸业务的理想选择。 首先&#xff0c;新加坡裸机云多IP服务器在性能上表现出色。由于去除了虚拟化层的开销&…

2024端午节活动回顾,传承经典文化,共铸企业精神之魂!

端午佳节来临&#xff0c;为了弘扬中华民族优秀的传统文化&#xff0c;国际数字影像产业园开展了“端午粽动员&#xff0c;尽情放粽”端午节主题活动&#xff0c;把节日的祝福送到了全体职工的身边。 此次端午节活动时间为上午9点半至下午1点&#xff0c;活动地点设置在国际数…

19.1 HTTP客户端-HTTP协议、GET请求、POST请求

1. HTTP协议 TCP/IP协议栈由上至下分为以下四层&#xff1a; 应用层&#xff1a;为用户提供应用服务时的通信活动 DNS、FTP&#xff08;文件传输&#xff09;、HTTP&#xff08;超文本传输&#xff09;传输层&#xff1a;网络中两台计算机之间的数据传输 TCP、UDP、SPX网络层…

百问网全志V853开发板烧录开发板系统教程

烧录开发板系统 注意&#xff1a;此方式烧录进的文件系统是ubifs文件系统&#xff0c;如果操作 需要网络文件系统挂载或者使用TF卡&#xff0c;不推荐使用。 准备工作 1.100ASK-V853-Pro开发板 x1 2. 下载全志线刷工具AllwinnertechPhoeniSuit 3. TypeC线 X2、12V电源线X1 4…

怎么恢复电脑删除的文件?4个策略恢复数据!

“不知道大家有什么方法可以恢复电脑上删除的文件吗&#xff1f;一不小心删除了部分文件&#xff0c;希望能得到大家的帮助。” 我们的电脑里会存储着很多重要的数据&#xff0c;包括各种重要的文件、照片、资料等。在使用电脑时&#xff0c;有时候可能会由于各种误操作导致它们…

PostgreSQL 多表连接不同维度聚合统计查询

摘要:在本文中,你将学习到如何使用 PostgreSQL 完全外连接,从两个或多个表中聚合维度统计数据。 文章目录 一、常用的连接类型图示二、数据库表设计示例三、连接查询示例1. inner join 内连接(不能满足维度统计需求)2. full join 完全外连接(满足维度统计需求)一、常用的…

环保空调的制冷量和耗电量是多少呢

环保空调的制冷量和耗电量因具体型号、功率以及使用情况而异。以下是一些关于环保空调制冷量和耗电量的详细解释和归纳&#xff1a; 制冷量 原理&#xff1a;环保空调主要利用水蒸发吸热的物理原理进行降温&#xff0c;这种降温方式能够带来显著的冷却效果。效果&#xff1a;…

【NLP】给Transformer降降秩,通过分层选择性降阶提高语言模型的推理能力

【NLP】给Transformer降降秩&#xff0c;通过分层选择性降阶提高语言模型的推理能力 文章目录 【自然语言处理-论文翻译与学习】序1、导论2、相关工作3、相关工具4、方案5、实验5.1 使用 GPT-J 对 CounterFact 数据集进行彻底分析5.1.1 数据集中的哪些事实是通过降阶恢复的&…

C++ 引用 - 引用的特点|在优化程序上的作用

引用是C 的一个别名机制&#xff0c;所谓别名&#xff0c;就是同一块内存共用多个名字&#xff0c;每个名字都指的是这片空间&#xff0c;通过这些别名都能访问到同样的一块空间。 就像鲁迅和周树人是同一个人。 ——鲁迅 一、引用的基本用法 int a 10; int& ref a; // …

磁盘管理 磁盘介绍 MBR

track&#xff1a;磁道&#xff0c;就是磁盘上同心圆&#xff0c;从外向里&#xff0c;依次1号、2号磁道..... sector&#xff1a;扇区&#xff0c;将磁盘分成一个一个扇形区域&#xff0c;每个扇区大小是512字节&#xff0c;从外向里&#xff0c;依次是1号扇区、2号扇区... cy…

LLVM Cpu0 新后端 系列课程总结

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…