Omówienie Wi-Fi Aware

Funkcje Wi-Fi Aware umożliwiają urządzeniom z Androidem 8.0 (poziom interfejsu API 26) lub nowszym wykrywanie się nawzajem i łączenie się bezpośrednio ze sobą bez żadnego innego rodzaju połączenia. Funkcja Wi-Fi Aware jest też nazywana Neighbor Awareness Networking (NAN).

Sieć Wi-Fi Aware działa przez tworzenie klastrów z urządzeniami w pobliżu lub przez tworzenie nowego klastra, jeśli urządzenie jest pierwszym na danym obszarze. To zachowanie klastrowania dotyczy całego urządzenia i jest zarządzane przez usługę systemową Wi-Fi Aware. Aplikacje nie mają kontroli nad zachowaniem klastrowania. Aplikacje używają interfejsów API Wi-Fi Aware do komunikacji 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 znajdowania innych urządzeń w pobliżu. Proces rozpoczyna się, gdy jedno urządzenie publikuje co najmniej 1 usługę, którą można wykryć. Gdy urządzenie zasubskrybuje co najmniej 1 usługę i znajdzie się w zasięgu sieci Wi-Fi wydawcy, subskrybent otrzyma powiadomienie o wykryciu pasującego wydawcy. Po wykryciu wydawcy subskrybent może wysłać krótką wiadomość lub nawiązać połączenie sieciowe z wykrytym urządzeniem. Urządzenia mogą być jednocześnie publikującymi i subskrybującymi.

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

Połączenia sieciowe Wi-Fi Aware obsługują wyższe szybkości transmisji 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 API 33)

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

Ulepszenia w Androidzie 12 (poziom interfejsu API 31)

Android 12 (poziom API 31) wprowadza pewne ulepszenia 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ć alerty, gdy aplikacja utraci wykrytą usługę z powodu jej zatrzymania lub znalezienia się poza zasięgiem.
  • Uprościliśmy 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 lub nowszym można skonfigurować urządzenie odpowiadające (serwer) tak, aby akceptowało dowolne urządzenie równorzędne, czyli nie musi ono znać adresu MAC urządzenia inicjującego. Przyspiesza to uruchamianie ścieżki danych i umożliwia tworzenie wielu połączeń punkt-punkt za pomocą tylko jednego żądania sieciowego.
  • Aplikacje działające na Androidzie 12 lub nowszym mogą używać metody WifiAwareManager.getAvailableAwareResources(), aby uzyskać liczbę obecnie dostępnych ścieżek danych, sesji publikowania i sesji subskrypcji. Dzięki temu aplikacja może określić, czy jest wystarczająco dużo dostępnych zasobów, aby wykonać żądaną funkcję.

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, używając 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. Funkcja Wi-Fi Aware może być dostępna na urządzeniu, ale może nie być obecnie dostępna, ponieważ użytkownik wyłączył Wi-Fi lub lokalizację. W zależności od możliwości sprzętu 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 funkcja Wi-Fi Aware jest obecnie dostępna, wywołaj isAvailable().

    Dostępność Wi-Fi Aware może się zmienić w każdej chwili. 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ę rozgłoszeniową, powinna odrzucić wszystkie istniejące sesje (zakładając, że usługa Wi-Fi Aware została zakłócona), a następnie sprawdzić bieżący stan dostępności i odpowiednio dostosować swoje działanie. 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 sekcji Transmisje.

Uzyskiwanie sesji

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

  • Włącza sprzęt Wi-Fi Aware.
  • Dołącza do klastra Wi-Fi Aware lub go tworzy.
  • 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ć we wszystkich dalszych operacjach sesji. Aplikacja może używać sesji do publikowania usługi lub subskrybowania usługi.

Aplikacja powinna wywołać metodę attach() tylko raz. Jeśli aplikacja wywołuje attach() wielokrotnie, otrzymuje inną sesję dla każdego wywołania, każdą z własną przestrzenią nazw. Może to być przydatne w złożonych scenariuszach, ale ogólnie 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ą zostać wykonane po wystąpieniu 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 publikowanie się powiedzie, wywoływana jest metoda wywołania zwrotnego onPublishStarted().

Po opublikowaniu, gdy urządzenia z pasującymi aplikacjami subskrybenta znajdą się w zasięgu Wi-Fi urządzenia publikującego, subskrybenci odkryją usługę. Gdy subskrybent odkryje wydawcę, wydawca nie otrzyma powiadomienia. Jeśli jednak subskrybent wyśle wiadomość do wydawcy, wydawca otrzyma powiadomienie. W takim przypadku wywoływana jest metoda wywołania zwrotnego onMessageReceived(). Możesz użyć argumentu PeerHandle tej metody, aby wysłać wiadomość z powrotem do subskrybenta lub utworzyć z nim połączenie.

