安卓 - jmdns无法发现设备

19

我正在尝试实现一个类来发现网络上的服务。 我已经尝试过使用Android的NSD,它可以很好地发现服务,但仅支持API级别16及以上,并且我似乎无法获取服务信息中的txtRecord字段(由于某种原因返回null)。结果发现这是个已知问题...

因此,现在我正在尝试使用jmDNS,但它似乎根本找不到服务。 以下是我的类(我正在使用AndroidAnnotations框架)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());
}
}

在我的应用程序中,我调用:

init(getActivity());

然后使用startDiscovery();开始扫描,使用stopDiscovery();停止扫描。

当然,在清单文件中给应用程序添加所需的权限... 我这里漏掉了什么吗? 如果您需要我提供其他代码/信息 - 请随时问。 谢谢!

2个回答

39

我是ZeroConf Browser for Android的作者,我使用开源库JmDNS来进行解析。它非常好用,但是有一些技巧需要掌握才能正常工作。

  1. 在您的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" />
在开始这项操作之前,您必须通过获取多播锁来允许多播数据包。
@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;
}
不要忘记在停止扫描时解锁多播锁并关闭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);
    }
}
  • 最重要的是不要使用默认构造函数。你必须使用IP地址构造函数。我注意到你的代码中只是在使用JmDNS.create()。我认为出于某种原因,在Android上它起作用的唯一方法是使用下面的构造函数。

  • jmdns = JmDNS.create(deviceIpAddress, HOSTNAME);
    

    2
    可以工作了!原来我使用了错误的构造函数,所以第4点解决了问题。非常感谢!! :) - orenk86
    @Melloware:我需要获取连接到我的WiFi网络的所有无线设备(不仅仅是Android手机)的详细信息,例如IP地址、MAC地址、供应商名称、设备类型(如笔记本电脑、移动设备、空调、冰箱)和主机名。我能否使用jmDNS/ZerConf来满足我的需求? - Santhosh
    @SANTHOSH 不可以。这只是用于发现Bonjour公开的服务,而不是一般的网络扫描器,我想这正是你所寻找的。 - Melloware
    @Melloware:什么能满足我的需求?你能给我建议吗?http://stackoverflow.com/questions/36567725/how-do-i-get-host-names-by-using-ip-mac-addresses-in-android - Santhosh
    1
    @emc,我刚刚编辑了帖子,添加了getDeviceIpAddress()方法。 - Melloware
    显示剩余12条评论

    1
    如果你在使用Android Oreo 8.x版本时遇到了这个错误,这些内容可能会对你有所帮助。
    首先,请确保将以下权限添加到您的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" />
    

    获取多播锁以允许多播数据包。
    WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
    MulticastLock lock = wifi.createMulticastLock("jmdns-multicast-lock");
    lock.setReferenceCounted(true);
    lock.acquire();
    

    现在,只使用这个构造函数JmDNS.create(InetAddress addr, String name)来创建一个JmDNS实例并将其绑定到特定的网络接口,给定其IP地址,例如:
    try {
        jmDNS = JmDNS.create(InetAddress.getByName(obtainIPv4Address(info)), HOST_NAME);
    } catch (IOException e) {
        LogHelper.e(TAG, "Error in JmDNS creation: " + e);
    }
    

    最后,请确保调用JmDNSunreisterAllServices()JmDNS.close()来停止JmDNS流并释放与其相关的任何系统资源。另外,在使用完毕后,请调用MulticastLock.release()以解锁组播锁定。
    try {
        if (jmDNS != null) {
            jmDNS.unregisterAllServices();
            jmDNS.close();
            jmDNS = null;
        }
        if (lock != null) {
            lock.release();
            lock = null;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    obtainIPv4Address() 必须是你的本地方法 @Teocci,但是用茶匙(已经有了)替换它,并按照你的大纲进行操作,在 Android 8 上无法工作,但在 Android 7 上可以。我认为我们有一个问题。这可能与 Android 8 的安全设置有关吗?我使用 LG V30。 - carl

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