Omówienie Wi-Fi Aware

Funkcje Wi-Fi Aware umożliwiają urządzeniom z Androidem 8.0 (poziom interfejsu API 26) i nowszym wykrywaniem i łączeniem się bezpośrednio ze sobą bez jakiegokolwiek innego połączenia. Wi-Fi Aware jest też nazywane siecią świadomości sąsiadów (NAN).

Obsługa sieci Wi-Fi Aware polega na tworzeniu klastrów z urządzeniami sąsiednimi lub przez utworzenie nowego klastra, jeśli urządzenie jest pierwsze w danym obszarze. Takie działanie grupowania dotyczy całego urządzenia i jest zarządzane przez usługę systemową Wi-Fi Aware. Aplikacje nie mają kontroli nad działaniem grupowania. Aplikacje korzystają z interfejsów Wi-Fi Aware API, by 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 pozwalają aplikacjom wykonywać te operacje:

  • Wykrywanie innych urządzeń: interfejs API ma mechanizm znajdowania innych urządzeń w pobliżu. Ten proces rozpoczyna się, gdy jedno urządzenie opublikuje co najmniej 1 usługę wykrywalną. Następnie, gdy urządzenie subskrybuje co najmniej jedną usługę i znajduje się w zasięgu Wi-Fi wydawcy, subskrybent otrzyma powiadomienie o wykryciu pasującego wydawcy. Gdy subskrybent odkryje wydawcę, może wysłać krótką wiadomość lub nawiązać połączenie sieciowe z wykrytym urządzeniem. Urządzenia mogą być jednocześnie jednocześnie wydawcami i subskrybentami.

  • Utwórz połączenie sieciowe: gdy 2 urządzenia odnajdą się, mogą nawiązać dwukierunkowe połączenie sieciowe Wi-Fi Aware bez punktu dostępu.

Połączenia sieciowe Wi-Fi Aware obsługują większą przepustowość na dłuższych dystansach niż połączenia Bluetooth. Tego typu połączenia są przydatne w aplikacjach, które udostępniają duże ilości danych między użytkownikami, np. w aplikacjach do udostępniania zdjęć.

Ulepszenia Androida 12 (poziom interfejsu API 31)

Android 12 (poziom interfejsu API 31) wprowadza kilka ulepszeń do 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 zatrzymania lub wykroczenia poza zasięg usługi.
  • Konfiguracja ścieżek danych Wi-Fi Aware została uproszczona. We wcześniejszych wersjach do dostarczenia adresu MAC inicjatora używano komunikatów L2, co spowodowało opóźnienie. Na urządzeniach z Androidem 12 lub nowszym można skonfigurować odpowiedź (serwer) tak, aby akceptowała wszystkie połączenia równorzędne, co oznacza, że nie trzeba od razu znać adresu MAC inicjatora. Przyspiesza to tworzenie ścieżki danych i umożliwia wiele połączeń między punktami za pomocą tylko jednego żądania sieciowego.
  • Aplikacje działające na Androidzie 12 lub nowszym mogą używać metody WifiAwareManager.getAvailableAwareResources(), aby sprawdzać liczbę obecnie dostępnych ścieżek danych oraz publikować i subskrybować sesje. Pomaga to aplikacji określić, czy ilość dostępnych zasobów jest wystarczająca do wykonania wymaganej funkcji.

Konfiguracja początkowa

