基于okhttp和RxJava封装的自动重连的WebSocket 原创 精华

sallylan44
发布于 2021-7-27 10:59
浏览
1收藏

@toc

一. 概述

  1. RxWebSocket是一个基于okhttpRxJava封装的WebSocket客户端,此库的核心特点是 除了手动关闭WebSocket(就是RxJava取消订阅),WebSocket在异常关闭的时候(onFailure,发生异常,如WebSocketException等等),会自动重连,永不断连.其次,对WebSocket做的缓存处理,同一个URL,共享一个WebSocket.
  2. 由于是基于RxJava封装,所以带来了无限可能,可以和RxBinding,Rxlifecycle一起使用,方便对WebSocket的管理.

效果图

基于okhttp和RxJava封装的自动重连的WebSocket-鸿蒙开发者社区

项目已经上传Jcenter,依赖方法:

//本项目
compile 'io.github.dzsf:RxWebSocket:1.0.0'

二. 使用方法

0.初始化,可以也忽略直接使用.

如果你想使用自己的okhttpClient:

OkHttpClient yourClient = new OkHttpClient();
RxWebSocketUtil.getInstance().setClient(yourClient);

是否打印日志:

RxWebSocketUtil.getInstance().setShowLog(BuildConfig.DEBUG);

1.获取一个WebSocket,接收消息,多种方式:

RxWebSocketUtil.getInstance().getWebSocketInfo(url)
        .subscribe(new Action1<WebSocketInfo>() {
            @Override
            public void call(WebSocketInfo webSocketInfo) {
                mWebSocket = webSocketInfo.getWebSocket();
                Log.d("MainActivity", webSocketInfo.getString());
                Log.d("MainActivity", "ByteString:" + webSocketInfo.getByteString());
            }
        });

mWebSocket.send("hello word");

//get StringMsg
RxWebSocketUtil.getInstance().getWebSocketString(url)
        .subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
            }
        });
// get ByteString
RxWebSocketUtil.getInstance().getWebSocketByteString(url)
        .subscribe(new Action1<ByteString>() {
            @Override
            public void call(ByteString byteString) {
            }
        });
//get WebSocket
RxWebSocketUtil.getInstance().getWebSocket(url)
        .subscribe(new Action1<WebSocket>() {
            @Override
            public void call(WebSocket webSocket) {
            }
        });
// 带timeout的WebSocket,当在指定时间内没有收到消息,就重连WebSocket.为了适配小米平板.
RxWebSocketUtil.getInstance().getWebSocketInfo(url,10, TimeUnit.SECONDS)
        .subscribe(new Action1<WebSocketInfo>() {
            @Override
            public void call(WebSocketInfo webSocketInfo) {
            }
        });

2.发送消息:

//用WebSocket的引用直接发
mWebSocket.send("hello word");

//url 对应的WebSocket 必须打开,否则报错
RxWebSocket.send(sendUrl, "hello");
RxWebSocket.send(sendUrl, ByteString.EMPTY);

//异步发送,若WebSocket已经打开,直接发送,若没有打开,打开一个WebSocket发送完数据,直接关闭.
RxWebSocket.asyncSend(sendUrl, "hello");
RxWebSocket.asyncSend(sendUrl, ByteString.EMPTY);

3.关闭WebSocket:

项目是依托RxJava实现的,所以关闭WebSocket的方法也就是在适当的时候注销 Observable,项目里的demo里,写了一个简单的lifecycle,将Observable生命绑定到Activity的onDestroy,自动注销.代码细节请看demo,因为内部实现了同一个URL的WebSocket共享机制,所以当外部所有持有这个URL的Observable都注销后,这个WebSocket连接就会自动关闭.请看原理解析部

//注意取消订阅,有多种方式,比如 rxlifecycle
mSubscription = RxWebSocketUtil.getInstance().getWebSocketInfo(url)
        .subscribe(new Action1<WebSocketInfo>() {
            @Override
            public void call(WebSocketInfo webSocketInfo) {
                mWebSocket = webSocketInfo.getWebSocket();

                if (webSocketInfo.isOnOpen()) {
                    Log.d("MainActivity", " on WebSocket open");
                } else {
                    String string = webSocketInfo.getString();

                    if (string != null) {
                        Log.d("MainActivity", string);
                        textview.setText(Html.fromHtml(string));
                    }

                    ByteString byteString = webSocketInfo.getByteString();

                    if (byteString != null) {
                        Log.d("MainActivity",
                            "webSocketInfo.getByteString():" +
                            byteString);
                    }
                }
            }
        });

