Обзор поддержки Wi-Fi

Возможности Wi-Fi Aware позволяют устройствам под управлением Android 8.0 (уровень API 26) и выше обнаруживать друг друга и подключаться друг к другу напрямую, без необходимости использования других типов соединений. Wi-Fi Aware также известен как Neighbor Awareness Networking (NAN).

Сетевое взаимодействие Wi-Fi Aware осуществляется путём формирования кластеров с соседними устройствами или путём создания нового кластера, если устройство является первым в зоне действия. Этот алгоритм кластеризации применяется ко всему устройству и управляется системной службой Wi-Fi Aware; приложения не могут контролировать алгоритм кластеризации. Приложения используют API Wi-Fi Aware для взаимодействия с системной службой Wi-Fi Aware, которая управляет оборудованием Wi-Fi Aware на устройстве.

API Wi-Fi Aware позволяют приложениям выполнять следующие операции:

  • Обнаружение других устройств: API предоставляет механизм поиска других устройств поблизости. Процесс начинается, когда одно устройство публикует один или несколько доступных для обнаружения сервисов. Затем, когда устройство подписывается на один или несколько сервисов и попадает в зону действия Wi-Fi издателя, подписчик получает уведомление об обнаружении подходящего издателя. После того, как подписчик обнаружит издателя, он может отправить короткое сообщение или установить сетевое соединение с обнаруженным устройством. Устройства могут одновременно быть как издателями, так и подписчиками.

  • Создание сетевого подключения: после того как два устройства обнаружат друг друга, они могут создать двунаправленное сетевое соединение Wi-Fi Aware без точки доступа.

Сетевые соединения Wi-Fi Aware обеспечивают более высокую пропускную способность на больших расстояниях, чем соединения Bluetooth . Такие соединения полезны для приложений, обменивающихся большими объёмами данных между пользователями, например, для обмена фотографиями.

Улучшения Android 13 (уровень API 33)

На устройствах под управлением Android 13 (уровень API 33) и выше, поддерживающих режим мгновенной связи, приложения могут использовать методы PublishConfig.Builder.setInstantCommunicationModeEnabled() и SubscribeConfig.Builder.setInstantCommunicationModeEnabled() для включения или отключения режима мгновенной связи для сеанса обнаружения издателя или подписчика. Режим мгновенной связи ускоряет обмен сообщениями, обнаружение сервисов и любой путь передачи данных, настроенный в рамках сеанса обнаружения издателя или подписчика. Чтобы определить, поддерживает ли устройство режим мгновенной связи, используйте метод isInstantCommunicationModeSupported() .

Улучшения Android 12 (уровень API 31)

Android 12 (уровень API 31) добавляет некоторые улучшения в Wi-Fi Aware:

  • На устройствах под управлением Android 12 (уровень API 31) или выше вы можете использовать обратный вызов onServiceLost() , чтобы получать оповещения о том, что ваше приложение потеряло обнаруженную службу из-за ее остановки или выхода за пределы диапазона.
  • Настройка каналов передачи данных Wi-Fi Aware была упрощена. В более ранних версиях для передачи MAC-адреса инициатора использовался обмен сообщениями уровня L2, что приводило к задержкам. На устройствах под управлением Android 12 и более поздних версий ответчик (сервер) можно настроить на приём любого однорангового узла, то есть ему не нужно заранее знать MAC-адрес инициатора. Это ускоряет установку каналов передачи данных и позволяет организовать несколько соединений «точка-точка» с помощью всего одного сетевого запроса.
  • Приложения, работающие на Android 12 и выше, могут использовать метод WifiAwareManager.getAvailableAwareResources() для получения количества доступных в данный момент путей передачи данных, сеансов публикации и сеансов подписки. Это помогает приложению определить, достаточно ли доступных ресурсов для выполнения требуемых функций.

Начальная настройка

