17

In Android app that I'm currently developing, I want to connect do zero-config networks using NsdManager.

I managed to run network service discovery and connect to the desired network, but after stopping discovery NsdManager thread is still running. This leads to the situation when after a few screen rotations there are many NsdManager threads that are browsing for a connection.

enter image description here

When any network is available, device tries to synchronize many times, so every NsdManager is still active, despite stopping service discovery.

Bellow is my code:

package dtokarzewsk.nsdservicetest;

import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;
import java.net.InetAddress;

public class NsdTest {
    private static final String NSD_SERVICE_NAME = "TestService";
    private static final String NSD_SERVICE_TYPE = "_http._tcp.";
    private int mPort;
    private InetAddress mHost;
    private Context mContext;
    private NsdManager mNsdManager;
    private android.net.nsd.NsdManager.DiscoveryListener mDiscoveryListener;
    private android.net.nsd.NsdManager.ResolveListener mResolveListener;

    public NsdTest(Context context) {
        mContext = context;
    }

    public void startListening() {
        initializeResolveListener();
        initializeDiscoveryListener();
        mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
        mNsdManager.discoverServices(NSD_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
    }

    public void stopListening() {
        mNsdManager.stopServiceDiscovery(mDiscoveryListener);
    }

    private void initializeResolveListener() {
        mResolveListener = new NsdManager.ResolveListener() {
            @Override
            public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
                Log.d("NSDService test","Resolve failed");
            }

            @Override
            public void onServiceResolved(NsdServiceInfo serviceInfo) {
                NsdServiceInfo info = serviceInfo;
                Log.d("NSDService test","Resolve failed");
                mHost = info.getHost();
                mPort = info.getPort();
                Log.d("NSDService test","Service resolved :" + mHost + ":" + mPort);
            }
        };
    }

    private void initializeDiscoveryListener() {
        mDiscoveryListener = new NsdManager.DiscoveryListener() {
            @Override
            public void onStartDiscoveryFailed(String serviceType, int errorCode) {
                Log.d("NSDService test","Discovery failed");
            }

            @Override
            public void onStopDiscoveryFailed(String serviceType, int errorCode) {
                Log.d("NSDService test","Stopping discovery failed");
            }

            @Override
            public void onDiscoveryStarted(String serviceType) {
                Log.d("NSDService test","Discovery started");
            }

            @Override
            public void onDiscoveryStopped(String serviceType) {
                Log.d("NSDService test","Discovery stopped");
            }

            @Override
            public void onServiceFound(NsdServiceInfo serviceInfo) {
                NsdServiceInfo info = serviceInfo;
                Log.d("NSDService test","Service found: " + info.getServiceName());
                if (info.getServiceName().equals(NSD_SERVICE_NAME))
                    mNsdManager.resolveService(info, mResolveListener);
            }

            @Override
            public void onServiceLost(NsdServiceInfo serviceInfo) {
                NsdServiceInfo info = serviceInfo;
                Log.d("NSDService test","Service lost: " + info.getServiceName());
            }
        };
    }
}

And in main Activity:

private NsdTest mNsdTest;

@Override
protected void onResume() {
    super.onResume();
    mNsdTest = new NsdTest(this);
    mNsdTest.startListening();
}

@Override
protected void onPause() {
    mNsdTest.stopListening();
    super.onPause();
}

How can I fix this problem?

Onik
  • 19,396
  • 14
  • 68
  • 91
  • Have you considered using a `WeakReference` to hold your `Context`? – Sebastiano May 02 '15 at 18:14
  • @dextor Unfortunately it doesn't change anything. Still multiple threads are being created. – Dawid Tokarzewski May 02 '15 at 23:45
  • How do you conclude that the `NSDManager` threads are actually still performing mDNS discovery, especially since you mention that `onDiscoveryStopped` is being called correctly? Are you seeing the mDNS packets in a sniffer? It *could* be the case that discovery has stopped but the system has kept the thread around, probably waiting to release some resources before finally killing it. – curioustechizen May 04 '15 at 11:26
  • 1
    I didn't say that threads are still performing discovery. Actually they don't. The problem is that after stopping discovery they are not killed, even after long time. In extreme situation when user performs 20-30 screen rotation there are 20-30 opened threads and instead killing them app crashes. – Dawid Tokarzewski May 04 '15 at 11:52
  • @DawidTokarzewski - I am using mDNS from nodemcu iot devices which gives final name as `serviceName._arduino._tcp.local` but the same is not getting discovered with the code... any help please? – Varun Jul 04 '22 at 16:56

2 Answers2

7

My current solution for this, is to make class containing NsdManager (NsdTest in above example) a singleton with changing context.

It doesn't change the fact that NSdManager thread is constantly running even after stopping service discovery, but at least there is only one active NsdManager thread after resuming application.

Maybe it's not the most elegant way, but is enough for me.

public class NsdTest {

private static NsdTest mInstance;

private static final String NSD_SERVICE_NAME = "TestService";
private static final String NSD_SERVICE_TYPE = "_http._tcp.";
private int mPort;
private InetAddress mHost;
private Context mContext;
private NsdManager mNsdManager;
private android.net.nsd.NsdManager.DiscoveryListener mDiscoveryListener;
private android.net.nsd.NsdManager.ResolveListener mResolveListener;
private static NsdTest mInstance;

private NsdTest(Context context) {
    mContext = context;
}

public static NsdTest getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new NsdTest(context);
    } else {
       mContext = context;
    }
    return mInstance;
}
...}

If anybody knows better solution, please post it.

  • 1
    I don't know a better solution, but I did notice that `NSD_SERVICE_TYPE = "_http._tcp."` seems to have an extra dot (`.`) at the end of the value. I don't know if that makes any difference, but the documentation shows a dot only in the middle, not at the end. – LarsH Oct 17 '18 at 19:35
  • I tried both versions with and without dot and it acts the same. – Dawid Tokarzewski Oct 19 '18 at 12:18
  • I think I'm fighting this same thing. I'm noticing my android clients take *forever* to find my service. Rebooting them seems to be the only cure. I'm about to borrow your fix and see if it helps. Also the extra dot is per spec. – JoeHz Mar 06 '19 at 22:40
  • Really dumb question here but the smart ones haven't solved it. The call to get the NSDManager takes a Context object. You are passing in the ApplicationContext and not the Activity's, right? – JoeHz Mar 06 '19 at 23:00
1

The reason is because NsdManager.stopServiceDiscovery is asynchronous. From the documentation, you must wait for onDiscoveryStopped(String) for the discovery session to actually end.

That you pause immediately after sending the asynchronous request probably means that the callback that would have been triggered got "lost" as your application was in the background. Asynchronous callbacks have a habit of not handling being in the background well.

justhecuke
  • 765
  • 4
  • 8
  • 1
    It's not the problem. onDiscoveryStopped(String) is triggered properly every time (on every screen rotation). – Dawid Tokarzewski May 03 '15 at 10:06
  • Per my understanding, even if the callback is "lost", the discovery should still stop. In other words, the callback methods should be invoked after the discovery actually stops. – curioustechizen May 04 '15 at 11:20
  • @curioustechizen In a perfect world that would be so. But Android does wonky things when you do Asynchronous calls and then put your app in the background. The NsdService could not have received the Post and may not have had the chance to run it, or it tried to run it but, because your app was in the background, some check somewhere failed and it exited without you knowing. Nsd has lots of race-conditions, so I'm a bit cynical. – justhecuke May 05 '15 at 05:06