最近在重构蓝牙配网的时候,发现了一个问题,在完成wifi账号密码传输后,设备连接wifi,并将自己的ipv4地址在局域网中广播出来,android手机在收到广播后,可以得到设备在局域网中的ip地址,从而进行下一步的操作
但是在android手机接收局域网广播的过程中,代码出现了问题,一般来说,使用DatagramSocket
是这样的:
DatagramSocket datagramSocket;
String boxIPV4 = null;
int port = 3005;
byte[] message = new byte[1024];
try {
// 建立Socket连接
datagramSocket = new DatagramSocket(port);
DatagramPacket dp = new DatagramPacket(message, message.length);
// 准备接收数据
// datagramSocket.setSoTimeout(165000);
datagramSocket.receive(dp);
boxIPV4 = new String(dp.getData(), 0, dp.getLength(), "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
如果不设置超时时间,那线程就会一直堵在datagramSocket.receive(datagramPacket)
这行,但是如果设置了超时时间,那如果是用户自己取消呢,所以就需要手动取消接收广播,像下面代码一样:
try {
if (datagramSocket != null && !datagramSocket.isClosed()) {
datagramSocket.disconnect();
datagramSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
但是奇怪的是,即使执行了上述的代码,socket也没有从receive方法处跳出来,反而出现了anr,查看了disconnect()
以及close()
两个方法的代码后,才明白,原来disconnect()
方法加了synchronized
关键字,而receive()
方法也同样加了一个大大的synchronized
代码块
// DatagramSocket.java
public void disconnect() {
synchronized (this) {
if (isClosed())
return;
if (connectState == ST_CONNECTED) {
impl.disconnect ();
}
connectedAddress = null;
connectedPort = -1;
connectState = ST_NOT_CONNECTED;
explicitFilter = false;
}
}
public synchronized void receive(DatagramPacket p) throws IOException {
synchronized (p) {
if (!isBound())
bind(new InetSocketAddress(0));
// BEGIN Android-changed
if (pendingConnectException != null) {
throw new SocketException("Pending connect failure", pendingConnectException);
}
// END Android-changed
...
}
}
receive方法还在等待接收数据,而disconnect方法又要断开连接,导致死锁,所以出现了anr,所以这里需要先close,然后再disconnect才行
try {
if (datagramSocket != null && !datagramSocket.isClosed()) {
datagramSocket.close();
datagramSocket.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
}
调整了close和disconnect的顺序后,就可以顺利跳出receive方法了
网上也搜到了一篇类似的文章:
https://blog.csdn.net/qq_35522272/article/details/54314289