Wi-Fi Aware – Übersicht

Dank Wi-Fi-Aware-Funktionen können Geräte mit Android 8.0 (API-Level 26) und höher sich gegenseitig erkennen und direkt eine Verbindung herstellen, ohne dass eine andere Art von Verbindung zwischen ihnen besteht. Wi-Fi Aware ist auch als Neighbor Awareness Networking (Neighbor Awareness Networking) bekannt.

In WLAN-sensitiven Netzwerken werden Cluster mit benachbarten Geräten gebildet oder ein neuer Cluster erstellt, wenn das Gerät das erste in einem Bereich ist. Dieses Clustering-Verhalten gilt für das gesamte Gerät und wird vom Wi-Fi Aware-Systemdienst verwaltet. Apps haben keine Kontrolle über das Clustering-Verhalten. Apps verwenden die Wi-Fi Aware APIs, um mit dem Wi-Fi Aware-Systemdienst zu kommunizieren, der die Wi-Fi Aware-Hardware auf dem Gerät verwaltet.

Mit den Wi-Fi Aware APIs können Apps folgende Vorgänge ausführen:

  • Andere Geräte finden:Die API bietet einen Mechanismus, um Geräte in der Nähe zu finden. Der Prozess beginnt, wenn ein Gerät einen oder mehrere auffindbare Dienste veröffentlicht. Wenn ein Gerät dann einen oder mehrere Dienste abonniert und in den WLAN-Bereich des Verlags oder Webpublishers gelangt, erhält der Abonnent eine Benachrichtigung, dass ein übereinstimmender Publisher gefunden wurde. Nachdem der Abonnent einen Publisher entdeckt hat, kann der Abonnent entweder eine kurze Nachricht senden oder eine Netzwerkverbindung mit dem erkannten Gerät herstellen. Geräte können gleichzeitig Publisher und Abonnenten sein.

  • Netzwerkverbindung erstellen:Nachdem zwei Geräte einander erkannt haben, können sie eine bidirektionale Wi-Fi Aware-Netzwerkverbindung ohne Zugangspunkt herstellen.

Wi-Fi-Aware-Netzwerkverbindungen unterstützen über größere Entfernungen höhere Durchsatzraten als Bluetooth-Verbindungen. Diese Verbindungstypen sind nützlich für Apps, die große Datenmengen unter Nutzern teilen, z. B. Apps zum Teilen von Fotos.

Verbesserungen für Android 12 (API-Level 31)

Unter Android 12 (API-Level 31) wurden einige Verbesserungen bei Wi-Fi Aware vorgenommen:

  • Auf Geräten mit Android 12 (API-Level 31) oder höher kannst du den onServiceLost()-Callback verwenden, um dich zu benachrichtigen, wenn deine App einen erkannten Dienst verloren hat, weil der Dienst angehalten wurde oder sich außerhalb des Bereichs bewegt.
  • Die Einrichtung von Wi-Fi Aware-Datenpfaden wurde vereinfacht. In früheren Versionen wurde L2-Messaging verwendet, um die MAC-Adresse des Initiators bereitzustellen, was Latenz verursachte. Auf Geräten mit Android 12 und höher kann der Listener (Server) so konfiguriert werden, dass er beliebige Peers akzeptiert. Das heißt, er muss die MAC-Adresse des Initators nicht im Voraus kennen. Dies beschleunigt das Hochladen des Datenpfads und ermöglicht mehrere Punkt-zu-Punkt-Links mit nur einer Netzwerkanfrage.
  • Apps, die unter Android 12 oder höher ausgeführt werden, können mit der Methode WifiAwareManager.getAvailableAwareResources() die Anzahl der aktuell verfügbaren Datenpfade sowie Veröffentlichungs- und Abositzungen abrufen. So kann die Anwendung feststellen, ob genügend Ressourcen für die Ausführung der gewünschten Funktion vorhanden sind.

Ersteinrichtung

