Используйте 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
Операция не удалась из-за внутренней ошибки.