Чтобы настроить приложение для использования обнаружения и сети Wi-Fi Aware, выполните следующие действия:

  1. Запросите следующие разрешения в манифесте вашего приложения:

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission 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: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" />
  2. Проверьте, поддерживает ли устройство Wi-Fi Aware с помощью API PackageManager , как показано ниже:

    Котлин

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Ява

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. Проверьте, доступна ли функция Wi-Fi Aware. Функция Wi-Fi Aware может быть установлена на устройстве, но в данный момент недоступна, поскольку пользователь отключил Wi-Fi или геолокацию. В зависимости от аппаратных и прошивочных возможностей некоторые устройства могут не поддерживать Wi-Fi Aware, если используется Wi-Fi Direct, SoftAP или модем. Чтобы проверить, доступна ли функция Wi-Fi Aware, вызовите функцию isAvailable() .

    Доступность Wi-Fi Aware может измениться в любой момент. Ваше приложение должно зарегистрировать BroadcastReceiver для получения ACTION_WIFI_AWARE_STATE_CHANGED , которое отправляется при каждом изменении доступности. Когда ваше приложение получает намерение трансляции, оно должно отменить все текущие сеансы (предполагается, что работа Wi-Fi Aware прервалась), затем проверить текущее состояние доступности и соответствующим образом скорректировать своё поведение. Например:

    Котлин

    val wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager?
    val filter = IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED)
    val myReceiver = object : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            // discard current sessions
            if (wifiAwareManager?.isAvailable) {
                ...
            } else {
                ...
            }
        }
    }
    context.registerReceiver(myReceiver, filter)

    Ява

    WifiAwareManager wifiAwareManager = 
            (WifiAwareManager)context.getSystemService(Context.WIFI_AWARE_SERVICE)
    IntentFilter filter =
            new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
    BroadcastReceiver myReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // discard current sessions
            if (wifiAwareManager.isAvailable()) {
                ...
            } else {
                ...
            }
        }
    };
    context.registerReceiver(myReceiver, filter);

Более подробную информацию смотрите в разделе Трансляции .

Получить сеанс

Чтобы начать использовать Wi-Fi Aware, ваше приложение должно получить сеанс WifiAwareSession , вызвав attach() . Этот метод выполняет следующие действия:

  • Включает оборудование Wi-Fi Aware.
  • Присоединяется или формирует кластер Wi-Fi Aware.
  • Создает сеанс Wi-Fi Aware с уникальным пространством имен, которое действует как контейнер для всех сеансов обнаружения, созданных в нем.

Если приложение успешно подключается, система выполняет обратный вызов onAttached() . Этот обратный вызов предоставляет объект WifiAwareSession , который ваше приложение должно использовать для всех дальнейших операций сеанса. Приложение может использовать сеанс для публикации сервиса или подписки на сервис .

Ваше приложение должно вызывать attach() только один раз. Если приложение вызывает attach() несколько раз, оно получает отдельный сеанс для каждого вызова, каждый со своим собственным пространством имён. Это может быть полезно в сложных сценариях, но, как правило, этого следует избегать.

Опубликовать услугу

Чтобы сделать службу обнаруживаемой, вызовите метод publish() , который принимает следующие параметры:

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

Вот пример:

Котлин

val config: PublishConfig = PublishConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.publish(config, object : DiscoverySessionCallback() {

    override fun onPublishStarted(session: PublishDiscoverySession) {
        ...
    }

    override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
        ...
    }
})

Ява

PublishConfig config = new PublishConfig.Builder()
    .setServiceName(Aware_File_Share_Service_Name)
    .build();

awareSession.publish(config, new DiscoverySessionCallback() {
    @Override
    public void onPublishStarted(PublishDiscoverySession session) {
        ...
    }
    @Override
    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
        ...
    }
}, null);

Если публикация прошла успешно, вызывается метод обратного вызова onPublishStarted() .

После публикации, когда устройства с соответствующими приложениями подписчиков попадают в зону действия Wi-Fi публикующего устройства, подписчики обнаруживают сервис. Когда подписчик обнаруживает издателя, издатель не получает уведомление; однако, если подписчик отправляет сообщение издателю, издатель получает уведомление. В этом случае вызывается метод обратного вызова onMessageReceived() . Вы можете использовать аргумент PeerHandle этого метода, чтобы отправить сообщение подписчику или установить с ним соединение .

