Omówienie Wi-Fi Aware

Funkcje Wi-Fi Aware umożliwiają urządzeniom z Androidem 8.0 (poziom interfejsu API 26) i nowszym wykrywanie się nawzajem i łączenie się bezpośrednio bez żadnego innego typu łączności. Wi-Fi Aware jest też znane jako Neighbor Awareness Networking (NAN).

Sieć Wi-Fi Aware działa przez tworzenie klastrów z sąsiednimi urządzeniami lub przez utworzenie nowego klastra, jeśli urządzenie jest pierwszym w danym obszarze. To grupowanie dotyczy całego urządzenia i jest zarządzane przez usługę systemową Wi-Fi Aware. Aplikacje nie mają kontroli nad grupowaniem. Aplikacje używają interfejsów API Wi-Fi Aware do komunikowania się z usługą systemową Wi-Fi Aware, która zarządza sprzętem Wi-Fi Aware na urządzeniu.

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

  • Wykrywanie innych urządzeń: interfejs API ma mechanizm wyszukiwania innych urządzeń w pobliżu. Proces rozpoczyna się, gdy jedno urządzenie publikuje co najmniej jedną usługę, którą można wykryć. Następnie, gdy urządzenie subskrybuje co najmniej jedną usługę i znajdzie się w zasięgu Wi-Fi wydawcy, subskrybent otrzymuje powiadomienie o wykryciu pasującego wydawcy. Gdy subskrybent wykryje wydawcę, może wysłać krótką wiadomość lub nawiązać połączenie sieciowe z wykrytym urządzeniem. Urządzenia mogą być jednocześnie wydawcami i subskrybentami.

  • Tworzenie połączenia sieciowego: po wykryciu się nawzajem 2 urządzenia 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 większych odległościach niż Bluetooth połączenia. Ten typ połączeń jest przydatny w przypadku aplikacji, które udostępniają duże ilości danych między użytkownikami, np. aplikacji do udostępniania zdjęć.

Ulepszenia w Androidzie 13 (poziom interfejsu API 33)

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

Ulepszenia w Androidzie 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żyć wywołania zwrotnego onServiceLost() , aby otrzymywać powiadomienia, gdy aplikacja utraci wykrytą usługę z powodu jej zatrzymania lub wyjścia poza zasięg.
  • Uproszczono konfigurację ścieżek danych Wi-Fi Aware. Wcześniejsze wersje używały wiadomości L2 do podawania adresu MAC inicjatora, co powodowało opóźnienia. Na urządzeniach z Androidem 12 i nowszym można skonfigurować odpowiadający (serwer) tak, aby akceptował dowolnego równorzędnego – nie musi on z góry znać adresu MAC inicjatora. Przyspiesza to uruchamianie ścieżki danych i umożliwia tworzenie wielu połączeń typu punkt-punkt za pomocą tylko jednego żądania sieciowego.
  • Aplikacje działające na Androidzie 12 lub nowszym mogą używać metody WifiAwareManager.getAvailableAwareResources() do uzyskiwania liczby aktualnie dostępnych ścieżek danych, sesji publikowania, i sesji subskrypcji. Dzięki temu aplikacja może sprawdzić, czy ma wystarczającą ilość zasobów do wykonania żądanej funkcji.

Konfiguracja początkowa

Aby skonfigurować aplikację do korzystania z wykrywania i sieci Wi-Fi Aware, wykonaj te czynności:

  1. Poproś o te uprawnienia w pliku manifestu aplikacji:

    <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 Wi-Fi Aware jest obecnie dostępne. Wi-Fi Aware może być dostępne na urządzeniu, ale może być obecnie niedostępne, ponieważ użytkownik wyłączył Wi-Fi lub lokalizację. W zależności od możliwości sprzętowych i oprogramowania układowego niektóre urządzenia mogą nie obsługiwać Wi-Fi Aware, jeśli używane jest Wi-Fi Direct, SoftAP lub tethering. Aby sprawdzić, czy Wi-Fi Aware jest obecnie dostępne, wywołaj metodę isAvailable().

    Dostępność Wi-Fi Aware może się zmienić w dowolnym momencie. Aplikacja powinna zarejestrować BroadcastReceiver, aby otrzymywać ACTION_WIFI_AWARE_STATE_CHANGED, który jest wysyłany za każdym razem, gdy zmienia się dostępność. Gdy aplikacja otrzyma intencję transmisji, powinna odrzucić wszystkie istniejące sesje (przyjąć, że usługa Wi-Fi Aware została przerwana), a następnie sprawdzić bieżący stan dostępności i odpowiednio dostosować swoje 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 Transmisje.

