Funkcje Wi-Fi Aware umożliwiają urządzeniom z Androidem w wersji 8.0 (poziom interfejsu API 26) i wyższej wykrywanie i łączenie się bezpośrednio ze sobą bez korzystania z innych typów połączeń. Wi-Fi Aware jest też nazywany Neighbor Awareness Networking (NAN).
Sieć Wi-Fi Aware działa poprzez tworzenie klastrów z sąsiednimi urządzeniami lub tworzenie nowego klastra, jeśli urządzenie jest pierwsze w danym obszarze. To zachowanie dotyczy całego urządzenia i jest zarządzane przez usługę systemową Wi-Fi Aware. Aplikacje nie mają kontroli nad tym zachowaniem. Aplikacje korzystają z 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 API Wi-Fi Aware umożliwiają aplikacjom wykonywanie tych operacji:
Wykrywanie innych urządzeń: interfejs API zawiera mechanizm umożliwiający wykrywanie innych urządzeń w pobliżu. Proces rozpoczyna się, gdy jedno urządzenie opublikuje co najmniej jedną usługę możliwą do znalezienia. Gdy urządzenie zasubskrybuje co najmniej 1 usługę i wejdzie w zakres sieci Wi-Fi wydawcy, subskrybent otrzyma powiadomienie o wykryciu odpowiedniego 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ą jednocześnie pełnić rolę 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ą wyższe przepustowości na większych 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. aplikacji do udostępniania zdjęć.
Ulepszenia w Androidzie 13 (poziom 33 interfejsu API)
Na urządzeniach z Androidem 13 (poziom interfejsu API 33) lub nowszym, które obsługują tryb komunikacji natychmiastowej, aplikacje mogą używać metod PublishConfig.Builder.setInstantCommunicationModeEnabled()
i SubscribeConfig.Builder.setInstantCommunicationModeEnabled()
, aby włączyć lub wyłączyć tryb komunikacji natychmiastowej w sesji odkrywania dla wydawcy lub subskrybenta. Tryb komunikacji natychmiastowej przyspiesza wymianę wiadomości, wykrywanie usług i jakiekolwiek ścieżki danych skonfigurowane w ramach sesji odkrywania wydawcy lub subskrybenta. Aby sprawdzić, czy urządzenie obsługuje tryb komunikacji natychmiastowej, użyj metody isInstantCommunicationModeSupported()
.
Ulepszenia w Androidzie 12 (poziom 31 interfejsu API)
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. - Uproszczona została konfiguracja ścieżek danych Wi-Fi Aware. Wcześniejsze wersje używały wiadomości L2 do przekazywania adresu MAC inicjatora, co powodowało opóźnienia. Na urządzeniach z Androidem 12 lub nowszym można skonfigurować serwer (odpowiadający) tak, aby akceptował dowolnego peera, czyli nie musiał znać z góry adresu MAC inicjatora. Przyspiesza to konfigurowanie ścieżki danych i umożliwia tworzenie wielu połączeń punkt-punkt za pomocą jednego żądania sieciowego.
- Aplikacje działające na Androidzie 12 lub nowszym mogą używać metody
WifiAwareManager.getAvailableAwareResources()
do uzyskiwania liczby obecnie dostępnych ścieżek danych, publikowania sesji i subskrybowania sesji. Dzięki temu aplikacja może określić, czy ma wystarczająco dużo dostępnych zasobów do wykonania żądanej funkcji.
Konfiguracja początkowa
Aby skonfigurować aplikację, tak aby wykorzystywała wykrywanie Wi-Fi Aware i korzystanie z sieci, wykonaj te czynności:
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" />
Sprawdź, czy urządzenie obsługuje Wi-Fi Aware, korzystając z 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 nie być obecnie dostępne, 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 w trybie Wi-Fi Direct, SoftAP lub tetheringu. Aby sprawdzić, czy funkcja Wi-Fi Aware jest obecnie dostępna, zadzwoń pod numer
isAvailable()
.Dostępność Wi-Fi Aware może się zmieniać w dowolnym momencie. Aplikacja powinna zarejestrować
BroadcastReceiver
, aby otrzymywaćACTION_WIFI_AWARE_STATE_CHANGED
, które jest wysyłane, gdy zmienia się dostępność. Gdy aplikacja otrzyma intencję rozgłoszenia, powinna odrzucić wszystkie istniejące sesje (zakładając, że usługa Wi-Fi Aware została przerwana), a następnie sprawdzić aktualny 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ącattach()
. Ta metoda:
- Włącza sprzęt Wi-Fi Aware.
- Dołącza do klastra Wi-Fi Aware lub tworzy taki klaster.
- Tworzy sesję Wi-Fi Aware z unikalnym przestrzenią nazw, która działa jako kontener dla wszystkich sesji wyszukiwania utworzonych w jej ramach.
Jeśli aplikacja zostanie pomyślnie dołączona, system wykona wywołanie onAttached()
.
Ten wywołanie zwrotne udostępnia obiekt WifiAwareSession
, którego aplikacja powinna używać do wszystkich dalszych operacji dotyczących sesji. Aplikacja może użyć sesji do publikowania usługi lub subskrybowania usługi.
Aplikacja powinna wywołać funkcję attach()
tylko raz. Jeśli Twoja aplikacja wywołuje attach()
kilka razy, w przypadku każdego wywołania otrzymuje inną sesję z własną przestrzenią nazw. Może to być przydatne w skomplikowanych 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 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ę powiedzie, wywołana zostanie metoda wywołania zwrotnego onPublishStarted()
.
Po opublikowaniu, gdy urządzenia z odpowiednimi aplikacjami subskrybentów znajdą się w zasięgu sieci Wi-Fi urządzenia publikującego, subskrybenci mogą odkryć usługę. Gdy subskrybent odkryje wydawcę, wydawca nie otrzyma powiadomienia. Jeśli jednak wyśle do niego wiadomość, wydawca otrzyma powiadomienie. Wtedy wywoływana jest metoda wywołania zwrotnego onMessageReceived()
. Argument PeerHandle
tej metody umożliwia wysłanie wiadomości do subskrybenta lub utworzenie połączenia z nim.
Aby zatrzymać publikowanie usługi, zadzwoń pod numer
DiscoverySession.close()
.
Sesje wyszukiwania są powiązane z elementem nadrzędnym WifiAwareSession
. Jeśli sesja nadrzędna zostanie zamknięta, powiązane z nią sesje odkrywania również zostaną zamknięte. 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 aplikacji funkcję onSubscribeStarted()
. Ponieważ w funkcjach wywołania możesz używać argumentu SubscribeDiscoverySession
do komunikacji z wydawcą po wykryciu przez aplikację wydawcy, zapisz to odwołanie. Sesję subskrypcji możesz zaktualizować w dowolnym momencie, wywołując w sesji wykrywania updateSubscribe()
.
W tym momencie Twoja subskrypcja czeka na to, aż pasujący wydawcy znajdą się w zasięgu Wi-Fi. W takim przypadku system wykonuje metodę wywołania zwrotnego onServiceDiscovered()
. Argument PeerHandle
z tego wywołania zwrotnego umożliwia wysłanie wiadomości lub utworzenie połączenia z tym wydawcą.
Aby zrezygnować z subskrypcji usługi, zadzwoń pod numer DiscoverySession.close()
.
Sesje wyszukiwania są powiązane z elementem nadrzędnym WifiAwareSession
. Jeśli sesja nadrzędna zostanie zamknięta, powiązane z nią sesje odkrywania również zostaną zamknięte. Porzucone obiekty są zamknięte, ale system nie gwarantuje, że sesje poza zakresem są zamknięte, dlatego zalecamy wywołanie metody close()
.
Wyślij wiadomość
Aby wysłać wiadomość na inne urządzenie, potrzebne będą te obiekty:
DiscoverySession
. Ten obiekt umożliwia wywołanie funkcjisendMessage()
. Aplikacja otrzymujeDiscoverySession
, gdy publikuje usługę lub subskrybuje usługę.PeerHandle
drugiego urządzenia, aby przekierować wiadomość. Aplikacja uzyskuje dostęp do danych z innego urządzeniaPeerHandle
w jeden z 2 sposobów:- Twoja aplikacja publikuje usługę i otrzymuje wiadomość od subskrybenta.
Aplikacja uzyskuje dane subskrybenta
PeerHandle
z pomocy wywołania zwrotnegoonMessageReceived()
. - Twoja aplikacja subskrybuje usługę. Gdy wykryje pasującego wydawcę, aplikacja otrzyma jego
PeerHandle
z powrotem wywołania funkcjionServiceDiscovered()
.
- Twoja aplikacja publikuje usługę i otrzymuje wiadomość od subskrybenta.
Aplikacja uzyskuje dane subskrybenta
Aby wysłać wiadomość, zadzwoń do sendMessage()
. W tym przypadku mogą wystąpić następujące wywołania zwrotne:
- Gdy wiadomość zostanie pomyślnie odebrana przez drugą stronę, system wywoła
onMessageSendSucceeded()
wywołanie zwrotne w aplikacji wysyłającej. - Gdy peer otrzyma wiadomość, system wywoła funkcję
onMessageReceived()
callback w aplikacji odbiorcy.
Chociaż PeerHandle
jest wymagany do komunikacji z urządzeniami peer-to-peer, nie należy polegać na nim jako trwałym identyfikatorze urządzeń peer-to-peer. Identyfikatory wyższego poziomu mogą być używane przez aplikację wbudowaną w usługę wyszukiwania lub w kolejnych wiadomościach. Identyfikator możesz osadzić w usłudze wyszukiwania za pomocą metody setMatchFilter()
lub setServiceSpecificInfo()
PublishConfig
lub SubscribeConfig
. Metoda setMatchFilter()
wpływa na odkrywanie, a metoda setServiceSpecificInfo()
nie ma na to wpływu.
Wbudowanie identyfikatora w wiadomości oznacza zmodyfikowanie tablicy bajtów wiadomości w celu uwzględnienia identyfikatora (np. jako pierwszych kilku bajtów).
Tworzenie połączenia
Wi-Fi Aware obsługuje sieć klient-serwer między dwoma urządzeniami Wi-Fi Aware.
Aby skonfigurować połączenie klienta z serwerem:
Użyj wykrywania Wi-Fi Aware, aby opublikować usługę (na serwerze) i zasubskrybować usługę (na kliencie).
Gdy subskrybent znajdzie wydawcę, wyślij wiadomość od subskrybenta do wydawcy.
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();
Użyj
ConnectivityManager
, aby poprosić wydawcę o utworzenie sieci Wi-Fi Aware, używającWifiAwareNetworkSpecifier
, podając sesję odkrywania iPeerHandle
subskrybenta, które zostały 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);
Gdy wydawca poprosi o dostęp do sieci, powinien wysłać wiadomość do subskrybenta.
Gdy subskrybent otrzyma wiadomość od wydawcy, poproś o utworzenie sieci Wi-Fi Aware u subskrybenta, używając tej samej metody co w przypadku wydawcy. Podczas tworzenia
NetworkSpecifier
nie określaj portu. Odpowiednie metody wywołania są wywoływane, gdy połączenie z siecią jest dostępne, zmienione lub utracone.Po wywołaniu metody
onAvailable()
subskrybenta dostępny jest obiektNetwork
, za pomocą którego możesz otworzyćSocket
, aby komunikować się zServerSocket
u wydawcy. Musisz jednak znać adres IPv6 i port IPv6ServerSocket
. Te informacje możesz uzyskać z obiektuNetworkCapabilities
, który jest przekazywany 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);
Po zakończeniu konfigurowania połączenia z siecią zadzwoń na
unregisterNetworkCallback()
.
Różne grupy rówieśników i odkrywanie z uwzględnieniem lokalizacji
Urządzenie z funkcją lokalizacji Wi-Fi RTT może bezpośrednio mierzyć odległość do urządzeń w swojej sieci i wykorzystywać te informacje do ograniczania wykrywania usługi 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że być ograniczone do wykrywania tylko usług w określonym geoogrodzeniu. Możesz na przykład skonfigurować geofence, który umożliwia wykrywanie urządzenia publikującego usługę "Aware_File_Share_Service_Name"
w odległości nie mniejszej niż 3 metry (określanej jako 3000 mm) i nie większej niż 10 metrów (określanej jako 10 000 mm).
Aby włączyć geofencing, zarówno wydawca, jak i subskrybent muszą wykonać działanie:
Wydawca musi włączyć zakresowanie w opublikowanej usłudze za pomocą funkcji setRangingEnabled(true).
Jeśli wydawca nie włączy pomiaru zasięgu, wszelkie ograniczenia geofence określone przez subskrybenta są ignorowane, a wykrywanie odbywa się w normalny sposób, ignorując odległość.
Subskrybent musi określić geofence, używając kombinacji funkcji setMinDistanceMm i setMaxDistanceMm.
W przypadku każdej z tych wartości nieokreślony dystans oznacza brak limitu. Podanie tylko maksymalnej odległości oznacza, że minimalna odległość to 0. Określenie tylko minimalnej odległości oznacza brak maksymalnej odległości.
Gdy usługa peer zostanie wykryta w geoogrodzeniu, zostanie wywołana funkcja onServiceDiscoveredWithinRange, która podaje zmierzoną odległość do peera. Następnie można wywołać bezpośredni interfejs API RTT Wi-Fi w celu pomiaru odległości w późniejszym czasie.