Используйте Wi-Fi Direct (P2P) для обнаружения служб.

В первом уроке этого курса, «Использование обнаружения сетевых служб» , было показано, как обнаруживать службы, подключенные к локальной сети. Однако использование обнаружения служб Wi-Fi Direct (P2P) позволяет обнаруживать службы близлежащих устройств напрямую, без подключения к сети. Вы также можете публиковать информацию о службах, запущенных на вашем устройстве. Эти возможности помогают наладить взаимодействие между приложениями даже при отсутствии локальной сети или точки доступа.

Хотя этот набор API по своему назначению аналогичен API обнаружения сетевых служб, описанным в предыдущем уроке, их реализация в коде существенно отличается. В этом уроке показано, как обнаруживать службы, доступные на других устройствах, с помощью Wi-Fi Direct. Предполагается, что вы уже знакомы с API Wi-Fi Direct .

Настройте манифест

Чтобы использовать Wi-Fi P2P, добавьте в манифест разрешения CHANGE_WIFI_STATE , ACCESS_WIFI_STATE , ACCESS_FINE_LOCATION и INTERNET . Если ваше приложение предназначено для Android 13 (уровень API 33) или выше, добавьте в манифест также разрешение NEARBY_WIFI_DEVICES . Несмотря на то, что Wi-Fi Direct не требует подключения к Интернету, он использует стандартные сокеты Java, и для их использования в 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"/>
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission
        android:name="android.permission.NEARBY_WIFI_DEVICES"
        <!-- If your app derives location information from Wi-Fi APIs,
             don't include the "usesPermissionFlags" attribute. -->
        android:usesPermissionFlags="neverForLocation" />
    <uses-permission
        android:required="true"
        android:name="android.permission.ACCESS_FINE_LOCATION"
        <!-- If any feature in your app relies on precise location information,
             don't include the "maxSdkVersion" attribute. -->
        android:maxSdkVersion="32" />
    ...

Помимо указанных выше разрешений, для следующих API также требуется включение режима определения местоположения:

Добавить локальную услугу

Если вы предоставляете локальный сервис, вам необходимо зарегистрировать его для обнаружения сервисов. После регистрации локального сервиса фреймворк автоматически отвечает на запросы обнаружения сервисов от одноранговых узлов.

Чтобы создать локальную службу:

  1. Создайте объект WifiP2pServiceInfo .
  2. Наполните его информацией о вашей услуге.
  3. Вызовите addLocalService() , чтобы зарегистрировать локальную службу для обнаружения служб.

Котлин

    private fun startRegistration() {
        //  Create a string map containing information about your service.
        val record: Map<String, String> = mapOf(
                "listenport" to SERVER_PORT.toString(),
                "buddyname" to "John Doe${(Math.random() * 1000).toInt()}",
                "available" to "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.
        val 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.
        manager.addLocalService(channel, serviceInfo, object : WifiP2pManager.ActionListener {
            override fun onSuccess() {
                // Command successful! Code isn't necessarily needed here,
                // Unless you want to update the UI or add logging statements.
            }

            override fun onFailure(arg0: Int) {
                // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
            }
        })
    }

Ява

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

Откройте для себя близлежащие услуги

Android использует методы обратного вызова для уведомления вашего приложения о доступных сервисах, поэтому первым делом нужно их настроить. Создайте прослушиватель WifiP2pManager.DnsSdTxtRecordListener для прослушивания входящих записей. Эта запись может также транслироваться другими устройствами. При поступлении записи скопируйте адрес устройства и любую другую необходимую информацию в структуру данных, внешнюю по отношению к текущему методу, чтобы иметь к ней доступ позже. В следующем примере предполагается, что запись содержит поле «buddyname», заполненное идентификатором пользователя.

Котлин

private val buddies = mutableMapOf<String, String>()
...
private fun discoverService() {
    /* 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.
     */
    val txtListener = DnsSdTxtRecordListener { fullDomain, record, device ->
        Log.d(TAG, "DnsSdTxtRecord available -$record")
        record["buddyname"]?.also {
            buddies[device.deviceAddress] = it
        }
    }
}

Ява

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 с соответствующей информацией о сервисе. После реализации обоих прослушивателей добавьте их в WifiP2pManager с помощью метода setDnsSdResponseListeners() .

Котлин

private fun discoverService() {
    ...

    val servListener = DnsSdServiceResponseListener { instanceName, registrationType, resourceType ->
        // Update the device name with the human-friendly version from
        // the DnsTxtRecord, assuming one arrived.
        resourceType.deviceName = buddies[resourceType.deviceAddress] ?: resourceType.deviceName

        // Add to the custom adapter defined specifically for showing
        // wifi devices.
        val fragment = fragmentManager
                .findFragmentById(R.id.frag_peerlist) as WiFiDirectServicesList
        (fragment.listAdapter as WiFiDevicesAdapter).apply {
            add(resourceType)
            notifyDataSetChanged()
        }

        Log.d(TAG, "onBonjourServiceAvailable $instanceName")
    }

    manager.setDnsSdResponseListeners(channel, servListener, txtListener)
    ...
}

Ява

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

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

Теперь создайте запрос на обслуживание и вызовите addServiceRequest() . Этот метод также требует прослушивателя для сообщения об успешном или неудачном выполнении.

Котлин

        serviceRequest = WifiP2pDnsSdServiceRequest.newInstance()
        manager.addServiceRequest(
                channel,
                serviceRequest,
                object : WifiP2pManager.ActionListener {
                    override fun onSuccess() {
                        // Success!
                    }

                    override fun onFailure(code: Int) {
                        // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
                    }
                }
        )

Ява

        serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
        manager.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() .

Котлин

        manager.discoverServices(
                channel,
                object : WifiP2pManager.ActionListener {
                    override fun onSuccess() {
                        // Success!
                    }

                    override fun onFailure(code: Int) {
                        // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
                        when (code) {
                            WifiP2pManager.P2P_UNSUPPORTED -> {
                                Log.d(TAG, "Wi-Fi Direct isn't supported on this device.")
                            }
                        }
                    }
                }
        )

Ява

        manager.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, "Wi-Fi Direct isn't supported on this device.");
                else if(...)
                    ...
            }
        });

Если всё прошло успешно, ура, готово! Если возникнут проблемы, помните, что асинхронные вызовы принимают в качестве аргумента WifiP2pManager.ActionListener , что обеспечивает обратные вызовы, указывающие на успешность или неудачу. Для диагностики проблем добавьте отладочный код в onFailure() . Код ошибки, возвращаемый методом, подскажет, в чём проблема. Вот возможные значения ошибок и их значение.

P2P_UNSUPPORTED
Wi-Fi Direct не поддерживается на устройстве, на котором запущено приложение.
BUSY
Система слишком занята для обработки запроса.
ERROR
Операция не удалась из-за внутренней ошибки.