Возможности 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 была упрощена. Более ранние версии использовали сообщения L2 для предоставления MAC-адреса инициатора, что приводило к задержке. На устройствах под управлением 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
, как показано ниже:Котлин
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Ява
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 была нарушена), затем проверить текущее состояние доступности и соответствующим образом скорректировать свое поведение. Например:Котлин
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.
Чтобы настроить соединение клиент-сервер:
Используйте обнаружение Wi-Fi Aware для публикации службы (на сервере) и подписки на службу (на клиенте).
Как только подписчик обнаружит издателя, отправьте сообщение от подписчика издателю.
Запустите
ServerSocket
на устройстве-издателе и установите или получите его порт:Котлин
val ss = ServerSocket(0) val port = ss.localPort
Ява
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
Используйте
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);
Как только издатель запрашивает сеть, он должен отправить сообщение подписчику.
После того, как подписчик получит сообщение от издателя, запросите сеть Wi-Fi Aware на подписчике, используя тот же метод, что и на издателе. Не указывайте порт при создании
NetworkSpecifier
. Соответствующие методы обратного вызова вызываются, когда сетевое соединение доступно, изменено или потеряно.После вызова метода
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);
Закончив сетевое подключение, вызовите
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 по мере необходимости для измерения расстояния в более позднее время.