Uzyskiwanie sesji

Aby zacząć korzystać z Wi-Fi Aware, aplikacja musi uzyskać WifiAwareSession, wywołując metodę attach(). Ta metoda wykonuje te czynności:

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

Jeśli aplikacja zostanie pomyślnie dołączona, system wykona wywołanie zwrotne onAttached(). To wywołanie zwrotne udostępnia obiekt WifiAwareSession, którego aplikacja powinna używać do wszystkich dalszych operacji sesji. Aplikacja może używać sesji do publikowania usługi lub subskrybowania usługi.

Aplikacja powinna wywoływać metodę attach() tylko raz. Jeśli aplikacja wywoła metodę attach() kilka razy, otrzyma inną sesję dla każdego wywołania, a każda z nich będzie miała własną przestrzeń nazw. Może to być przydatne w złożonych scenariuszach, ale na ogół należy tego unikać.

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 konfiguracyjne, np. filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają być wykonywane w przypadku wystąpienia zdarzeń, np. gdy subskrybent otrzyma 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ę powiedzie, zostanie wywołana metoda wywołania zwrotnego onPublishStarted().

Po publikacji, gdy urządzenia z pasującymi aplikacjami subskrybenta znajdą się w zasięgu Wi-Fi urządzenia publikującego, subskrybenci wykryją usługę. Gdy subskrybent wykryje wydawcę, wydawca nie otrzyma powiadomienia. Jeśli jednak subskrybent wyśle wiadomość do wydawcy, wydawca otrzyma powiadomienie. W takim przypadku zostanie wywołana metoda wywołania zwrotnego onMessageReceived(). Za pomocą argumentu PeerHandle z tej metody możesz wysłać wiadomość do subskrybenta lub utworzyć z nim połączenie.

Aby zatrzymać publikowanie usługi, wywołaj metodę DiscoverySession.close(). Sesje wykrywania są powiązane z sesją nadrzędną WifiAwareSession. Jeśli sesja nadrzędna zostanie zamknięta, powiązane z nią sesje wykrywania również zostaną zamknięte. Odrzucone obiekty są również zamykane, ale system nie gwarantuje, kiedy sesje poza zakresem zostaną zamknięte, dlatego zalecamy jawne wywoływanie metod close().

Subskrybowanie usługi

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

  • SubscribeConfig określa nazwę usługi, którą chcesz subskrybować, oraz inne właściwości konfiguracyjne, np. filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają być wykonywane w przypadku wystąpienia zdarzeń, np. gdy zostanie wykryty wydawca.

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(). Ponieważ możesz użyć argumentu SubscribeDiscoverySession w wywołaniu zwrotnym do komunikowania się z wydawcą po wykryciu go przez aplikację, zapisz to odwołanie. Sesję subskrypcji możesz zaktualizować w dowolnym momencie, wywołując metodę updateSubscribe() w sesji wykrywania.

W tym momencie subskrypcja czeka, aż pasujący wydawcy znajdą się w zasięgu Wi-Fi. Gdy tak się stanie, system wykona metodę wywołania zwrotnego onServiceDiscovered(). Za pomocą argumentu PeerHandle z tego wywołania zwrotnego możesz wysłać wiadomość lub utworzyć połączenie z tym wydawcą.

Aby zatrzymać subskrybowanie usługi, wywołaj metodę DiscoverySession.close(). Sesje wykrywania są powiązane z sesją nadrzędną WifiAwareSession. Jeśli sesja nadrzędna zostanie zamknięta, powiązane z nią sesje wykrywania również zostaną zamknięte. Odrzucone obiekty są również zamykane, ale system nie gwarantuje, kiedy sesje poza zakresem zostaną zamknięte, dlatego zalecamy jawne wywoływanie metod close().

