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

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

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

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

  • Обнаружение других устройств. 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 Aware была упрощена. Более ранние версии использовали обмен сообщениями L2 для предоставления MAC-адреса инициатора, что приводило к задержке. На устройствах под управлением 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 с помощью PackageManager API, как показано ниже:

    Котлин

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