Функции 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, выполните следующие действия:
Запросите следующие разрешения в манифесте приложения:
<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" />
Проверьте, поддерживает ли устройство Wi-Fi Aware, с помощью API
PackageManager, как показано ниже.Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
Проверьте, доступна ли сейчас функция 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 была прервана), затем проверить текущее состояние доступности и соответствующим образом скорректировать свое поведение. Пример:Kotlin
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)
Java
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указывает, какие действия нужно выполнить при возникновении событий, например когда подписчик получает сообщение.
Рассмотрим пример.
Kotlin
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) { ... } })
Java
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указывает действия, которые нужно выполнить при возникновении событий, например при обнаружении издателя.
Рассмотрим пример.
Kotlin
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)
Java
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.
Чтобы настроить подключение клиента к серверу, выполните следующие действия:
Используйте обнаружение Wi-Fi Aware, чтобы опубликовать сервис (на сервере) и подписаться на сервис (на клиенте).
После того как подписчик обнаружит издателя, отправьте сообщение от подписчика издателю.
Запустите
ServerSocketна устройстве издателя и задайте или получите его порт:Kotlin
val ss = ServerSocket(0) val port = ss.localPort
Java
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
Используйте
ConnectivityManager, чтобы запросить сеть Wi-Fi Aware у издателя с помощьюWifiAwareNetworkSpecifier, указав сеанс обнаружения иPeerHandleподписчика, полученный из сообщения, переданного подписчиком:Kotlin
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);
Java
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);
После того как издатель запросит сеть, он должен отправить сообщение подписчику.
Когда подписчик получит сообщение от издателя, запросите сеть Wi-Fi Aware на подписчике, используя тот же метод, что и на издателе. При создании
NetworkSpecifierне указывайте порт. При наличии, изменении или потере сетевого подключения вызываются соответствующие методы обратного вызова.После вызова метода
onAvailable()для подписчика становится доступен объектNetwork, с помощью которого можно открытьSocketдля связи сServerSocketиздателя. Однако для этого нужно знать IPv6-адрес и портServerSocket. Их можно получить из объектаNetworkCapabilities, который предоставляется в обратном вызовеonCapabilitiesChanged():Kotlin
val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo val peerIpv6 = peerAwareInfo.peerIpv6Addr val peerPort = peerAwareInfo.port ... val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)
Java
WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo(); Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr(); int peerPort = peerAwareInfo.getPort(); ... Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
Когда сетевое подключение больше не нужно, вызовите
unregisterNetworkCallback().
Определение расстояния до других устройств и обнаружение с учетом местоположения
Устройство с поддержкой определения местоположения по Wi-Fi RTT может напрямую измерять расстояние до других устройств и использовать эту информацию для ограничения обнаружения сервисов Wi-Fi Aware.
Wi-Fi RTT API позволяет напрямую определять расстояние до однорангового узла 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, чтобы измерить расстояние позже.