Aby zatrzymać publikowanie usługi, wywołaj DiscoverySession.close(). Sesje wykrywania są powiązane z ich elementem nadrzędnym WifiAwareSession. Jeśli sesja nadrzędna zostanie zamknięta, powiązane z nią sesje wykrywania również zostaną zamknięte. Odrzucone obiekty również są zamykane, ale system nie gwarantuje, kiedy sesje poza zakresem zostaną zamknięte, 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ć, oraz inne właściwości konfiguracji, takie jak filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają zostać wykonane po wystąpieniu zdarzeń, np. po wykryciu 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 wywołanie zwrotne onSubscribeStarted(). Ponieważ możesz użyć argumentu SubscribeDiscoverySession w wywołaniu zwrotnym, aby komunikować się z wydawcą po jego wykryciu przez aplikację, zapisz to odwołanie. Sesję subskrypcji możesz zaktualizować w dowolnym momencie, wywołując funkcję updateSubscribe() w sesji wykrywania.

W tym momencie subskrypcja czeka, aż pasujący wydawcy znajdą się w zasięgu Wi-Fi. W takim przypadku system wykonuje metodę wywołania zwrotnego onServiceDiscovered(). Możesz użyć argumentu PeerHandle z tego wywołania zwrotnego, aby wysłać wiadomość lub utworzyć połączenie z tym wydawcą.

Aby zrezygnować z subskrypcji usługi, zadzwoń pod numer DiscoverySession.close(). Sesje wykrywania są powiązane z ich elementem nadrzędnym WifiAwareSession. Jeśli sesja nadrzędna zostanie zamknięta, powiązane z nią sesje wykrywania również zostaną zamknięte. Odrzucone obiekty również są zamykane, ale system nie gwarantuje, kiedy sesje poza zakresem zostaną zamknięte, dlatego zalecamy jawne wywoływanie metod close().

Wyślij wiadomość

Aby wysłać wiadomość na inne urządzenie, potrzebujesz tych obiektów:

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

  • GDT wiadomość zostanie odebrana przez urządzenie, system wywołuje wywołanie zwrotne onMessageSendSucceeded() w wysyłającej aplikacji.
  • Gdy urządzenie odbierze wiadomość, system wywołuje wywołanie zwrotne onMessageReceived() w odbierającej aplikacji.

Chociaż PeerHandle jest wymagany do komunikacji z urządzeniami równorzędnymi, nie należy go traktować jako stałego identyfikatora tych urządzeń. Identyfikatory wyższego poziomu mogą być używane przez aplikację – osadzone 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() interfejsu PublishConfig lub SubscribeConfig. Metoda setMatchFilter() wpływa na wykrywanie, a metoda setServiceSpecificInfo() nie.

Umieszczenie 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 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 zasubskrybować usługę (na kliencie).

  2. Gdy subskrybent wykryje publikującego, wyślij wiadomość od subskrybenta do publikującego.

  3. Uruchom ServerSocket na urządzeniu publikującym 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 u wydawcy za pomocą WifiAwareNetworkSpecifier, określając sesję wykrywania i PeerHandle 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);
  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 urządzeniu subskrybenta, używając tej samej metody co na urządzeniu wydawcy. Podczas tworzenia NetworkSpecifier nie podawaj 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() na subskrybencie dostępny jest obiekt Network, za pomocą którego można otworzyć Socket, aby komunikować się z ServerSocket na wydawcy, ale musisz znać adres IPv6 i port ServerSocket. Możesz je uzyskać 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. Gdy skończysz połączenie sieciowe, zadzwoń pod numer unregisterNetworkCallback().

Wykrywanie urządzeń peer-to-peer i wykrywanie z uwzględnieniem lokalizacji

Urządzenie z funkcją lokalizacji Wi-Fi RTT może bezpośrednio mierzyć odległość od urządzeń równorzędnych i wykorzystywać te informacje do ograniczania wykrywania usług Wi-Fi Aware.

Interfejs Wi-Fi RTT API umożliwia bezpośrednie określanie odległości od urządzenia równorzędnego Wi-Fi Aware za pomocą jego adresu MAC lub PeerHandle.

Wykrywanie Wi-Fi Aware można ograniczyć do wykrywania usług tylko w określonym obszarze geograficznym. Możesz na przykład skonfigurować geofence, który umożliwia wykrywanie urządzenia publikującego usługę "Aware_File_Share_Service_Name", które znajduje się nie bliżej niż 3 metry (określone jako 3000 mm) i nie dalej niż 10 metrów (określone jako 10 000 mm).

Aby włączyć geofencing, zarówno wydawca, jak i subskrybent muszą wykonać odpowiednie czynności:

  • Wydawca musi włączyć określanie odległości w opublikowanej usłudze za pomocą metody setRangingEnabled(true).

    Jeśli wydawca nie włączy określania odległości, wszystkie ograniczenia geofence określone przez subskrybenta będą ignorowane i będzie przeprowadzane normalne wykrywanie, bez uwzględniania odległości.

  • Subskrybent musi określić geofence za pomocą kombinacji setMinDistanceMm i setMaxDistanceMm.

    W przypadku obu wartości nieokreślona odległość oznacza brak limitu. Jeśli określisz tylko maksymalną odległość, minimalna odległość będzie wynosić 0. Określenie tylko minimalnej odległości oznacza brak maksymalnej.

Po wykryciu usługi równorzędnej w obrębie geofence wywoływane jest wywołanie zwrotne onServiceDiscoveredWithinRange, które podaje zmierzoną odległość do urządzenia równorzędnego. W razie potrzeby można wywołać bezpośredni interfejs Wi-Fi RTT API, aby zmierzyć odległość w późniejszym czasie.