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:
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" />
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);
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:
PublishConfigokreśla nazwę usługi i inne właściwości konfiguracyjne, np. filtr dopasowania.DiscoverySessionCallbackokreś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:
-
SubscribeConfigokreśla nazwę usługi, którą chcesz subskrybować, oraz inne właściwości konfiguracyjne, np. filtr dopasowania. DiscoverySessionCallbackokreś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:
DiscoverySession. Ten obiekt umożliwia wywołanie metodysendMessage(). Aplikacja otrzymujeDiscoverySession, publikując usługę lub subskrybując usługę.PeerHandleinnego urządzenia, aby przekierować wiadomość. Aplikacja może uzyskaćPeerHandleinnego urządzenia na 2 sposoby:- Aplikacja publikuje usługę i otrzymuje wiadomość od subskrybenta.
Aplikacja otrzymuje
PeerHandlesubskrybenta z wywołania zwrotnegoonMessageReceived(). - Aplikacja subskrybuje usługę. Gdy wykryje pasującego
wydawcę, otrzyma jego
PeerHandlezonServiceDiscovered()wywołania zwrotnego.
- Aplikacja publikuje usługę i otrzymuje wiadomość od subskrybenta.
Aplikacja otrzymuje
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:
Użyj wykrywania Wi-Fi Aware, aby opublikować usługę (na serwerze) i subskrybować usługę (na kliencie).
Gdy subskrybent wykryje wydawcę, wyślij wiadomość od subskrybenta do wydawcy.
Uruchom
ServerSocketna 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();
Użyj
ConnectivityManageraby poprosić o sieć Wi-Fi Aware na wydawcy za pomocąWifiAwareNetworkSpecifier, określając sesję wykrywania iPeerHandlesubskrybenta, 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);
Gdy wydawca poprosi o sieć, powinien wysłać wiadomość do subskrybenta.
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
NetworkSpecifiernie określaj portu. Gdy połączenie sieciowe jest dostępne, zmienione lub utracone, wywoływane są odpowiednie metody wywołania zwrotnego.Gdy na subskrybencie zostanie wywołana metoda
onAvailable(), dostępny jest obiektNetwork, za pomocą którego możesz otworzyćSocket, aby komunikować się zServerSocketna wydawcy, ale musisz znać adres IPv6 i portServerSocket. Uzyskasz je z obiektuNetworkCapabilitiespodanego w wywołaniu zwrotnymonCapabilitiesChanged():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);
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ść.