//注销
if (mSubscription != null) {
    mSubscription.unsubscribe();
}

//lifecycle注销,详情看demo
RxWebSocketUtil.getInstance().getWebSocketString(url)
            .compose(this.<String>bindOnActivityEvent(ActivityEvent.onDestory))
            .subscribe(new Action1<String>() {
                @Override
                public void call(String s) {
                }
            });

三. 原理解析

1. 首先需要将okhttp的WebSocket包装成Observable,由于需要将WebSocket,Stringmsg,ByteString等信息一同发送给观察者所以先构建一个WebSocketInfo类,将信息封装:

public class WebSocketInfo {
    private WebSocket mWebSocket;
    private String mString;
    private ByteString mByteString;
    private boolean onOpen;
    //其他省略
}

onOpen字段主要用来判断当前的这个WebSocketInfo是否是当WebSocket打开时发送的消息(onOpen),这时,Stringmsg和ByteString都是null.

2. 将WebSocketInfo包装成Observable发出:

private final class WebSocketOnSubscribe implements Observable.OnSubscribe<WebSocketInfo> {
    private String url;
    private WebSocket webSocket;
    private WebSocketInfo startInfo;
    private WebSocketInfo stringInfo;
    private WebSocketInfo byteStringInfo;

    public WebSocketOnSubscribe(String url) {
        this.url = url;
        startInfo = new WebSocketInfo(true);
        stringInfo = new WebSocketInfo();
        byteStringInfo = new WebSocketInfo();
    }

    @Override
    public void call(final Subscriber<?super WebSocketInfo> subscriber) {
        if (webSocket != null) {
            //降低重连频率
            if (!"main".equals(Thread.currentThread().getName())) {
                SystemClock.sleep(2000);
            }
        }

        initWebSocket(subscriber);
    }

    private void initWebSocket(
        final Subscriber<?super WebSocketInfo> subscriber) {
        webSocket = client.newWebSocket(getRequest(url),
                new WebSocketListener() {
                    @Override
                    public void onOpen(final WebSocket webSocket,
                        Response response) {
                        if (showLog) {
                            Log.d("RxWebSocketUtil", url + " --> onOpen");
                        }

                        webSocketMap.put(url, webSocket);
                        AndroidSchedulers.mainThread().createWorker().schedule(new Action0() {
                                @Override
                                public void call() {
                                    if (!subscriber.isUnsubscribed()) {
                                        subscriber.onStart();
                                        startInfo.setWebSocket(webSocket);
                                        subscriber.onNext(startInfo);
                                    }
                                }
                            });
                    }

                    @Override
                    public void onMessage(WebSocket webSocket, String text) {
                        if (!subscriber.isUnsubscribed()) {
                            stringInfo.setWebSocket(webSocket);
                            stringInfo.setString(text);
                            subscriber.onNext(stringInfo);
                        }
                    }

                    @Override
                    public void onMessage(WebSocket webSocket, ByteString bytes) {
                        if (!subscriber.isUnsubscribed()) {
                            byteStringInfo.setWebSocket(webSocket);
                            byteStringInfo.setByteString(bytes);
                            subscriber.onNext(byteStringInfo);
                        }
                    }

                    @Override
                    public void onFailure(WebSocket webSocket, Throwable t,
                        Response response) {
                        if (showLog) {
                            Log.e("RxWebSocketUtil",
                                t.toString() +
                                webSocket.request().url().uri().getPath());
                        }

                        if (!subscriber.isUnsubscribed()) {
                            subscriber.onError(t);
                        }
                    }

                    @Override
                    public void onClosing(WebSocket webSocket, int code,
                        String reason) {
                        webSocket.close(1000, null);
                    }

                    @Override
                    public void onClosed(WebSocket webSocket, int code,
                        String reason) {
                        if (showLog) {
                            Log.d("RxWebSocketUtil",
                                url + " --> onClosed:code= " + code);
                        }
                    }
                });
        subscriber.add(new MainThreadSubscription() {
                @Override
                protected void onUnsubscribe() {
                    webSocket.close(3000, "手动关闭");
                }
            });
    }
}

