原文地址:http://fedepaol.github.io/blog/2017/04/30/android-okhttp-and-websockets/
1. Websockets
Rest类型的http调用是Android应用和远程服务器之间最常见的交互方式。然而,有一些场景的交互通过持续连接来处理会更好:考虑到一个聊天室,或者是一个可以双向数据交换的多玩家游戏,服务器能够将数据推送给客户端以及服务器需要知道哪些客户端连接上了。 这种类型的场景可以通过Websockets实现。
2. OkHttp和Websockets
当我最近必须使用websockets时,考虑到Square公司提供的库的质量,OkHttp是我考虑的第一个库。幸运地是,OkHttp(3.5)在2016年12月引入了对Websocket的支持。在这篇博客里,我将尝试描述如何去使用它,并且展示使用它和普通的http请求有什么不同。
3. 建立连接
建立连接是很直接的。你可以一如既往地声明OkHttp client。
client = new OkHttpClient.Builder()
.readTimeout(3, TimeUnit.SECONDS)
.build();
然后从中取出websocket对象:
Request request = new Request.Builder()
.url(serverUrl)
.build();
webSocket = client.newWebSocket(request, new WebSocketListener()
{ ... });
请注意在创建websocket的时候,OkHttp将试图和服务器建立连接。newWebSocket这个工厂方法的第二个参数需要实现WebSocketListener接口,目的是能够得到在对应socket上发生的多种事件的异步通知(例如收到消息,或者是从socket断开连接,或者是失败)。
4. 发送消息
发送消息也是很容易的。直接调用send方法(此方法能接收两种类型的参数:String和ByteString)。因为OkHttp将使用它自己的后台线程发送数据,所以send能够在任何线程中调用而不用担心阻塞当前线程(也不会有抛出NetworkOnMainThreadException的风险)。 这里唯一的警告就是正的返回结果(即返回true)仅仅表明消息被插入队列,但是它并不会反应出传送的结果。据我理解,库的使用者只有在失败的情况下会回调onFailure,所以必须采取乐观的态度。
5. 回调
WebSocketListener接口提供了与之相关联的socket的回调来处理异步事件。这其中包括socket的打开(或者关闭),或者接收到新消息。 不像数据的传送,回调和Android主线程之间的交互需要小心实现,因为WebSocketListener中的方法将运行在后台线程中。Android中最普通的方式就是使用Handler来让后台线程和关联到looper的线程进行交互(比如说Android主线程)。
@Override
public void onMessage(WebSocket webSocket, String text) {
...
handler.sendMessage(..);
}
另外可以实现相同的结果的方法是使用响应式然后暴露出被观察者来发布事件。
6. 关闭连接
OkHttp提供两个方法来关闭连接:
- close webSocket.close(0, “bye”);
请求服务器优雅地关闭连接然后等待确认。在关闭之前,所有已经在队列中的消息将被传送完毕。 既然涉及到交互,那么socket可能不会立即关闭。如果初始化和关闭连接是和Activity的生命周期绑定的(比如onPause/onResume),有一些消息可能是在close被调用之后接收到,所以这需要小心去处理。
- cancel
cancel更加残忍:它会丢弃所有已经在队列中的消息然后残忍地关闭socket。这样也有优点:不必等待家政(housekeeping)和已在队列中消息的传送。然而,选择cancel还是close取决于使用场景。
7. 多说无益,给我看代码
在这里我上传了一个简单的例子:允许app在处于前台时去打开websocket,而当app处于后台时关闭websocket。这就是持续连接被建议使用的方式。使用Service来保持持续连接被认为是错误的行为并且doze模式将使得你的app的声明变得非常艰难(即很容易被系统杀死)。 当然这个例子还有许多需要被完善的点:
- 当app处于后台时调用了cancel 这就意味着有些消息最终是被丢弃了的。一种更好的方式是调用close然后等待所有消息都发送后优雅地关闭。因为在Activity的onPause中处理订阅,不会发生泄漏。我们希望应用进程将存活足够长来让OkHttp线程优雅关闭连接(这期间OkHttp线程需要处理一些事情来达到优雅关闭)。还有更复杂的方式将设计到Service或者使用JobScheduler。
- 没有考虑传送失败 onFailure应该是为了失败监听,在这个例子中只有强制关闭才会通知用户失败。
- 没有使用RxJava 我想要使得保持app的简单,避免引入额外的复杂性,但是Handler太老了(原句是Handlers are so 2013,没懂)。一种更好的解决方式是使用RxJava(这里可能有许多优秀的库对此支持地非常好)。使用RxJava将非常容易地转换接收到的消息和实现智能重连策略(比如说指数退避)。
8. 结论
使用websockets和从http终端取/存(它会忘记调用)完全不一样,然而OkHttp的实现使得很容易地去使用。 在你那边,你不仅需要处理消息的应答,而且还需要监视连接的状态并且采取相应的行为。