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!!

Original source