实现一个WebSocketOnSubscribe 将WebSocket的回调转化成subscriber调用.发送给Observable下游.在onOpen时调用 subscriber.onStart(),并且发送一个onOpen的WebSocketInfo.在subscriber注销的时候关闭WebSocket.在call方法最上面有个SystemClock.sleep(2000),这个主要是为了降低在断连的时候的重连频率,将在下面讲到.
包装成Observable:

Observable.create(new WebSocketOnSubscribe(url))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());

3. 实现自动重连:

Observable.create(new WebSocketOnSubscribe(url))
            //自动重连
            .timeout(timeout, timeUnit).retry()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());

RxJava retry操作符,很完美的实现了这个功能,当上游发出Throwable的时候,retry将错误吃掉,并重新调用 onSubscribe的call方法,也就是WebSocketOnSubscribe的call,就会重新初始化一个WebSocket连接,达到重连的目的,如果一直没有网络,这个retry的调用频率非常高,所以在call方法里面,当是重连的时候,就SystemClock.sleep(2000),休眠2秒,这样重连的频率就是2秒重连一次. 当然在retry上面还有一个timeout操作符.当subscriber.onNext()在指定时间间隔里没有调用,就发出一个timeoutException,让retry重连WebSocket.这个主要是为了适配部分国产机型,当WebSocket发生连接异常时,不会及时发出错误,如小米平板.在每次重连都会把原来的WebSocket关闭.

4. 实现同一个URL的WebSocket共享

Observable.create(new WebSocketOnSubscribe(url))
        //自动重连
        .timeout(timeout, timeUnit)
        .retry()
        //共享
        .doOnUnsubscribe(new Action0() {
            @Override
            public void call() {
                observableMap.remove(url);
                webSocketMap.remove(url);

                if (showLog) {
                    Log.d("RxWebSocketUtil", "注销");
                }
            }
        })
        .doOnNext(new Action1<WebSocketInfo>() {
            @Override
            public void call(WebSocketInfo webSocketInfo) {
                if (webSocketInfo.isOnOpen()) {
                    webSocketMap.put(url, webSocketInfo.getWebSocket());
                }
            }
        })
        .share()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

实现共享功能,主要是为了防止一个URL的WebSocket,建立多个连接,这个主要是由RxJava的share操作符实现,share操作符,使得一个Observable可以有多个subscriber,当有多个subscriber时,当所有的subscriber都取消订阅,这个Observable才会取消订阅.
getWebSocketInfo()方法完整代码:

public Observable<WebSocketInfo> getWebSocketInfo(final String url, final long timeout, final TimeUnit timeUnit) {
    Observable<WebSocketInfo> observable = observableMap.get(url);
    if (observable == null) {
        observable = Observable.create(new WebSocketOnSubscribe(url))
                //自动重连
                .timeout(timeout, timeUnit)
                .retry()
                //共享
                .doOnUnsubscribe(new Action0() {
                    @Override
                    public void call() {
                        observableMap.remove(url);
                        webSocketMap.remove(url);
                        if (showLog) {
                            Log.d("RxWebSocketUtil", "注销");
                        }
                    }
                })
                .doOnNext(new Action1<WebSocketInfo>() {
                    @Override
                    public void call(WebSocketInfo webSocketInfo) {
                        if (webSocketInfo.isOnOpen()) {
                            webSocketMap.put(url, webSocketInfo.getWebSocket());
                        }
                    }
                })
                .share()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
        observableMap.put(url, observable);
    } else {
        observable = Observable.merge(Observable.just(new WebSocketInfo(webSocketMap.get(url), true)), observable);
    }
    return observable;
}

doOnUnsubscribe作用:在Observable注销,即 WebSocket关闭时,移除map中的缓存的Observable和WebSocket.

doOnNext作用: 判断接收到的WebSocketInfo是否是WebSocket在onOpen的时候发的,然后将其缓存起来.作用就是:如果有一个相同的URL订阅Observable,就从缓存中取,这个时候我们应该把一个WebSocket的onOpen事件也发给这个订阅者:

//使用merge操作符,将onOpen事件发给订阅者
observable = Observable.merge(Observable.just(new WebSocketInfo(webSocketMap.get(url), true)), observable);

这样的话,同一个URL的WebSocket,不管在什么地方什么时间订阅,都能收到一个onOpen事件,外部表现的就像一个新的WebSocket.
getWebSocketInfo方法的几种变体:

