Android - jmdns doesn't discover devices
Asked Answered
S

2

19

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

Sakai answered 22/5, 2014 at 11:44 Comment(0)
A
40

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);
    
Assemble answered 25/5, 2014 at 11:32 Comment(17)
Works! Turns out I used the wrong constructor, so bullet 4 was what solved it. Thanks a million!! :)Sakai
One thing that kill me is how can one quickly find devices in the local network with some many service types being available. Your browser is fast! Can you give a hint on how you do it? I'm working on a remote control for IoT devices and need to know anything that lives on the local network fast (and get device info in the process)Urinate
So are you saying your code is not finding the services fast enough by my ZeroConf Browser app is? All I am using is the above code so theoretically yours should be as fast as mine?Assemble
On the contrary I'm saying yours is much faster than mine!! Because I loop through a huge list of services (slow). So please, just as a general tip, how do you find so many services in a short time? Do you have a list of services to search for? jmDNS finds all services already? All the examples I see show how to connect to a specific service. But I want to see the "menu" of available services on the local network :)Urinate
Nevermind. I wasn't using jmdns right. I was taking the services from my own big list and not subscribing to service type changes (now it all makes sense). Thx anyway!Urinate
@Assemble : I need to get the details of all wireless devices (not just android mobiles) that are connected to my wifi network. like, IP, MAC addresses, vendor-name, device type (i.e laptop, mobile, AC, Refrigerator) and host names. Can I use jmDNS/ZerConf for my need?Balderas
@SANTHOSH No you cannot. This is only for discovering services exposed by Bonjour. Not a general network scanner which is what I think you are looking for.Assemble
@Assemble : What serves my requirement? Could you please suggest me? #36568225Balderas
Where is getDeviceIpAddress()?Kozhikode
@Kozhikode I just edited the post to add the getDeviceIpAddress() method.Assemble
@Assemble Late to the party but what would be wrong with a call to InetAddress.getLocalHost() to determine the local IP address? Not guaranteed to be the wifi interface?Thirtyone
@Thirtyone you can try it as you can see this post is 5 years old now but the only reliable way I could get the IP to properly resolve for all cases was using the above trick. I remember their being some quirks on old Android versions with getLocalHost(). You can definitely try it though!Assemble
@melloware, shouldn't this excellent "tutorial" become that; a tutorial for using jmdns for Android? Just spent a day pondering as the code works on Windows, but not on Android. I am sure it will after this. Thanks!Superannuate
@Superannuate yeah it should I just need time to blog post somewhere. Usually I just point people here. :0Assemble
@melloware. As assumed it worked. Please do put one line with a link in the GitHub readme pointing here for (future) Android users.Superannuate
@Superannuate do you mean in the JMDNS Github? I am not a committer there so all I could do is suggest it.Assemble
@melloware, Ok. Thought you were, but suggest is a good alternative, yesSuperannuate
C
2

If you are having this error in Android Oreo 8.x, this might help you.

First, Remember to make sure you have added these permissions into your Android manifest.xml.

<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" />

Acquire a multicast lock to allow multi-cast packets.

WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
MulticastLock lock = wifi.createMulticastLock("jmdns-multicast-lock");
lock.setReferenceCounted(true);
lock.acquire();

Now, use only this constructor JmDNS.create(InetAddress addr, String name) to create an instance of JmDNS and bind it to a specific network interface given its IP-address, like this:

try {
    jmDNS = JmDNS.create(InetAddress.getByName(obtainIPv4Address(info)), HOST_NAME);
} catch (IOException e) {
    LogHelper.e(TAG, "Error in JmDNS creation: " + e);
}

Finally, just make sure to call JmDNSunreisterAllServices(), and JmDNS.close() to stop JmDNS stream and releases any system resources associated with it. Also, call MulticastLock.release() to unlock the multicast lock when you're done with it.

try {
    if (jmDNS != null) {
        jmDNS.unregisterAllServices();
        jmDNS.close();
        jmDNS = null;
    }
    if (lock != null) {
        lock.release();
        lock = null;
    }
} catch (Exception e) {
    e.printStackTrace();
}
Comeback answered 27/5, 2019 at 3:10 Comment(1)
The obtainIPv4Address() must be a local method of yours @Teocci, but replacing that, and following your outline with a teaspoon (already had it), it does not work on Android 8, but on Android 7. I think we have a problem. Could it be something in the security setting of Android 8? I use LG V30.Superannuate

© 2022 - 2024 — McMap. All rights reserved.