Omówienie Wi-Fi Aware

Dzięki funkcjom Wi-Fi Aware urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym mogą wykrywać się i łączyć ze sobą bezpośrednio, bez żadnego innego rodzaju połączeń. Wi-Fi Aware jest też nazywane siecią Sąsiedztwa (NAN).

Sieć Wi-Fi Aware polega na tworzeniu klastrów z sąsiadującymi urządzeniami lub przez utworzenie nowego klastra, jeśli urządzenie jest pierwsze w danym obszarze. Ten sposób grupowania odnosi się do całego urządzenia i jest zarządzany przez usługę systemową Wi-Fi Aware. Aplikacje nie mają kontroli nad działaniem grupowania. Aplikacje używają interfejsów API Wi-Fi Aware, aby komunikować się z usługą systemową Wi-Fi Aware, która zarządza sprzętem Wi-Fi Aware na urządzeniu.

Interfejsy Wi-Fi Aware API umożliwiają aplikacjom wykonywanie tych operacji:

  • Wykrywanie innych urządzeń: interfejs API zawiera mechanizm znajdowania innych urządzeń w pobliżu. Ten proces rozpoczyna się, gdy jedno urządzenie publikuje co najmniej 1 wykrywalną usługę. Następnie, gdy urządzenie zasubskrybuje co najmniej jedną usługę i dołączy do zasięgu sieci Wi-Fi wydawcy, subskrybent otrzyma powiadomienie o wykryciu pasującego wydawcy. Gdy subskrybent znajdzie wydawcę, może wysłać krótką wiadomość lub nawiązać połączenie sieciowe z wykrytym urządzeniem. Urządzeniami mogą być równocześnie wydawców i subskrybentów.

  • Tworzenie połączenia sieciowego: gdy 2 urządzenia wykryją się nawzajem, mogą utworzyć dwukierunkowe połączenie sieciowe Wi-Fi Aware bez punktu dostępu.

Połączenia sieciowe Wi-Fi Aware obsługują większą przepustowość na dłuższych odległościach niż połączenia Bluetooth. Tego typu połączenia są przydatne w przypadku aplikacji, które udostępniają duże ilości danych między użytkownikami, np. do udostępniania zdjęć.

Ulepszenia Androida 13 (poziom interfejsu API 33)

Na urządzeniach z Androidem 13 (poziom interfejsu API 33) lub nowszym, które obsługują tryb komunikacji błyskawicznej, aplikacje mogą używać metod PublishConfig.Builder.setInstantCommunicationModeEnabled() i SubscribeConfig.Builder.setInstantCommunicationModeEnabled() do włączania i wyłączania trybu błyskawicznej komunikacji w sesji odkrywania wydawcy lub subskrybenta. Tryb komunikacji błyskawicznej przyspiesza wymianę wiadomości, wykrywanie usług i wszelkie ścieżki danych skonfigurowane w ramach sesji odkrywania wydawcy lub subskrybenta. Aby określić, czy urządzenie obsługuje tryb komunikacji błyskawicznej, użyj metody isInstantCommunicationModeSupported().

Ulepszenia Androida 12 (poziom interfejsu API 31)

Android 12 (poziom interfejsu API 31) wprowadza kilka ulepszeń w Wi-Fi Aware:

  • Na urządzeniach z Androidem 12 (poziom interfejsu API 31) lub nowszym możesz używać wywołania zwrotnego onServiceLost(), aby otrzymywać alerty, gdy aplikacja utraci wykrytą usługę w związku z jej zatrzymaniem lub wykroczeniem poza jej zasięg.
  • Konfiguracja ścieżek danych Aware Aware została uproszczona. Wcześniejsze wersje używały komunikatów L2 do podawania adresu MAC inicjatora, co spowodowało opóźnienie. Na urządzeniach z Androidem 12 lub nowszym respondent (serwer) można skonfigurować tak, aby akceptował dowolny peer – nie trzeba z góry znać adresu MAC inicjatora. Przyspiesza to uruchamianie ścieżki danych i umożliwia korzystanie z wielu połączeń punkt-punkt przy jednym żądaniu sieciowym.
  • Aplikacje działające na Androidzie 12 lub nowszym mogą używać metody WifiAwareManager.getAvailableAwareResources(), aby uzyskiwać informacje o liczbie obecnie dostępnych ścieżek danych, sesjach publikowania i sesjach subskrypcji. Dzięki temu aplikacja może określić, czy ma wystarczającą ilość dostępnych zasobów do realizacji jej funkcji.

