Обзор поддержки 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, позволяют приложениям выполнять следующие операции:

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

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

Соединения сети с поддержкой Wi-Fi обеспечивают более высокую пропускную способность на больших расстояниях, чем соединения 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 была упрощена. В более ранних версиях для передачи 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)

    Java

    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)

    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.
  • Присоединяется к кластеру 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) {
        ...
    }
})

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 определяет действия, которые должны выполняться при возникновении событий, например, при обнаружении издателя.

Вот пример:

Котлин

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.

Для установления соединения между клиентом и сервером:

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

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

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

    Котлин

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

    Java

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

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

    Java

    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 метров (указано как 10000 мм).

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

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

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

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

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

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