本文讨论 Android 如何实现异步全双工 socket

随着移动设备硬件能力的提升和对用户体验的进一步追求,有一些要求高实时性并且存在双向(客户端向服务器端、服务器端向客户端)数据发送的场景,我们需要在 Android 上实现异步(不阻塞 UI 线程)全双工(能同时读、写)的 socket 来满足需求。这个问题在 iOS 平台上已经有非常好的解决方案:CocoaAsyncSocket。它的功能非常完善,其中我们最关心的异步读写:

Queued non-blocking reads and writes, with optional timeouts.
You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically.

Android 平台上类似的开源项目 AndroidAsync,使用的是 java.nio 包下的类。但是 java.nio 包存在先天不足,IO 操作在上一个操作没有完成时会阻塞

This method may be invoked at any time. If another thread has already initiated a write operation upon this channel, however, then an invocation of this method will block until the first operation is complete.

而解决了这一问题的 java.nio2 包在 Android 上没有包含。自己实现异步 socket,首先考虑的是这两个问题:

  1. 是否使用 Service
  2. 使用什么样的方案来实现异步全双工?AsyncTask?线程加同步?

Service 适用于执行长时间在后台运行的没有 UI 的任务,而我们使用 socket 模块大部分是在特定的需要高实时性的页面下。如果用户退出这个页面就不再执行相关代码。也就是说在我们的用例下,socket 模块和 UI 是有很强的绑定关系的。如果使用 Service,还需要处理其相关生命周期,引入不必要的复杂度。综合考虑不使用 Service。

接下来是异步 IO 实现方案的选择。AsyncTask 适用于执行短暂的没有复杂逻辑的后台任务。它没有精确的线程调度机制,其实现在 Android 不同版本间也有差别。异步 socket 模块需要维护连接状态、执行读写操作,有一系列的状态需要维护。在 AsyncTask 无能为力的情况下,如果直接使用线程,不可避免会有同步的问题。线程这套 API 相对于我们需要解决的问题有点偏底层了。Android 提供了一套线程消息机制:Handler。在这基础上,可以方便的实现异步全双工 socket。长连接的状态在主线程维护,IO 通过 handler 发送消息到后台线程执行,执行完毕后再发送消息回主线程。

comments powered by Disqus