Konfiguracja początkowa

Aby skonfigurować aplikację, tak aby wykorzystywała wykrywanie Wi-Fi Aware i korzystanie z sieci, wykonaj te czynności:

  1. W pliku manifestu aplikacji poproś o te uprawnienia:

    <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. Sprawdź, czy urządzenie obsługuje Wi-Fi Aware za pomocą interfejsu API PackageManager, jak pokazano poniżej:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
    

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
    
  3. Sprawdź, czy funkcja Wi-Fi Aware jest obecnie dostępna. Subskrypcja Wi-Fi Aware istnieje na urządzeniu, ale może być niedostępna, ponieważ użytkownik wyłączył Wi-Fi lub Lokalizację. W zależności od możliwości sprzętowych i oprogramowania niektóre urządzenia mogą nie obsługiwać Wi-Fi Aware, jeśli są używane Wi-Fi Direct, SoftAP lub tethering. Aby sprawdzić, czy funkcja Wi-Fi Aware jest obecnie dostępna, zadzwoń pod numer isAvailable().

    Dostępność Wi-Fi Aware może się zmienić w każdej chwili. Aby Twoja aplikacja mogła otrzymywać dane ACTION_WIFI_AWARE_STATE_CHANGED, powinna ona zarejestrować element BroadcastReceiver, który jest wysyłany przy każdej zmianie dostępności. Gdy aplikacja otrzyma intencję transmitowania, powinna odrzucić wszystkie istniejące sesje (przy założeniu, że usługa Wi-Fi Aware została zakłócona), a następnie sprawdzić bieżący stan dostępności i odpowiednio dostosować jej działanie. Na przykład:

    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ęcej informacji znajdziesz w artykule Komunikaty.

Uzyskiwanie sesji

Aby zacząć korzystać z Wi-Fi Aware, aplikacja musi uzyskać WifiAwareSession, nawiązując połączenie z attach(). Ta metoda:

  • Włącza sprzęt Wi-Fi Aware.
  • Dołącza do klastra Wi-Fi Aware lub tworzy go.
  • Tworzy sesję Wi-Fi Aware z unikalną przestrzenią nazw, która będzie działać jako kontener dla wszystkich utworzonych w niej sesji wykrywania.

Jeśli aplikacja się przyłączy, system wykonuje wywołanie zwrotne onAttached(). To wywołanie zwrotne udostępnia obiekt WifiAwareSession, którego aplikacja powinna używać na potrzeby wszystkich kolejnych operacji sesji. Aplikacja może użyć sesji, by opublikować usługę lub ją zasubskrybować.

Aplikacja powinna wywołać attach() tylko raz. Jeśli aplikacja wielokrotnie wywołuje funkcję attach(), dla każdego wywołania otrzymuje inną sesję z własną przestrzenią nazw. Może to być przydatne w złożonych scenariuszach, ale generalnie nie powinno się tego robić.

Publikowanie usługi

Aby usługa była wykrywalna, wywołaj metodę publish(), która przyjmuje te parametry:

  • PublishConfig określa nazwę usługi i inne właściwości konfiguracji, takie jak filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają być wykonywane w przypadku wystąpienia zdarzeń, na przykład gdy subskrybent otrzymuje wiadomość.

Oto przykład:

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

Jeśli publikacja się uda, wywoływana jest metoda wywołania zwrotnego onPublishStarted().