Aby skonfigurować w aplikacji wykrywanie sieci i wykrywanie 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 funkcję Wi-Fi Aware z interfejsem 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 sieć Wi-Fi Aware jest obecnie dostępna. Wi-Fi Aware może istnieć na urządzeniu, ale obecnie może nie być dostępne, 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 korzystasz z Wi-Fi Direct, SoftAP lub tetheringu. Aby sprawdzić, czy Wi-Fi Aware jest obecnie dostępne, zadzwoń pod numer isAvailable().

    Dostępność Wi-Fi Aware może się zmienić w każdej chwili. Twoja aplikacja powinna zarejestrować element BroadcastReceiver, aby otrzymać ACTION_WIFI_AWARE_STATE_CHANGED, który jest wysyłany zawsze, gdy zmieni się dostępność. Gdy aplikacja otrzyma intencję transmisji, powinna odrzucić wszystkie istniejące sesje (przy założeniu, ż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. 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 Komunikaty.

Pobieranie sesji

Aby zacząć korzystać z Wi-Fi Aware, aplikacja musi uzyskać WifiAwareSession, dzwoniąc pod numer attach(). Ta metoda:

  • Włącza sprzęt Wi-Fi Aware.
  • Dołącza lub tworzy klaster Wi-Fi Aware.
  • Tworzy sesję Wi-Fi Aware z unikalną przestrzenią nazw, która działa jako kontener na wszystkie utworzone w niej sesje wykrywania.

Jeśli aplikacja się połączy, system wykona wywołanie zwrotne onAttached(). To wywołanie zwrotne udostępnia obiekt WifiAwareSession, którego aplikacja powinna używać przy wszystkich kolejnych operacjach w ramach sesji. Aplikacja może wykorzystać sesję do opublikowania usługi lub subskrypcji usługi.

Aplikacja powinna wywołać metodę attach() tylko raz. Jeśli aplikacja wielokrotnie wywołuje funkcję attach(), dla każdego wywołania otrzymuje inną sesję z własną przestrzenią nazw. Może to być przydatne w złożonych scenariuszach, ale zasadniczo należy tego unikać.

Publikowanie usługi

Aby usługa stała się wykrywalna, wywołaj metodę publish(), która przyjmuje te parametry:

  • PublishConfig określa nazwę usługi i inne właściwości konfiguracji, np. filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają być wykonywane, gdy wystąpią zdarzenia, na przykład 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().

Gdy po publikacji urządzenia z pasującymi aplikacjami do subskrypcji przejdą do zasięgu Wi-Fi urządzenia publikującego, subskrybenci odkrywają usługę. Gdy subskrybent znajdzie wydawcę, wydawca nie otrzyma powiadomienia. Jeśli jednak subskrybent wyśle do niego wiadomość, otrzyma on powiadomienie. W takim przypadku wywoływana jest metoda wywołania zwrotnego onMessageReceived(). Możesz użyć argumentu PeerHandle z tej metody, aby wysłać wiadomość z powrotem do subskrybenta lub utworzyć z nią połączenie.

Aby zakończyć publikowanie usługi, wywołaj DiscoverySession.close(). Sesje Discovery są powiązane z kontem 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, że sesje spoza zakresu 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ć, i inne właściwości konfiguracji, takie jak filtr dopasowania.
  • DiscoverySessionCallback określa działania, które mają być wykonywane w momencie wystąpienia 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(). Za pomocą argumentu SubscribeDiscoverySession w wywołaniu zwrotnym możesz komunikować się z wydawcą, gdy aplikacja go wykryje, dlatego warto zapisać to odwołanie. W każdej chwili możesz zaktualizować sesję subskrypcji, wywołując updateSubscribe() w sesji wykrywania.

W tym momencie subskrypcja czeka, aż pasujących wydawców znajdzie się w zasięgu sieci Wi-Fi. W takim przypadku system wykonuje metodę wywołania zwrotnego onServiceDiscovered(). Aby wysłać wiadomość lub utworzyć połączenie z tym wydawcą, możesz użyć argumentu PeerHandle.

Aby przestać subskrybować usługę, wywołaj DiscoverySession.close(). Sesje Discovery są powiązane z kontem 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, że sesje spoza zakresu zostaną zamknięte. Dlatego zalecamy jawne wywoływanie metod close().

Wysyłanie wiadomości

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

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

  • Gdy połączenie zostanie odebrane, system wywołuje wywołanie zwrotne onMessageSendSucceeded() w aplikacji do wysyłania.
  • Gdy peer otrzyma wiadomość, system wywołuje wywołanie zwrotne onMessageReceived() w aplikacji odbierającej.

Chociaż PeerHandle jest wymagany do komunikacji z peerami, nie należy traktować go jako stałego identyfikatora grupy porównawczej. Identyfikatory wyższego poziomu mogą być używane przez aplikację osadzone w usłudze wykrywania lub w kolejnych wiadomościach. Identyfikator możesz umieścić w usłudze wykrywania za pomocą metody setMatchFilter() lub setServiceSpecificInfo() PublishConfig bądź SubscribeConfig. Metoda setMatchFilter() wpływa na wykrywanie, a metoda setServiceSpecificInfo() nie.

Umieszczenie identyfikatora w wiadomości oznacza zmodyfikowanie tablicy bajtów wiadomości w taki sposób, aby zawierała identyfikator (na przykład na kilka pierwszych bajtów).

Tworzenie połączenia

Funkcja Wi-Fi Aware obsługuje sieć klient-serwer między dwoma urządzeniami Wi-Fi Aware.

Aby skonfigurować połączenie klient-serwer:

  1. Za pomocą wykrywania przez Wi-Fi Aware możesz opublikować usługę (na serwerze) i zasubskrybować usługę (w kliencie).

  2. Gdy subskrybent znajdzie wydawcę, wyślij do niego wiadomość.

  3. Uruchom instancję ServerSocket na urządzeniu wydawcy i ustaw lub pobierz jej port:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
    
  4. Użyj metody ConnectivityManager, aby zażądać od wydawcy dostępu do sieci Wi-Fi Aware za pomocą metody WifiAwareNetworkSpecifier, określając sesję wykrywania i PeerHandle subskrybenta, które zostały uzyskane z wiadomości przesył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 dostęp do sieci, powinien wysłać wiadomość do subskrybenta.

  6. Gdy subskrybent otrzyma wiadomość od wydawcy, poproś go o dostęp do sieci Wi-Fi Aware, używając tej samej metody co w przypadku wydawcy. Podczas tworzenia NetworkSpecifier nie określaj 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() u subskrybenta dostępny jest obiekt Network, za pomocą którego możesz otworzyć obiekt Socket, aby komunikować się z ServerSocket u wydawcy. Musisz jednak znać adres IPv6 i port protokołu IPv6.ServerSocket Uzyskujesz te wyniki 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 sieciowego wywołaj unregisterNetworkCallback().

Duża liczba podobnych odbiorców i odkrywanie z uwzględnieniem lokalizacji

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

Interfejs Wi-Fi RTT API umożliwia bezpośrednie przechodzenie do peera za pomocą sieci Wi-Fi Aware przy użyciu jego adresu MAC lub klasy PeerHandle.

Wykrywanie usług przez Wi-Fi można ograniczyć do wykrywania usług w obrębie określonej geofencingu. Możesz na przykład skonfigurować geofencing, który umożliwia wykrywanie urządzenia publikującego usługę "Aware_File_Share_Service_Name" na odległość nie większą niż 3 metry (3000 mm) ani 10 metrów (10 000 mm).

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

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

    Jeśli wydawca nie włączy zakresu, wszystkie ograniczenia geofencingu określone przez subskrybenta będą ignorowane, a wykrywanie będzie przebiegać normalnie z pominięciem odległości.

  • Subskrybent musi określić strefę geofencingu przy użyciu kombinacji atrybutów setMinDISTANCEMm i setMaxRangeMm.

    W przypadku każdej z tych wartości nieokreślona odległość oznacza brak limitu. Tylko określenie maksymalnej odległości oznacza minimalną odległość 0. Tylko określenie minimalnej odległości nie oznacza, że nie ma maksymalnego.

Po wykryciu usługi równorzędnej w obrębie geofencingu uruchamiane jest wywołanie zwrotne onServiceDiscoveredWithinRange, które dostarcza zmierzoną odległość do peera. Interfejs bezpośredniego połączenia Wi-Fi RTT API może zostać wywołany w razie potrzeby do pomiaru odległości w późniejszych momentach.