marclee44 / me

1 stars 0 forks source link

WebSocket客户端初体验——okhttp3 #23

Open marclee44 opened 2 years ago

marclee44 commented 2 years ago

之前写过一篇MQTT客户端初体验——eclipse/paho.mqtt.android,这次正好需要,类似的写一篇WebSocket的。

简介

HTTP协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理,HTTP协议无法实现服务器主动向客户端发起消息。 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和 XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源。 Websocket应运而生,WebSocket连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。 原理图

特点

okhttp3.WebSocket

okhttp应该算是Android中使⽤最⼴泛的⽹络库了,我们通常会利⽤它来实现HTTP请求,实际上它还可以⽀持WebSocket,并且使⽤起来也⾮常的便捷。这里我们尝试写一个带自动重连功能的客户端。

  1. 引用 在应用build.gradle中,仅需添加okhttp的引用。这里我们使用4.9.3版本
    implementation "com.squareup.okhttp3:okhttp:4.9.3"
  2. 声明服务AndroidManifest.xml中,需要声明服务,以便App在后台时也能保持WebSocket连接
        <service
            android:name=".WebSocketService"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="com.space365.receiver.intent.action.ReceiveWebSocket"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
  3. 创建WebSocketListener(事件监听)

    private class WebSocketClient extends WebSocketListener {
        @Override
        public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
            super.onOpen(webSocket, response);
            online = true;
            Logger.i("WebSocket connect succeed!");
        }
    
        @Override
        public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
            online = false;
            //非正常退出code,则重连
            if (code != CLOSE_CODE) {
                Logger.e("WebSocket closed! Reason: %s", reason);
                reConnect();
            }
        }
    
        @Override
        public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) {
            Logger.e(t, "WebSocket closed with error!");
            online = false;
            reConnect();
        }
    
        @Override
        public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
            Logger.i(text);
        }
    }
  4. 创建WebSocket客户端

    public class WebSocketService extends Service {
    private static final int CLOSE_CODE = 1000;
    private static final String CLOSE_REASON = "destroy";
    private String host;
    private WebSocket webSocket;
    private boolean online;
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    
    @Override
    public void onCreate() {
        Logger.i("WebSocketService onCreate");
        super.onCreate();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.i("WebSocketService onStartCommand");
        host = "ws://ip:port/channel/xxxx-changed";
        connect();
        return START_NOT_STICKY;
    }
    
    private void connect() {
        OkHttpClient client = new OkHttpClient.Builder()
                .build();
        Request request = new Request.Builder()
                .url(host)
                .build();
        webSocket = client.newWebSocket(request, new WebSocketClient());
    }
    
    //RxJava,3秒延迟后重新连接
    private void reConnect() {
        disposables.add(
                Single.timer(3, TimeUnit.SECONDS)
                        .subscribe(p -> connect())
        );
    }
    
    private final CompositeDisposable disposables;
    {
        disposables = new CompositeDisposable();
    }
    
    private class MyBinder extends Binder {
        WebSocketService getService() {
            return WebSocketService.this;
        }
    }
    
    private class WebSocketClient extends WebSocketListener { 
        ...
    }
    }
  5. 发送消息
    private void publish(String message) {
        if (online) {
            if (webSocket.send(message)) {
                Logger.i("WebSocket publish succeed!");
            } else {
                Logger.e("WebSocket publish failed!");
            }
        } else {
            Logger.e("WebSocket is not connected, publish failed!");
        }
    }
  6. 退出时断开连接

    private static final String CLOSE_REASON = "destroy";
    
    @Override
    public void onDestroy() {
        Logger.i("WebSocketService onDestroy");
        if (online) {
            webSocket.close(CLOSE_CODE, CLOSE_REASON);
        }
        webSocket.cancel();
        disposables.dispose();
        super.onDestroy();
    }