Po opublikowaniu urządzenia z odpowiednią aplikacją do subskrypcji zostaną połączone z siecią Wi-Fi urządzenia wydawniczego, a subskrybenci odkryją tę usługę. Gdy subskrybent odkryje wydawcę, wydawca nie otrzyma powiadomienia. Jeśli jednak wyśle do niego wiadomość, wydawca otrzyma powiadomienie. W takim przypadku wywoływana jest metoda wywołania zwrotnego onMessageReceived(). Za pomocą argumentu PeerHandle z tej metody możesz wysłać wiadomość z powrotem do subskrybenta lub utworzyć z nim połączenie.

Aby zakończyć publikowanie usługi, wywołaj DiscoverySession.close(). Sesje odkrywania są powiązane z elementem nadrzędnym WifiAwareSession. Jeśli sesja nadrzędna jest zamknięta, powiązane z nią sesje wykrywania również są zamykane. Chociaż odrzucone obiekty też są zamykane, system nie gwarantuje zamknięcia sesji spoza zakresu, dlatego zalecamy jawne wywoływanie metod close().

Subskrybowanie usługi

Aby zasubskrybować usługę, wywołaj metodę subscribe(), która przyjmuje te parametry:

  • SubscribeConfig określa nazwę usługi, którą chcesz zasubskrybować, i inne właściwości konfiguracji, takie jak filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają być wykonywane w przypadku wystąpienia zdarzeń, na przykład w przypadku wykrycia wydawcy.

Oto przykład:

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

Jeśli operacja subskrypcji się powiedzie, system wywoła w Twojej aplikacji wywołanie zwrotne onSubscribeStarted(). Możesz używać argumentu SubscribeDiscoverySession w wywołaniu zwrotnym do komunikowania się z wydawcą po wykryciu go przez aplikację, dlatego musisz zapisać to odwołanie. Sesję subskrypcji możesz zaktualizować w dowolnym momencie, wywołując w sesji wykrywania updateSubscribe().

Na tym etapie Twoja subskrypcja czeka, aż pasujący wydawca zbliży się do zasięgu sieci Wi-Fi. W takim przypadku system wykonuje metodę wywołania zwrotnego onServiceDiscovered(). Za pomocą argumentu PeerHandle z tego wywołania zwrotnego możesz wysłać wiadomość lub utworzyć połączenie z wydawcą.

Aby zrezygnować z subskrypcji usługi, zadzwoń pod numer DiscoverySession.close(). Sesje odkrywania są powiązane z elementem nadrzędnym WifiAwareSession. Jeśli sesja nadrzędna jest zamknięta, powiązane z nią sesje wykrywania również są zamykane. Chociaż odrzucone obiekty też są zamykane, system nie gwarantuje zamknięcia sesji spoza zakresu, dlatego zalecamy jawne wywoływanie metod close().

Wysyłanie wiadomości

Aby wysłać wiadomość na inne urządzenie, potrzebne będą te obiekty:

Aby wysłać wiadomość, zadzwoń pod numer sendMessage(). Mogą wystąpić te wywołania zwrotne:

  • Po pomyślnym odebraniu wiadomości przez peera system wywołuje wywołanie zwrotne onMessageSendSucceeded() w aplikacji wysyłającej.
  • Gdy peer otrzyma wiadomość, system wywołuje wywołanie zwrotne onMessageReceived() w aplikacji odbierającej.

Chociaż interfejs PeerHandle jest wymagany do komunikacji z elementami równorzędnymi, nie należy polegać na nim jako stały identyfikator elementu równorzędnego. Aplikacja może używać identyfikatorów wyższego poziomu osadzonych w samej usłudze wykrywania lub w kolejnych wiadomościach. Identyfikator możesz umieścić w usłudze wykrywania za pomocą metody setMatchFilter() lub setServiceSpecificInfo() PublishConfig albo SubscribeConfig. Metoda setMatchFilter() wpływa na wykrywanie, a metoda setServiceSpecificInfo() nie wpływa na wykrywanie.

Umieszczenie identyfikatora w wiadomości wymaga modyfikacji tablicy bajtów wiadomości w taki sposób, aby zawierała identyfikator (na przykład jako pierwsze bajty).