Führe die folgenden Schritte aus, um deine App für die WLAN-Erkennung und -Netzwerke einzurichten:

  1. Fordere im Manifest deiner App die folgenden Berechtigungen an:

    <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. Prüfe, ob das Gerät Wi-Fi Aware mit der PackageManager API unterstützt:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
    

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
    
  3. Prüfen Sie, ob Wi-Fi-Aware derzeit verfügbar ist. Möglicherweise ist Wi-Fi-Aware auf dem Gerät vorhanden, ist aber derzeit nicht verfügbar, weil der Nutzer WLAN oder den Standort deaktiviert hat. Je nach Hardware- und Firmwarefunktionen unterstützen einige Geräte Wi-Fi Direct, SoftAP oder Tethering möglicherweise nicht. Wenn du prüfen möchtest, ob Wi-Fi Aware derzeit verfügbar ist, ruf isAvailable() an.

    Die Verfügbarkeit von Wi-Fi Aware kann sich jederzeit ändern. Ihre Anwendung sollte eine BroadcastReceiver registrieren, um ACTION_WIFI_AWARE_STATE_CHANGED zu erhalten, die gesendet wird, wenn sich die Verfügbarkeit ändert. Wenn Ihre App den Broadcast-Intent empfängt, sollten alle vorhandenen Sitzungen verworfen werden (unter der Annahme, dass der Wi-Fi Aware-Dienst unterbrochen wurde). Anschließend sollte der aktuelle Verfügbarkeitsstatus geprüft und das Verhalten entsprechend angepasst werden. Beispiele:

    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);
    

Weitere Informationen finden Sie unter Broadcasts.

Sitzung erhalten

Damit du Wi-Fi Aware nutzen kannst, muss deine App durch Aufrufen von attach() ein WifiAwareSession anfordern. Diese Methode führt folgende Schritte aus:

  • Aktiviert Wi-Fi Aware-Hardware.
  • Stellt einem Wi-Fi Aware-Cluster bei oder bildet einen solchen Cluster.
  • Erstellt eine Wi-Fi Aware-Sitzung mit einem eindeutigen Namespace, der als Container für alle darin erstellten Erkennungssitzungen fungiert.

Wenn die Anwendung erfolgreich angehängt wurde, führt das System den onAttached()-Callback aus. Dieser Callback stellt ein WifiAwareSession-Objekt bereit, das deine App für alle weiteren Sitzungsvorgänge verwenden soll. Über die Sitzung kann eine Anwendung einen Dienst veröffentlichen oder einen Dienst abonnieren.

Deine App sollte attach() nur einmal aufrufen. Wenn Ihre Anwendung attach() mehrmals aufruft, erhält sie für jeden Aufruf eine andere Sitzung mit jeweils einem eigenen Namespace. Dies kann bei komplexen Szenarien nützlich sein, sollte aber im Allgemeinen vermieden werden.

Dienst veröffentlichen

Wenn Sie einen Dienst auffindbar machen möchten, rufen Sie die Methode publish() auf, die die folgenden Parameter verwendet:

  • PublishConfig gibt den Namen des Dienstes und andere Konfigurationsattribute wie Übereinstimmungsfilter an.
  • DiscoverySessionCallback gibt die Aktionen an, die bei Ereignissen ausgeführt werden sollen, z. B. wenn der Abonnent eine Nachricht empfängt.

Beispiel:

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);

Bei erfolgreicher Veröffentlichung wird die Callback-Methode onPublishStarted() aufgerufen.

Wenn sich nach der Veröffentlichung Geräte, auf denen passende Abonnenten-Apps ausgeführt werden, in die WLAN-Reichweite des Veröffentlichungsgeräts bewegen, entdecken Abonnenten den Dienst. Wenn ein Abonnent einen Verlag oder Webpublisher entdeckt, erhält dieser keine Benachrichtigung. Wenn der Abonnent jedoch eine Nachricht an den Verlag oder Webpublisher sendet, erhält dieser eine Benachrichtigung. In diesem Fall wird die Callback-Methode onMessageReceived() aufgerufen. Mit dem Argument PeerHandle dieser Methode können Sie eine Nachricht an den Abonnenten zurücksenden oder eine Verbindung zu ihr erstellen.

