androidandroid-annotationsjmdnsmdnsandroid-nsd

Android - jmdns doesn't discover devices


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


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.

    1. 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" />
      
    2. 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;
      }
      
    3. 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);
          }
      }
      
    4. 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);