• 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、加入网络请求权限
         
    
    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成功断开");
            }
        };
    }
    
  • 相关阅读:
    相控阵天线(十二):天线校准技术仿真介绍之旋转矢量法
    JS基础
    Linux CentOS7设置时区
    进行股票量化交易接口程序化开发要注意的事项
    Vue3.x使用vuex进行页面间通信
    Spring Security JWT 添加额外信息
    【深入理解Kotlin协程】协程的创建、启动、挂起函数【理论篇】
    PyCharm配置及使用Git教程
    Windows网络监视工具
    vue使用CSS 变量
  • 原文地址:https://blog.csdn.net/Billy_Zuo/article/details/139633072