只用MulticastLock是不够的。
先看ubuntu miniDLNA/android BubleUPnP 的抓包例子:
# 192.168.2.103:33940 --> 239.255.255.250:1900
M-SEARCH * HTTP/1.1
Man: "ssdp:discover"
Mx: 3
Host: 239.255.255.250:1900
St: ssdp:all
# 192.168.2.109:1900 --> 192.168.2.103:33940
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1810
DATE: Mon, 05 Jan 2015 15:27:27 GMT
ST: uuid:4d696e69-444c-164e-9d41-206a8a7025d1
USN: uuid:4d696e69-444c-164e-9d41-206a8a7025d1
EXT:
SERVER: 3.13.0-43-generic DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.4
LOCATION: http://192.168.2.109:8200/rootDesc.xml
Content-Length: 0
miniDLNA是开源的C代码,而我只关心android上怎么实现dlna client, 无赖BubleDLNA是闭源的。网上搜了一个例子UpnpDemo[1],核心部分是cling[2]。
而我关心的是里面的AndroidRouter。
# AndroidRouter
super.lock();
setWiFiMulticastLock(true);
setWifiLock(true);
# RouterImpl.enable()
## lock(writeLock)
ReentrantReadWriteLock routerLock = new ReentrantReadWriteLock(true);
Lock readLock = routerLock.readLock();
Lock writeLock = routerLock.writeLock();
lock.tryLock(timeoutMilliseconds, TimeUnit.MILLISECONDS);
lock.unlock();
## createNetworkAddressFactory
import java.net.NetworkInterface;
System.setProperty("java.net.preferIPv4Stack", "true");
NetworkInterface iface \in NetworkInterface.getNetworkInterfaces();
iface.getName() \in System.getProperty(SYSTEM_PROPERTY_NET_IFACES);
iface.getDisplayName(); //wlan0
InetAddress addr = iface.getInetAddresses();
addr.getHostAddress() \in System.getProperty(SYSTEM_PROPERTY_NET_ADDRESSES);
addr instanceof Inet4Address
addr.isLoopbackAddress()
addr.getHostAddress();
InterfaceAddress faddr \in iface.getInterfaceAddresses();
faddr.getBroadcast();
faddr.getAddress() instanceof InetAddress
faddr.getNetworkPrefixLength();
## MulticastReceiver multicastReceiver = createMulticastReceiver()
### init
InetSocketAddress addr = new InetSocketAddress(, port);
MulticastSocket socket = new MulticastSocket(port);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(32768);
socket.joinGroup(multicastAddress, faddr);
### run
byte[] buf = new byte[640];
DatagramPacket datagram = new DatagramPacket(buf, buf.length);
socket.receive(datagram);
### stop
socket.isClosed();
socket.leaveGroup(multicastAddress, faddr);
socket.close();
测试发现上面的receiive已经可以收到udp了。下面的不必要,只是为了完整起见放在这里。
## createStreamServer
### init
localAddress = new InetSocketAddress(addr, 0);
socket = new MulticastSocket(localAddress);
socket.setTimeToLive(4);
socket.setReceiveBufferSize(262144);
### run
byte[] buf = new byte[640];
DatagramPacket datagram = new DatagramPacket(buf, buf.length);
socket.receive(datagram);
datagram.getAddress().getHostAddress();
datagram.getPort();
## createStreamClient
### createRequest http GET/POST
杂项
# isWifi
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); //.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
networkInfo.isAvailable();
networkInfo.isConnected();
NetworkInfo networkInfo.getType():
ConnectivityManager.TYPE_WIFI
ConnectivityManager.TYPE_MOBILE
9 //Ethernet since api 13
代码
talk is cheap, show me the code -- 根据AndroidRouter写一个最简版的demo:
https://github.com/DeYangLiu/AndroidPlayer/commit/cb3dd172df8838dd40952342ef0133be1f39d357
测试时不需ndk, 只需把AndroidManifest.xml里面的intent filter注释打开即可。
小结: Android组播需要注意的地方
* InetAddress.getLocalHost() 返回的是 "127.0.0.1"。
* WifiManager.MulticastLock 应该放在joinGroup后面。
# 01.08
感觉说了上面两条没底气,今天来验证了下:
Log.v("gw", "==Inet=" + NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getDisplayName()); 打印的是lo,当然收不到别人的组播啦。
WifiManager.MulticastLock 放在joinGroup前面也可以的。
更正结论:
* 要选择正确的网络接口,通常是wlan0,才能接收组播数据。
* 组播和广播接收发送为了省电默认是关闭的, 需要用WifiManager.MulticastLock.acquire()打开;由于它耗电,acquire/release圈起来的代码要求尽可能的少。
Ref
[1] http://blog.csdn.net/ljx19900116/article/details/42272095
[2] https://github.com/4thline/cling