本阶段的第一课《使用网络服务搜索》,展示如何搜索连接了本地网络的服务。然而使用 WiFi 对等网络(P2P)搜索服务可以直接搜索到附近设备的服务,而不需要连接网络。你可以让该服务运行在手机。这些功能可以帮助我们在两个应用间通讯,即使没有有效的网络或热点。

尽管这一套 API 与之前课程《网络服务搜索》 API 大纲目的类似,但在代码实现还是有很大不同。这节课解说使用 WiFi P2P 如何从其它设备中搜索有效服务。这节课假定你已经熟悉了 WiFi P2P API。

设置清单文件

使用 WiFi P2P 时需要添加权限 CHANGE_WIFI_STATE, ACCESS_WIFI_STATE, 和 INTERNET 到清单文件。虽然 WiFi P2P 使用标准 Java Socket,并不需要有互联网连接,但是在 Android 中使用这些时需要请求权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.nsdchat"
    ...

    <uses-permission
        android:required="true"
        android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission
        android:required="true"
        android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission
        android:required="true"
        android:name="android.permission.INTERNET"/>
    ...

添加本地服务

如果提供本地服务,需要为它注册服务搜索。一旦本地服务注册后,Framework 会自动响应服务搜索请求。

创建本地服务:

  1. 创建 WifiP2pServiceInfo 对象
  2. 配置关于服务的信息
  3. 调用 addLocalService()) 方法注册本地服务
private void startRegistration() {
    //  Create a string map containing information about your service.
    Map record = new HashMap();
    record.put("listenport", String.valueOf(SERVER_PORT));
    record.put("buddyname", "John Doe" + (int) (Math.random() * 1000));
    record.put("available", "visible");

    // Service information.  Pass it an instance name, service type
    // _protocol._transportlayer , and the map containing
    // information other devices will want once they connect to this one.
    WifiP2pDnsSdServiceInfo serviceInfo =
            WifiP2pDnsSdServiceInfo.newInstance("_test", "_presence._tcp", record);

    // Add the local service, sending the service info, network channel,
    // and listener that will be used to indicate success or failure of
    // the request.
    mManager.addLocalService(channel, serviceInfo, new ActionListener() {
        @Override
        public void onSuccess() {
            // Command successful! Code isn't necessarily needed here,
            // Unless you want to update the UI or add logging statements.
        }

        @Override
        public void onFailure(int arg0) {
            // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
        }
    });
}

搜索附近服务

使用回调方法通知应用中的有效服务,因此第一步是创建它们。创建 WifiP2pManager.DnsSdTxtRecordListener 对象来监听到来的消息,该消息由其它设备随意的广播。当收到一条消息,复制设备地址和其它任何你需要的相关信息到现在方法之外的数据结构中,这样稍后可以使用它们。下面的例子采用消息的 “buddyname” 字段作为它的标识。

final HashMap<String, String> buddies = new HashMap<String, String>();
...
private void discoverService() {
    DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
        @Override
        /* Callback includes:
         * fullDomain: full domain name: e.g "printer._ipp._tcp.local."
         * record: TXT record dta as a map of key/value pairs.
         * device: The device running the advertised service.
         */

        public void onDnsSdTxtRecordAvailable(
                String fullDomain, Map record, WifiP2pDevice device) {
                Log.d(TAG, "DnsSdTxtRecord available -" + record.toString());
                buddies.put(device.deviceAddress, record.get("buddyname"));
            }
        };
    ...
}

获取服务信息时,创建 WifiP2pManager.DnsSdServiceResponseListener 对象,它接收实际的描述和连接信息。之前的代码块实现的 Map 对象匹配了设备地址和名称。服务返回 DNS 记录和相关的服务信息。当完成这两个监听的实现后,使用 setDnsSdResponseListeners()) 方法添加它们到 WifiP2pManager 中。

private void discoverService() {
...

    DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
        @Override
        public void onDnsSdServiceAvailable(String instanceName, String registrationType,
                WifiP2pDevice resourceType) {

                // Update the device name with the human-friendly version from
                // the DnsTxtRecord, assuming one arrived.
                resourceType.deviceName = buddies
                        .containsKey(resourceType.deviceAddress) ? buddies
                        .get(resourceType.deviceAddress) : resourceType.deviceName;

                // Add to the custom adapter defined specifically for showing
                // wifi devices.
                WiFiDirectServicesList fragment = (WiFiDirectServicesList) getFragmentManager()
                        .findFragmentById(R.id.frag_peerlist);
                WiFiDevicesAdapter adapter = ((WiFiDevicesAdapter) fragment
                        .getListAdapter());

                adapter.add(resourceType);
                adapter.notifyDataSetChanged();
                Log.d(TAG, "onBonjourServiceAvailable " + instanceName);
        }
    };

    mManager.setDnsSdResponseListeners(channel, servListener, txtListener);
    ...
}

现在调用 addServiceRequest()) 方法创建服务请求,该方法接收一个监听来报告成功或失败。

serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
mManager.addServiceRequest(channel,
        serviceRequest,
        new ActionListener() {
            @Override
            public void onSuccess() {
                // Success!
            }

            @Override
            public void onFailure(int code) {
                // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
            }
        });

最后调用 discoverServices()) 方法。

mManager.discoverServices(channel, new ActionListener() {

    @Override
    public void onSuccess() {
        // Success!
    }

    @Override
    public void onFailure(int code) {
        // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
        if (code == WifiP2pManager.P2P_UNSUPPORTED) {
            Log.d(TAG, "P2P isn't supported on this device.");
        else if(...)
            ...
    }
});

如果一切顺利,很好,已经完成了。如果遇到问题,记得在之前有设置 WifiP2pManager.ActionListener 的异步监听,它提供了告诉成功和失败的回调。诊断问题时,使用在 onFailure()) 方法中的调试码,该错误码示意着问题。下面是可能的错误码和它们的意思:

  1. P2P_UNSUPPORTED
    当前设备不支持 Wi-Fi P2P

  2. BUSY
    系统忙

  3. ERROR
    内部错误,操作失败

翻译:@iOnesmile
原始文档:https://developer.android.google.cn/training/connect-devices-wirelessly/nsd-wifi-direct.html#manifest