Rufen Sie DiscoverySession.close() auf, um die Veröffentlichung des Dienstes zu beenden. Discovery-Sitzungen sind der übergeordneten WifiAwareSession zugeordnet. Wenn die übergeordnete Sitzung geschlossen wird, werden auch die zugehörigen Erkennungssitzungen geschlossen. Auch wenn verworfene Objekte geschlossen werden, kann das System nicht garantieren, dass Sitzungen, die nicht im Projektumfang enthalten sind, geschlossen werden. Daher empfehlen wir, die Methoden close() explizit aufzurufen.

Dienst abonnieren

Wenn Sie einen Dienst abonnieren möchten, rufen Sie die Methode subscribe() auf. Sie verwendet die folgenden Parameter:

  • SubscribeConfig gibt den Namen des Dienstes, der abonniert werden soll, und andere Konfigurationsattribute wie Übereinstimmungsfilter an.
  • DiscoverySessionCallback gibt die Aktionen an, die ausgeführt werden sollen, wenn Ereignisse eintreten, z. B. wenn ein Publisher erkannt wird.

Beispiel:

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);

Wenn der Subscribe-Vorgang erfolgreich ist, ruft das System den onSubscribeStarted()-Callback in der App auf. Da du das SubscribeDiscoverySession-Argument im Callback verwenden kannst, um mit einem Publisher zu kommunizieren, nachdem deine App einen Publisher erkannt hat, solltest du diesen Verweis speichern. Du kannst die Abositzung jederzeit aktualisieren, indem du in der Erkennungssitzung updateSubscribe() aufrufst.

An diesem Punkt wartet Ihr Abo, bis passende Verlage und Webpublisher im WLAN-Bereich kommen. In diesem Fall führt das System die Callback-Methode onServiceDiscovered() aus. Sie können das Argument PeerHandle aus diesem Callback verwenden, um eine Nachricht zu senden oder eine Verbindung zu diesem Publisher zu erstellen.

Wenn Sie einen Dienst nicht mehr abonnieren möchten, rufen Sie DiscoverySession.close() auf. Discovery-Sitzungen sind der übergeordneten WifiAwareSession zugeordnet. Wenn die übergeordnete Sitzung geschlossen wird, werden auch die zugehörigen Erkennungssitzungen geschlossen. Auch wenn verworfene Objekte geschlossen werden, kann das System nicht garantieren, dass Sitzungen, die nicht im Projektumfang enthalten sind, geschlossen werden. Daher empfehlen wir, die Methoden close() explizit aufzurufen.

Eine Nachricht posten

Zum Senden einer Nachricht an ein anderes Gerät benötigen Sie die folgenden Objekte:

Um eine Nachricht zu senden, rufen Sie sendMessage() auf. Die folgenden Callbacks können dann auftreten:

  • Wenn die Nachricht vom Peer erfolgreich empfangen wurde, ruft das System den onMessageSendSucceeded()-Callback in der Senden-App auf.
  • Wenn der Peer eine Nachricht empfängt, ruft das System den onMessageReceived()-Callback in der Empfänger-App auf.

Obwohl der PeerHandle für die Kommunikation mit Peers erforderlich ist, sollten Sie sich nicht darauf als dauerhafte Kennung von Peers verlassen. Übergeordnete Kennungen können von der Anwendung verwendet werden, eingebettet in den Erkennungsdienst selbst oder in nachfolgenden Nachrichten. Sie können eine Kennung mit der Methode setMatchFilter() oder setServiceSpecificInfo() von PublishConfig oder SubscribeConfig in den Erkennungsdienst einbetten. Die Methode setMatchFilter() wirkt sich auf die Erkennung aus, die Methode setServiceSpecificInfo() hingegen nicht.

Das Einbetten einer Kennung in eine Nachricht impliziert, dass das Nachrichtenbyte-Array so geändert wird, dass es eine Kennung enthält (z. B. die ersten Byte).

Verbindung herstellen

Wi-Fi Aware unterstützt Client-Server-Netzwerke zwischen zwei Wi-Fi Aware-Geräten.

