使用网络服务发现

    添加网络服务发现(Network Service Discovery)到我们的 app 中,可以使我们的用户辨识在局域网内支持我们的 app 所请求的服务的设备。这种技术在点对点应用中能够提供大量帮助,例如文件共享、联机游戏等。Android 的网络服务发现(NSD)API 大大降低实现上述功能的难度。

    本讲将简要介绍如何创建 NSD 应用,使其能够在本地网络内广播自己的名称和连接信息,并且扫描其它正在做同样事情的应用信息。最后,将介绍如何连接运行着同样应用的另一台设备。

    在局域网内注册自己服务的第一步是创建 NsdServiceInfo 对象。此对象包含的信息能够帮助网络中的其他设备决定是否要连接到我们所提供的服务。

    这段代码将服务命名为“NsdChat”。该名称将对所有局域网络中使用 NSD 查找本地服务的设备可见。需要注意的是,在网络内该名称必须是独一无二的。Android 系统会自动处理冲突的服务名称。如果同时有两个名为“NsdChat”的应用,其中一个会被自动转换为类似“NsdChat(1)”这样的名称。

    第二个参数设置了服务类型,即指定应用使用的协议和传输层。语法是“_< protocol >._< transportlayer >”。在上面的代码中,服务使用了TCP协议上的HTTP协议。想要提供打印服务(例如,一台网络打印机)的应用应该将服务的类型设置为“_ipp._tcp”。

    当为我们的服务设置端口号时,应该尽量避免将其硬编码在代码中,以防止与其他应用产生冲突。例如,如果我们的应用仅仅使用端口1337,就可能与其他使用1337端口的应用发生冲突。解决方法是,不要硬编码,使用下一个可用的端口。不必担心其他应用无法知晓服务的端口号,因为该信息将包含在服务的广播包中。接收到广播后,其他应用将从广播包中得知服务端口号,并通过端口连接到我们的服务上。

    如果使用的是 socket,那么我们可以将端口设置为 0,来初始化 socket 到任意可用的端口。

    1. // Initialize a server socket on the next available port.
    2. mServerSocket = new ServerSocket(0);
    3. // Store the chosen port.
    4. mLocalPort = mServerSocket.getLocalPort();
    5. ...
    6. }

    现在,我们已经成功的创建了 对象,接下来要做的是实现 RegistrationListener 接口。该接口包含了注册在 Android 系统中的回调函数,作用是通知应用程序服务注册和注销的成功或者失败。

    万事俱备只欠东风,调用 方法,真正注册服务。

    因为该方法是异步的,所以在服务注册之后的操作都需要在 onServiceRegistered() 方法中进行。

    1. public void registerService(int port) {
    2. NsdServiceInfo serviceInfo = new NsdServiceInfo();
    3. serviceInfo.setServiceName("NsdChat");
    4. serviceInfo.setServiceType("_http._tcp.");
    5. serviceInfo.setPort(port);
    6. mNsdManager = Context.getSystemService(Context.NSD_SERVICE);
    7. mNsdManager.registerService(
    8. serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);

    与注册网络服务类似,服务发现需要两步骤:用相应的回调函数设置发现监听器(Discover Listener),以及调用 discoverServices() 这个异步API。

    首先,实例化一个实现 接口的匿名类。下列代码是一个简单的范例:

    NSD API 通过使用该接口中的方法通知用户程序发现何时开始、何时失败以及何时找到可用服务和何时服务丢失(丢失意味着“不再可用”)。在上述代码中,当发现了可用的服务时,程序做了几次检查。

    1. 比较找到服务的名称与本地服务的名称,判断设备是否获得自己的(合法的)广播。
    2. 检查服务的类型,确认这个类型我们的应用是否可以接入。
    3. 检查服务的名称,确认是否接入了正确的应用。

    我们并不需要每次都检查服务名称,仅当我们想要接入特定的应用时需要检查。例如,应用只想与运行在其他设备上的相同应用通信。然而,如果应用仅仅想接入到一台网络打印机,那么看到服务类型是“_ipp._tcp”的服务就足够了。

    当配置好监听器后,调用 discoverService() 函数,其参数包括试图发现的服务种类、发现使用的协议、以及上一步创建的监听器。

    1. mNsdManager.discoverServices(
    2. SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

    当我们的应用发现了网上可接入的服务,首先需要调用 方法,以确定服务的连接信息。实现 NsdManager.ResolveListener 对象并将其传入 resolveService() 方法,并使用这个 NsdManager.ResolveListener 对象获得包含连接信息的 。

    在应用的生命周期中正确的开启和关闭 NSD 服务是十分关键的。在程序退出时注销服务可以防止其他程序因为不知道服务退出而反复尝试连接的行为。另外,服务发现是一种开销很大的操作,应该随着父 Activity 的暂停而停止,当用户返回该界面时再开启。因此,开发者应该重写 Activity 的生命周期函数,并添加按照需要开启和停止服务广播和发现的代码。

    1. //In your application's Activity
    2. @Override
    3. protected void onPause() {
    4. if (mNsdHelper != null) {
    5. mNsdHelper.tearDown();
    6. }
    7. super.onPause();
    8. }
    9. @Override
    10. super.onResume();
    11. mNsdHelper.registerService(mConnection.getLocalPort());
    12. mNsdHelper.discoverServices();
    13. }
    14. }
    15. @Override
    16. protected void onDestroy() {
    17. mNsdHelper.tearDown();
    18. mConnection.tearDown();
    19. super.onDestroy();
    20. }
    21. // NsdHelper's tearDown method
    22. public void tearDown() {
    23. mNsdManager.unregisterService(mRegistrationListener);
    24. mNsdManager.stopServiceDiscovery(mDiscoveryListener);
    25. }