Wysyłanie wiadomości

Aby wysłać wiadomość do innego urządzenia, potrzebujesz tych obiektów:

Aby wysłać wiadomość, wywołaj metodę sendMessage(). Następnie mogą wystąpić te wywołania zwrotne:

  • Gdy wiadomość zostanie pomyślnie odebrana przez równorzędnego, system wywoła wywołanie zwrotne onMessageSendSucceeded() w wysyłającej aplikacji.
  • Gdy równorzędny otrzyma wiadomość, system wywoła wywołanie zwrotne onMessageReceived() w odbierającej aplikacji.

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

Osadzenie identyfikatora w wiadomości oznacza zmodyfikowanie tablicy bajtów wiadomości w taki sposób, aby zawierała identyfikator (np. jako pierwsze kilka bajtów).

Tworzenie połączenia

Wi-Fi Aware obsługuje sieci klient-serwer między 2 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 subskrybować usługę (na kliencie).

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

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

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Użyj ConnectivityManager aby poprosić o sieć Wi-Fi Aware na wydawcy za pomocą WifiAwareNetworkSpecifier, określając sesję wykrywania i PeerHandle subskrybenta, który został uzyskany 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 sieć, powinien wysłać wiadomość do subskrybenta.

  6. Gdy subskrybent otrzyma wiadomość od wydawcy, poproś o sieć Wi-Fi Aware na subskrybencie, używając tej samej metody co na wydawcy. Podczas tworzenia NetworkSpecifier nie określaj portu. Gdy połączenie sieciowe jest dostępne, zmienione lub utracone, wywoływane są odpowiednie metody wywołania zwrotnego.

  7. Gdy na subskrybencie zostanie wywołana metoda onAvailable(), dostępny jest obiekt Network, za pomocą którego możesz otworzyć Socket, aby komunikować się z ServerSocket na wydawcy, ale musisz znać adres IPv6 i port ServerSocket. Uzyskasz je z obiektu NetworkCapabilitiespodanego 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. Gdy skończysz korzystać z połączenia sieciowego, wywołaj metodę unregisterNetworkCallback().

Określanie zasięgu równorzędnych i wykrywanie z uwzględnieniem lokalizacji

Urządzenie z funkcjami lokalizacji Wi-Fi RTT może bezpośrednio mierzyć odległość do równorzędnych i używać tych informacji do ograniczania wykrywania usług Wi-Fi Aware.

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

Wykrywanie Wi-Fi Aware można ograniczyć do wykrywania tylko usług w określonym obszarze. Możesz na przykład skonfigurować obszar, który umożliwia wykrywanie urządzenia publikującego usługę "Aware_File_Share_Service_Name" znajdującego się w odległości nie mniejszej niż 3 metry (określonej jako 3000 mm) i nie większej niż 10 metrów (określonej jako 10 000 mm).

Aby włączyć geofencing, wydawca i subskrybent muszą podjąć działania:

  • Wydawca musi włączyć określanie zasięgu w opublikowanej usłudze za pomocą metody setRangingEnabled(true).

    Jeśli wydawca nie włączy określania zasięgu, wszystkie ograniczenia obszaru określone przez subskrybenta zostaną zignorowane i zostanie przeprowadzone normalne wykrywanie z pominięciem odległości.

  • Subskrybent musi określić obszar, używając kombinacji metod setMinDistanceMm i setMaxDistanceMm.

    W przypadku obu wartości nieokreślona odległość oznacza brak limitu. Określenie tylko maksymalnej odległości oznacza minimalną odległość 0. Określenie tylko minimalnej odległości oznacza brak maksymalnej.

Gdy usługa równorzędna zostanie wykryta w obszarze, zostanie wywołane wywołanie zwrotne onServiceDiscoveredWithinRange, które podaje zmierzoną odległość do równorzędnego. W razie potrzeby można później wywołać bezpośredni interfejs API Wi-Fi RTT, aby zmierzyć odległość.