Utwórz połączenie

Wi-Fi Aware obsługuje sieć klient-serwer między dwoma urządzeniami Wi-Fi Aware.

Aby skonfigurować połączenie klient-serwer:

  1. Użyj wykrywania Wi-Fi Aware, aby opublikować usługę (na serwerze) i zasubskrybować usługę (na kliencie).

  2. Gdy subskrybent znajdzie wydawcę, wyślij do wydawcy wiadomość od subskrybenta.

  3. Uruchom ServerSocket na urządzeniu wydawcy i ustaw lub uzyskaj port:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
    
  4. Użyj obiektu ConnectivityManager, aby poprosić o dostęp do sieci Wi-Fi Aware u wydawcy za pomocą komponentu WifiAwareNetworkSpecifier, określając sesję wykrywania i PeerHandle subskrybenta uzyskane z wiadomości przesłanej przez subskrybenta:

    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);
    
  5. Gdy wydawca poprosi o dostęp do sieci, powinien wysłać wiadomość do subskrybenta.

  6. Gdy subskrybent otrzyma wiadomość od wydawcy, poproś subskrybenta o połączenie z siecią Wi-Fi Aware, korzystając z tej samej metody co u wydawcy. Podczas tworzenia NetworkSpecifier nie określaj portu. Odpowiednie metody wywołania zwrotnego są wywoływane, gdy połączenie sieciowe jest dostępne, zmienione lub utracone.

  7. Po wywołaniu metody onAvailable() subskrybenta dostępny jest obiekt Network, za pomocą którego możesz otworzyć Socket, aby komunikować się z ServerSocket u wydawcy. Musisz jednak znać adres IPv6 i port IPv6 ServerSocket. Te informacje pochodzą z obiektu NetworkCapabilities podanego w wywołaniu zwrotnym 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);
    
  8. Po zakończeniu połączenia sieciowego wywołaj unregisterNetworkCallback().

Różne grupy rówieśników i odkrywanie z uwzględnieniem lokalizacji

Urządzenie, które obsługuje lokalizację RTT przez Wi-Fi, może bezpośrednio mierzyć odległość od innych osób i używać tych informacji do ograniczania wykrywania usług Wi-Fi Aware.

Interfejs Wi-Fi RTT API umożliwia bezpośrednie określanie zakresu do połączenia równorzędnego Aware Aware Wi-Fi za pomocą jego adresu MAC lub PeerHandle.

Wykrywanie Wi-Fi Aware można ograniczyć do wykrywania usług tylko w ramach określonego geofencingu. Można na przykład skonfigurować geofence, która umożliwia wykrywanie urządzenia publikującego usługę "Aware_File_Share_Service_Name" w odległości maksymalnie 3 metrów (3000 mm) i 10 metrów (10 000 mm).

Aby włączyć geofencing, zarówno wydawca, jak i subskrybent muszą wykonać działanie:

  • Wydawca musi włączyć zakres w opublikowanej usłudze za pomocą funkcji setRangingEnabled(true).

    Jeśli wydawca nie włączy zakresu, wszystkie ograniczenia geofencingu określone przez subskrybenta są ignorowane, a proces wykrywania jest wykrywany w zwykły sposób, z pominięciem odległości.

  • Subskrybent musi określić geofence za pomocą kombinacji parametrów setMinOdległośćMm i setMaxOdległośćMm.

    W przypadku każdej z tych wartości nieokreślony dystans oznacza brak limitu. Samo określenie maksymalnej odległości zakłada minimalną odległość równą 0. Samo określenie minimalnej odległości nie oznacza żadnej maksymalnej odległości.

Gdy usługa równorzędna zostanie wykryta w geofence, wywoływane jest wywołanie zwrotne onServiceDiscoveredWithinRange, które podaje zmierzoną odległość do połączenia z peerem. W razie potrzeby interfejs bezpośredniego Wi-Fi RTT API można później wywołać, aby zmierzyć odległość.