Чтобы остановить публикацию службы, вызовите DiscoverySession.close() . Сеансы обнаружения связаны с родительским сеансом WifiAwareSession . Если родительский сеанс закрыт, связанные с ним сеансы обнаружения также закрываются. Хотя отброшенные объекты также закрываются, система не гарантирует закрытие сеансов, выходящих за рамки области действия, поэтому мы рекомендуем явно вызывать методы close() .

Подписаться на услугу

Чтобы подписаться на услугу, вызовите метод subscribe() , который принимает следующие параметры:

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

Вот пример:

Котлин

val config: SubscribeConfig = SubscribeConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.subscribe(config, object : DiscoverySessionCallback() {

    override fun onSubscribeStarted(session: SubscribeDiscoverySession) {
        ...
    }

    override fun onServiceDiscovered(
            peerHandle: PeerHandle,
            serviceSpecificInfo: ByteArray,
            matchFilter: List<ByteArray>
    ) {
        ...
    }
}, null)

Ява

SubscribeConfig config = new SubscribeConfig.Builder()
    .setServiceName("Aware_File_Share_Service_Name")
    .build();

awareSession.subscribe(config, new DiscoverySessionCallback() {
    @Override
    public void onSubscribeStarted(SubscribeDiscoverySession session) {
        ...
    }

    @Override
    public void onServiceDiscovered(PeerHandle peerHandle,
            byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
        ...
    }
}, null);

Если операция подписки выполнена успешно, система вызывает функцию обратного вызова onSubscribeStarted() в вашем приложении. Поскольку вы можете использовать аргумент SubscribeDiscoverySession в функции обратного вызова для связи с издателем после того, как ваше приложение его обнаружило, следует сохранить эту ссылку. Вы можете обновить сеанс подписки в любое время, вызвав функцию updateSubscribe() в сеансе обнаружения.

На этом этапе ваша подписка ожидает появления соответствующих издателей в зоне действия Wi-Fi. Когда это происходит, система выполняет метод обратного вызова onServiceDiscovered() . Вы можете использовать аргумент PeerHandle из этого метода обратного вызова для отправки сообщения или создания подключения к этому издателю.

Чтобы прекратить подписку на службу, вызовите DiscoverySession.close() . Сеансы обнаружения связаны с родительским сеансом WifiAwareSession . Если родительский сеанс закрыт, связанные с ним сеансы обнаружения также закрываются. Хотя отброшенные объекты также закрываются, система не гарантирует закрытие сеансов, выходящих за рамки области действия, поэтому мы рекомендуем явно вызывать методы close() .

Отправить сообщение

Чтобы отправить сообщение на другое устройство, вам понадобятся следующие объекты:

  • DiscoverySession . Этот объект позволяет вызывать sendMessage() . Ваше приложение получает DiscoverySession , публикуя службу или подписываясь на службу .

  • PeerHandle другого устройства для маршрутизации сообщения. Ваше приложение получает PeerHandle другого устройства одним из двух способов:

    • Ваше приложение публикует сервис и получает сообщение от подписчика. Оно получает PeerHandle подписчика из обратного вызова onMessageReceived() .
    • Ваше приложение подписывается на сервис. Затем, когда оно находит подходящего издателя, оно получает PeerHandle из обратного вызова onServiceDiscovered() .

Чтобы отправить сообщение, вызовите sendMessage() . После этого могут произойти следующие обратные вызовы:

  • Когда сообщение успешно получено одноранговым узлом, система вызывает обратный вызов onMessageSendSucceeded() в отправляющем приложении.
  • Когда одноранговый узел получает сообщение, система вызывает обратный вызов onMessageReceived() в принимающем приложении.

Хотя PeerHandle необходим для взаимодействия с одноранговыми узлами, не следует полагаться на него как на постоянный идентификатор одноранговых узлов. Приложение может использовать идентификаторы более высокого уровня, встроенные в саму службу обнаружения или в последующие сообщения. Вы можете встроить идентификатор в службу обнаружения с помощью метода setMatchFilter() или setServiceSpecificInfo() класса PublishConfig или SubscribeConfig . Метод setMatchFilter() влияет на обнаружение, тогда как метод setServiceSpecificInfo() не влияет на него.

Внедрение идентификатора в сообщение подразумевает изменение массива байтов сообщения для включения идентификатора (например, в качестве первых нескольких байтов).

Создать соединение

