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 bezpośrednie łączenie się ze sobą bez żadnego innego rodzaju łączności. Wi-Fi Aware jest też znana jako 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 pierwszym w 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 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 Wi-Fi Aware API umożliwiają aplikacjom wykonywanie tych działań:

  • Odkrywanie 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ć. 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. Gdy subskrybent znajdzie 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.

  • Utwórz połączenie sieciowe: 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ększe szybkości transmisji danych na większych odległościach niż połączenia Bluetooth. Tego typu połączenia są przydatne w przypadku aplikacji, które udostępniają użytkownikom duże ilości danych, 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()SubscribeConfig.Builder.setInstantCommunicationModeEnabled(), aby włączyć lub wyłączyć tryb komunikacji natychmiastowej w sesji wykrywania wydawcy lub subskrybenta. Tryb komunikacji natychmiastowej 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 błyskawicznej, użyj metody isInstantCommunicationModeSupported().

Ulepszenia w Androidzie 12 (poziom 31 interfejsu API)

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

  • Na urządzeniach z Androidem 12 (API na poziomie 31) lub nowszym możesz używać wywołania zwrotnego onServiceLost() do otrzymywania powiadomień o utracie wykrytej usługi przez aplikację z powodu zatrzymania usługi lub jej wyjścia poza zasięg.
  • Uprościliśmy konfigurację ścieżek danych Wi-Fi Aware. Wcześniejsze wersje używały wiadomości warstwy 2, aby podać adres MAC inicjatora, co powodowało opóźnienia. Na urządzeniach z Androidem 12 lub nowszym serwer może być skonfigurowany tak, aby akceptować dowolne urządzenie równorzędne, czyli nie musi 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 1 żą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, publikowania sesji i subskrybowania sesji. Dzięki temu aplikacja może określić, czy ma wystarczającą ilość dostępnych 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ępna. Funkcja Wi-Fi Aware może być dostępna na urządzeniu, ale obecnie 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 używane są Wi-Fi Direct, SoftAP lub tethering. Aby sprawdzić, czy Wi-Fi Aware jest obecnie dostępna, zadzwoń pod numer 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óre są wysyłane za każdym razem, gdy zmienia się dostępność. Gdy aplikacja otrzyma intencję transmisji, powinna odrzucić wszystkie istniejące sesje (zakładając, ż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 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 wszystkich sesji wykrywania utworzonych w jej ramach.

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

Aplikacja powinna wywoływać funkcję attach() tylko raz. Jeśli Twoja aplikacja wywołuje attach() kilka razy, przy każdym wywołaniu otrzymuje inną sesję z własną przestrzenią nazw. Może to być przydatne w złożonych scenariuszach, ale generalnie 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 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 publikacja 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 wykryją usługę. Gdy subskrybent odkryje publikującego, publikujący nie otrzyma powiadomienia. Jeśli jednak subskrybent wyśle do publikującego wiadomość, publikujący otrzyma powiadomienie. Gdy to nastąpi, wywoływana jest metoda wywołania zwrotnego onMessageReceived(). Możesz użyć argumentu PeerHandle z tej metody, aby wysłać wiadomość do subskrybenta lub utworzyć z nim połączenie.

Aby zakończyć publikowanie usługi, zadzwoń pod numer DiscoverySession.close(). Sesje wykrywania są powiązane z nadrzędnym elementem 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 zamykane są sesje wykraczające poza zakres, więc 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, do której chcesz się subskrybować, oraz inne właściwości konfiguracji, takie jak filtr dopasowania.
  • DiscoverySessionCallback określa czynności, które mają być wykonywane w przypadku wystąpienia zdarzeń, np. 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ę zwrotną onSubscribeStarted(). Ponieważ w funkcji zwrotnej możesz użyć argumentu SubscribeDiscoverySession do komunikowania się z wydawcą po wykryciu go przez aplikację, warto zapisać ten odnośnik. Sesję subskrypcji możesz zaktualizować w dowolnym momencie, wywołując funkcję updateSubscribe() w sesji wykrywania.

Na tym etapie subskrypcja czeka na wydawców, którzy 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 nadrzędnym elementem 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 zamykane są sesje wykraczające poza zakres, więc 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:

  • Gdy wiadomość zostanie odebrana przez urządzenie równorzędne, system wywoła wywołanie zwrotne w aplikacji wysyłającej.onMessageSendSucceeded()
  • Gdy urządzenie odbierające otrzyma wiadomość, system wywoła funkcję zwrotną w aplikacji odbierającej.onMessageReceived()

Chociaż PeerHandle jest wymagany do komunikacji z urządzeniami równorzędnymi, nie należy go traktować jako trwałego identyfikatora tych urządzeń. Identyfikatory wyższego poziomu mogą być używane przez aplikację – wbudowane w samą usługę wykrywania lub w kolejnych wiadomościach. Identyfikator możesz umieścić w usłudze wykrywania za pomocą metody setMatchFilter() lub setServiceSpecificInfo() w przypadku PublishConfig albo SubscribeConfig. Metoda setMatchFilter() wpływa na odkrywanie, a metoda setServiceSpecificInfo() nie ma na nie wpływu.

Osadzanie identyfikatora w wiadomości oznacza modyfikowanie tablicy bajtów wiadomości w celu uwzględnienia identyfikatora (np. jako pierwszych kilku bajtów).

Tworzenie połączenia

Wi-Fi Aware obsługuje sieci 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 publikującego, wyśle do niego wiadomość.

  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 urządzeniu 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, zażądaj sieci Wi-Fi Aware na urządzeniu subskrybenta, korzystając z 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, ulegnie zmianie lub zostanie utracone.

  7. Gdy w przypadku subskrybenta zostanie wywołana metoda onAvailable(), dostępny będzie obiekt Network, za pomocą którego możesz otworzyć Socket, aby komunikować się z ServerSocket na stronie wydawcy. Musisz jednak znać adres IPv6 i port ServerSocket. Otrzymujesz je 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 z siecią zadzwoń pod numer unregisterNetworkCallback().

Określanie odległości od innych urządzeń i wykrywanie z uwzględnieniem lokalizacji

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

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

Wykrywanie Wi-Fi Aware można ograniczyć do wykrywania tylko usług 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" w odległości nie mniejszej niż 3 metry (3000 mm) i nie większej niż 10 metrów (10 000 mm).

Aby włączyć geofencing, wydawca i subskrybent muszą wykonać te czynności:

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

    Jeśli wydawca nie włączy określania zakresu, wszystkie ograniczenia geofence określone przez subskrybenta są ignorowane i przeprowadzane jest normalne wykrywanie z pominięciem odległości.

  • Subskrybent musi określić geofence, używając kombinacji funkcji setMinDistanceMmsetMaxDistanceMm.

    W przypadku obu wartości nieokreślona odległość oznacza brak limitu. Określenie tylko maksymalnej odległości oznacza, że minimalna odległość wynosi 0. Określenie tylko minimalnej odległości oznacza brak maksymalnej odległości.

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