Android - jmdns doesn't discover devices
android, android-annotations, android-nsd, jmdns, mdns
Solution
I am the author of ZeroConf Browser for Android and I use the open source Library JmDNS for all my resolving. It works great but there are a few tricks to getting it to work properly.
In your Android manifest.xml make sure you have these permissions at least.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
Before starting the activity you must allow multi-cast packets by acquiring a multicast lock.
@Override
protected void onStart() {
Log.i(TAG, "Starting ServiceActivity...");
super.onStart();
try {
Log.i(TAG, "Starting Mutlicast Lock...");
WifiManager wifi = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
// get the device ip address
final InetAddress deviceIpAddress = getDeviceIpAddress(wifi);
multicastLock = wifi.createMulticastLock(getClass().getName());
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
Log.i(TAG, "Starting ZeroConf probe....");
jmdns = JmDNS.create(deviceIpAddress, HOSTNAME);
jmdns.addServiceTypeListener(this);
} catch (IOException ex) {
Log.e(TAG, ex.getMessage(), ex);
}
Log.i(TAG, "Started ZeroConf probe....");
}
private InetAddress getDeviceIpAddress(WifiManager wifi) {
InetAddress result = null;
try {
// default to Android localhost
result = InetAddress.getByName("10.0.0.2");
// figure out our wifi address, otherwise bail
WifiInfo wifiinfo = wifi.getConnectionInfo();
int intaddr = wifiinfo.getIpAddress();
byte[] byteaddr = new byte[] { (byte) (intaddr & 0xff), (byte) (intaddr >> 8 & 0xff),
(byte) (intaddr >> 16 & 0xff), (byte) (intaddr >> 24 & 0xff) };
result = InetAddress.getByAddress(byteaddr);
} catch (UnknownHostException ex) {
Log.w(TAG, String.format("getDeviceIpAddress Error: %s", ex.getMessage()));
}
return result;
}
And don't forget on stopping the scan to unlock the multicast lock and shut down JmDNS.
@Override
protected void onStop() {
Log.i(TAG, "Stopping ServiceActivity...");
super.onStop();
stopScan();
}
private static void stopScan() {
try {
if (jmdns != null) {
Log.i(TAG, "Stopping ZeroConf probe....");
jmdns.unregisterAllServices();
jmdns.close();
jmdns = null;
}
if (multicastLock != null) {
Log.i(TAG, "Releasing Mutlicast Lock...");
multicastLock.release();
multicastLock = null;
}
} catch (Exception ex) {
Log.e(TAG, ex.getMessage(), ex);
}
}
Most importanty don't use the default constructor. You must use the IP Address Constructor. I noticed in your code you are just doing JmDNS.create(). I think for some reason the only way it works on Android is to use the contructor below.
jmdns = JmDNS.create(deviceIpAddress, HOSTNAME);
Problem
I'm trying to implement a class to discover services on the network. I've tried working with Android's NSD and it does discover the services fine, but it supports only API levels 16 and up, and I can't seem to retrieve the txtRecord field within the service info (it returns null for some reason). Turns out it's a known problem... So now I'm trying to work with jmDNS, which doesn't seem to find services at all. here's my class (I'm working with the AndroidAnnotations framework) MDnsHelper: ``` @EBean public class MDnsHelper implements ServiceListener { public static final String SERVICE_TYPE = "_http._tcp.local"; Activity activity; private JmDNS jmdns; private MulticastLock multicastLock; WifiManager wm; InetAddress bindingAddress; boolean isDiscovering; public void init(Activity activity) { this.activity = activity; isDiscovering = false; wm = (WifiManager) activity.getSystemService(Context.WIFI_SERVICE); multicastLock = wm.createMulticastLock(activity.getPackageName()); multicastLock.setReferenceCounted(false); } @Background public void startDiscovery() { if (isDiscovering) return; System.out.println("starting..."); multicastLock.acquire(); try { System.out.println("creating jmdns"); jmdns = JmDNS.create(); System.out.println("jmdns created"); } catch (IOException e) { e.printStackTrace(); } finally { if (jmdns != null) { jmdns.addServiceListener(SERVICE_TYPE, MDnsHelper.this); isDiscovering = true; System.out.println("discovering services of type: " + SERVICE_TYPE); } } } @Background public void stopDiscovery() { if (!isDiscovering || jmdns == null) return; System.out.println("stopping..."); multicastLock.release(); jmdns.removeServiceListener(SERVICE_TYPE, MDnsHelper.this); System.out.println("listener for " + SERVICE_TYPE + " removed"); try { jmdns.close(); isDiscovering = false; System.out.println("jmdns closed"); } catch (IOException e) { e.printStackTrace(); } } @Override public void serviceAdded(ServiceEvent service) { System.out.println("found: " + service.getInfo().toString()); } @Override public void serviceRemoved(ServiceEvent service) { System.out.println("lost: " + service.getInfo().toString()); } @Override public void serviceResolved(ServiceEvent service) { System.out.println("resolved: " + service.getInfo().toString()); } } ``` And in my app I call: ``` init(getActivity()); ``` And then `startDiscovery();` to start scanning and `stopDiscovery();` to stop scanning. And of course, I gave the app the required permissions in the manifest... What am I missing here? If you need me to provide additional code/info - just ask. thanks!!