Wi-Fi Aware поддерживает клиент-серверное сетевое взаимодействие между двумя устройствами Wi-Fi Aware.

Чтобы настроить соединение клиент-сервер:

  1. Используйте обнаружение Wi-Fi Aware для публикации службы (на сервере) и подписки на службу (на клиенте).

  2. Как только подписчик обнаружит издателя, отправьте сообщение от подписчика издателю.

  3. Запустите ServerSocket на устройстве-издателе и установите или получите его порт:

    Котлин

    val ss = ServerSocket(0)
    val port = ss.localPort

    Ява

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Используйте ConnectivityManager для запроса сети Wi-Fi Aware на издателе с помощью WifiAwareNetworkSpecifier , указав сеанс обнаружения и PeerHandle подписчика, который вы получили из сообщения, переданного подписчиком:

    Котлин

    val networkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build()
    val myNetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build()
    val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            ...
        }
    
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            ...
        }
    
        override fun onLost(network: Network) {
            ...
        }
    }
    
    connMgr.requestNetwork(myNetworkRequest, callback);

    Ява

    NetworkSpecifier networkSpecifier = new WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build();
    NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build();
    ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            ...
        }
    
        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
            ...
        }
    
        @Override
        public void onLost(Network network) {
            ...
        }
    };
    
    ConnectivityManager connMgr.requestNetwork(myNetworkRequest, callback);
  5. Как только издатель запрашивает сеть, он должен отправить сообщение подписчику.

  6. После того, как подписчик получит сообщение от издателя, запросите у подписчика сеть Wi-Fi Aware тем же методом, что и у издателя. Не указывайте порт при создании NetworkSpecifier . Соответствующие методы обратного вызова вызываются при наличии, изменении или потере сетевого соединения.

  7. После вызова метода onAvailable() на подписчике становится доступен объект Network , с помощью которого можно открыть Socket для взаимодействия с ServerSocket на издателе, но для этого необходимо знать IPv6-адрес и порт ServerSocket . Эти данные можно получить из объекта NetworkCapabilities , предоставленного в обратном вызове onCapabilitiesChanged() :

    Котлин

    val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo
    val peerIpv6 = peerAwareInfo.peerIpv6Addr
    val peerPort = peerAwareInfo.port
    ...
    val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)

    Ява

    WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
    Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
    int peerPort = peerAwareInfo.getPort();
    ...
    Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
  8. Когда вы закончите сетевое подключение, вызовите unregisterNetworkCallback() .

Ранжирование сверстников и обнаружение с учетом местоположения

Устройство с возможностями определения местоположения Wi-Fi RTT может напрямую измерять расстояние до одноранговых устройств и использовать эту информацию для ограничения обнаружения службой Wi-Fi Aware.

API Wi-Fi RTT позволяет напрямую подключаться к одноранговому узлу Wi-Fi Aware, используя либо его MAC-адрес, либо его PeerHandle .

Обнаружение Wi-Fi Aware можно ограничить, чтобы оно обнаруживало только сервисы в пределах определённой геозоны. Например, можно настроить геозону, которая позволит обнаруживать устройства, публикующие сервис "Aware_File_Share_Service_Name" на расстоянии не ближе 3 метров (указывается как 3000 мм) и не дальше 10 метров (указывается как 10 000 мм).

Чтобы включить геозонирование, издатель и подписчик должны выполнить следующие действия:

  • Издатель должен включить ранжирование на опубликованной службе с помощью setRangingEnabled(true) .

    Если издатель не включает диапазон, то любые ограничения геозон, указанные подписчиком, игнорируются и выполняется обычное обнаружение, игнорирующее расстояние.

  • Абонент должен указать геозону, используя некоторую комбинацию setMinDistanceMm и setMaxDistanceMm .

    Для любого из этих значений неуказанное расстояние не подразумевает ограничения. Указание только максимального расстояния подразумевает минимальное расстояние, равное 0. Указание только минимального расстояния не подразумевает ограничения.

При обнаружении однорангового сервиса в пределах геозоны срабатывает обратный вызов onServiceDiscoveredWithinRange , который предоставляет измеренное расстояние до однорангового сервиса. При необходимости можно вызвать API Wi-Fi RTT для измерения расстояния в дальнейшем.