NsdManager无法停止服务发现。

17
在我目前正在开发的 Android 应用中,我想使用 NsdManager 连接零配置网络。我成功地运行了网络服务发现并连接到所需的网络,但在停止发现后,NsdManager 线程仍在运行。这会导致在几次屏幕旋转后,有许多正在浏览连接的 NsdManager 线程。
当任何网络可用时,设备尝试同步多次,因此每个 NsdManager 仍处于活动状态,尽管已经停止服务发现。
下面是我的代码:
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());
            }
        };
    }
}

在主要的Activity中:

private NsdTest mNsdTest;

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

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

我该如何解决这个问题?


你是否考虑使用 WeakReference<Context> 来保存你的 Context - Sebastiano
很遗憾,它并没有改变任何事情。仍然会创建多个线程。 - Dawid Tokarzewski
你如何得出 NSDManager 线程仍在执行 mDNS 发现的结论,特别是你提到 onDiscoveryStopped 被正确调用了?你是否在嗅探器中看到了 mDNS 数据包?也许发现已经停止,但系统仍然保留线程,可能在最终杀死它之前等待释放一些资源。 - curioustechizen
1
我并没有说线程仍在执行发现操作,实际上它们已经停止了。问题在于,在停止发现操作后,这些线程并没有被杀死,即使过了很长时间也是如此。在极端情况下,当用户进行20-30次屏幕旋转时,会有20-30个线程处于打开状态,而不是被杀死,这将导致应用程序崩溃。 - Dawid Tokarzewski
@DawidTokarzewski - 我正在使用来自nodemcu iot设备的mDNS,它将最终名称设置为serviceName._arduino._tcp.local,但是代码无法发现相同的内容...请帮忙解决一下吗? - Varun
2个回答

7

我的当前解决方案是,将包含NsdManager(在上面的示例中为NsdTest)的类作为单例使用,同时改变上下文。

这并不能改变NSdManager线程在停止服务发现后仍然不断运行的事实,但至少在恢复应用程序后只有一个活动的NsdManager线程。

也许这不是最优雅的方式,但对我来说已经足够了。

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;
}
...}

如果有更好的解决方案,请发表。

1
我不知道更好的解决方案,但我注意到 NSD_SERVICE_TYPE = "_http._tcp." 似乎在值的末尾有一个额外的点(.)。我不知道这是否有任何区别,但文档只显示中间有一个点,而不是末尾。 - LarsH
我尝试了带点和不带点的两个版本,但它们的表现是一样的。 - Dawid Tokarzewski
我认为我正在与相同的问题作斗争。我注意到我的Android客户端需要很长时间才能找到我的服务。重新启动它们似乎是唯一的解决办法。我即将借用你的解决方法,看看是否有所帮助。此外,额外的点是按照规范的要求添加的。 - JoeHz
这里有一个非常愚蠢的问题,但那些聪明的人还没有解决它。调用获取NSDManager的方法需要一个Context对象。你传递的是ApplicationContext而不是Activity的,对吗? - JoeHz

1
由于NsdManager.stopServiceDiscovery是异步的,所以需要等待文档中的onDiscoveryStopped(String)回调函数才能确保发现会话实际上已经结束。
您立即在发送异步请求后暂停,可能意味着由于应用程序在后台运行时触发的回调被“丢失”。 异步回调习惯于无法很好地处理在后台运行的情况。

1
这不是问题。onDiscoveryStopped(String) 在每次屏幕旋转时都能正确触发。 - Dawid Tokarzewski
据我理解,即使回调“丢失”,发现仍应该停止。换句话说,在发现实际停止后,回调方法应该被调用。 - curioustechizen
在完美的世界中,那样做可能是没问题的。但是当你进行异步调用并将应用程序置于后台时,Android会出现一些奇怪的问题。NsdService 可能没有收到 Post 并且可能没有机会运行它,或者它尝试运行它,但由于您的应用程序在后台,某个检查失败并且退出而不通知您。由于 Nsd 中存在大量的竞争条件,因此我有点愤世嫉俗。 - justhecuke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接