/**
 * default timeout: 30 days
 * <p>
 * 若忽略小米平板,请调用这个方法
 * </p>
 */
public Observable<WebSocketInfo> getWebSocketInfo(String url) {
    return getWebSocketInfo(url, 30, TimeUnit.DAYS);
}

public Observable<String> getWebSocketString(String url) {
    return getWebSocketInfo(url)
            .map(new Func1<WebSocketInfo, String>() {
                @Override
                public String call(WebSocketInfo webSocketInfo) {
                    return webSocketInfo.getString();
                }
            })
            .filter(new Func1<String, Boolean>() {
                @Override
                public Boolean call(String s) {
                    return s != null;
                }
            });
}

public Observable<ByteString> getWebSocketByteString(String url) {
    return getWebSocketInfo(url)
            .map(new Func1<WebSocketInfo, ByteString>() {
                @Override
                public ByteString call(WebSocketInfo webSocketInfo) {
                    return webSocketInfo.getByteString();
                }
            })
            .filter(new Func1<ByteString, Boolean>() {
                @Override
                public Boolean call(ByteString byteString) {
                    return byteString != null;
                }
            });
}

public Observable<WebSocket> getWebSocket(String url) {
    return getWebSocketInfo(url)
            .map(new Func1<WebSocketInfo, WebSocket>() {
                @Override
                public WebSocket call(WebSocketInfo webSocketInfo) {
                    return webSocketInfo.getWebSocket();
                }
            });
}

5 . send信息到服务端

上面已经讲到WebSocketInfo包含了WebSocket,所以在订阅后,就可以拿到这个WebSocket引用就可以WebSocket.send发送消息到服务端.当然我们的RxWebSocketUtil已经将开启的WebSocket已经缓存.所以我们也可以这样发消息:

/**
 * 如果url的WebSocket已经打开,可以直接调用这个发送消息.
 *
 * @param url
 * @param msg
 */
public void send(String url, String msg) {
    WebSocket webSocket = webSocketMap.get(url);
    if (webSocket != null) {
        webSocket.send(msg);
    } else {
        throw new IllegalStateException("The WebSokcet not open");
    }
}

/**
 * 如果url的WebSocket已经打开,可以直接调用这个发送消息.
 *
 * @param url
 * @param byteString
 */
public void send(String url, ByteString byteString) {
    WebSocket webSocket = webSocketMap.get(url);
    if (webSocket != null) {
        webSocket.send(byteString);
    } else {
        throw new IllegalStateException("The WebSokcet not open");
    }
}

当指定的URL的WebSocket没有打开会直接报错.
异步发送消息到服务端

/**
 * 不用关心url 的WebSocket是否打开,可以直接发送
 *
 * @param url
 * @param msg
 */
public void asyncSend(String url, final String msg) {
    getWebSocket(url)
            .first()
            .subscribe(new Action1<WebSocket>() {
                @Override
                public void call(WebSocket webSocket) {
                    webSocket.send(msg);
                }
            });

}

/**
 * 不用关心url 的WebSocket是否打开,可以直接发送
 *
 * @param url
 * @param byteString
 */
public void asyncSend(String url, final ByteString byteString) {
    getWebSocket(url)
            .first()
            .subscribe(new Action1<WebSocket>() {
                @Override
                public void call(WebSocket webSocket) {
                    webSocket.send(byteString);
                }
            });
}

这两种发送方式,你不用关心URL的WebSocket是否打开,可以直接发送.实现思路也很简单,getWebSocket(url)会获取到Observable,或者是从缓存中取,或者是重新开启一个WebSocket,但你都不需要关心,经过first操作符后,如果是从缓存取的Observable,就注销的当前的Observable,当是新开的WebSocket,注销掉当前的subscriber后,就没有其他subscriber了,这个新开的WebSocket就会关闭(share操作符作用).
最后,如有什么好的建议,可以联系我.

项目地址: gitee

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-7-27 11:00:48修改
2
收藏 1
回复
举报
2条回复
按时间正序
/
按时间倒序
wx610273f694f1c
wx610273f694f1c

优秀的websocket客户端

 

回复
2021-7-29 17:57:22
wx6132e58209156
wx6132e58209156

依赖编译通不过

 

 

回复
2021-9-14 10:30:55
回复
    相关推荐