So richten Sie die Client-Server-Verbindung ein:

  1. Nutzen Sie die Wi-Fi Aware-Erkennung, um einen Dienst auf dem Server zu veröffentlichen und einen Dienst zu abonnieren (auf dem Client).

  2. Sobald der Abonnent den Verlag oder Webpublisher gefunden hat, sende eine Nachricht vom Abonnenten an den Verlag oder Webpublisher.

  3. Starte ein ServerSocket auf dem Gerät des Verlags oder Webpublishers und rufe entweder den Port fest oder rufe ihn ab:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
    
  4. Verwende den ConnectivityManager, um mit einem WifiAwareNetworkSpecifier ein WLAN-sensitives Netzwerk beim Verlag oder Webpublisher anzufordern. Gib dabei die Erkennungssitzung und den PeerHandle des Abonnenten an, den du aus der vom Abonnenten übertragenen Nachricht erhalten hast:

    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. Wenn der Verlag oder Webpublisher ein Netzwerk anfordert, sollte er eine Nachricht an den Abonnenten senden.

  6. Sobald der Abonnent die Nachricht vom Verlag oder Webpublisher erhalten hat, fordern Sie beim Abonnenten mit derselben Methode wie beim Verlag oder Webpublisher ein Wi-Fi Aware-Netzwerk an. Geben Sie beim Erstellen von NetworkSpecifier keinen Port an. Die entsprechenden Callback-Methoden werden aufgerufen, wenn die Netzwerkverbindung verfügbar ist, geändert wird oder unterbrochen wird.

  7. Sobald die Methode onAvailable() auf dem Abonnenten aufgerufen wird, steht ein Network-Objekt zur Verfügung, mit dem Sie ein Socket öffnen können, um mit dem ServerSocket auf dem Publisher zu kommunizieren. Sie müssen jedoch die IPv6-Adresse und den Port von ServerSocket kennen. Sie erhalten diese vom NetworkCapabilities-Objekt, das im onCapabilitiesChanged()-Callback bereitgestellt wird:

    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. Wenn die Netzwerkverbindung fertig ist, rufen Sie unregisterNetworkCallback() auf.

Verschiedene Mitbewerber und standortbezogene Informationen

Ein Gerät mit WLAN-RTT-Standort kann die Entfernung zu Peers direkt messen und diese Informationen verwenden, um die Erkennung des Wi-Fi Aware-Dienstes einzuschränken.

Die Wi-Fi RTT API ermöglicht das direkte Routing zu einem Wi-Fi Aware Peer entweder über seine MAC-Adresse oder sein PeerHandle.

Die Wi-Fi Aware-Erkennung kann so eingeschränkt werden, dass Dienste nur innerhalb eines bestimmten Geofence erkannt werden. Sie können beispielsweise einen Geofence einrichten, der die Erkennung eines Geräts ermöglicht,das einen "Aware_File_Share_Service_Name"-Dienst veröffentlicht,der nicht näher als 3 Meter (angegeben als 3.000 mm) und nicht weiter als 10 Meter (angegeben als 10.000 mm) ist.

Um Geofencing zu aktivieren, müssen sowohl der Publisher als auch der Abonnent Folgendes tun:

  • Der Publisher muss den Bereich mit setRangingEnabled(true) für den veröffentlichten Dienst aktivieren.

    Wenn der Verlag oder Webpublisher die Entfernung nicht aktiviert, werden alle vom Abonnenten angegebenen Geofence-Einschränkungen ignoriert und eine normale Erkennung ausgeführt, wobei die Entfernung ignoriert wird.

  • Der Abonnent muss einen Geofence mit einer Kombination aus setMinDistanceMm und setMaxDistanceMm angeben.

    Bei beiden Werten stellt eine nicht angegebene Entfernung keine Begrenzung dar. Nur die Angabe der maximalen Entfernung gilt für eine Mindestentfernung von 0. Nur die Angabe der Mindestentfernung hat keine Begrenzung.

Wenn ein Peer-Dienst innerhalb eines Geofence erkannt wird, wird der Callback onServiceDiscoveredWithinRange ausgelöst, der die gemessene Entfernung zum Peer angibt. Die Direct Wi-Fi RTT API kann dann bei Bedarf aufgerufen werden, um später die Entfernung zu messen.