Android2.2有一个Bug:调用Socket close方法后,阻塞在Socket读上的线程不会抛出IOException,而是继续阻塞(http://code.google.com/p/android/issues/detail?id=7933)。这对于非echo类的TCP应用来说可能是致命的。因为一种通常做法是socket连接后,建立一个线程专门来接收数据。socket关闭,接收线程阻塞读要么扔异常,要么返回错误。但这个Bug导致本地接收线程继续阻塞,还可能导致此Socket无法释放,remote端仍以为连接可用。
有个办法能轻松解决这个bug,那就是升级系统到2.3+。如若必须支持2.2,以下提供workaround和solution各一。
有一个我验证可用的workaround:那就是用Socket的setSoTimeout方法来避免不限时的阻塞读。代码如下:
Socket mTcpSocket = null;
boolean mRunningFlag = false;
public void recvThread() {
Log.d("recvThread", "recvThread start");
try {
mTcpSocket.setSoTimeout(1000); // 超时时长,越短越灵敏,但开销也越大
InputStream in = mTcpSocket.getInputStream();
while (mRunningFlag) {
byte[] data = new byte[1024];
try {
int len = in.read(data);
if (len < 0) {
throw new IOException("remote close socket");
}
Log.d("recvThread", "" + len);
// 这边应替换为处理代码
} catch (SocketTimeoutException e) {
// ignore
}
}
in.close();
} catch (IOException e) {
Log.d("recvThread", e.getMessage());
} finally {
// 记得清理!
Log.d("recvThread", "recvThread stop");
}
}
关闭连接的时候就类似这样:
mRunningFlag = false;
mRecvThread.join();
workaround的运行开销比较大。我觉得更好且更优雅的解决方案是:增加一种消息格式,客户端发送给服务端后,服务端主动关闭连接,之后客户端的接收线程将会抛出IOException,这样就不会阻塞住了。ps:即使不是为了规避这个Bug,也建议客户端断开连接时采用这种方式,因为这样才能保证所有数据都已经发给服务器。