Wi-Fi Aware – Übersicht

Mit Wi‑Fi Aware-Funktionen können Geräte mit Android 8.0 (API-Level 26) und höher sich ohne andere Art der Verbindung direkt finden und miteinander verbinden. Wi‑Fi Aware wird auch als Neighbor Awareness Networking (NAN) bezeichnet.

Wi-Fi Aware-Netzwerke bilden Cluster mit benachbarten Geräten oder es wird ein neuer Cluster erstellt, wenn das Gerät das erste Gerät 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 die folgenden Vorgänge ausführen:

  • Andere Geräte finden:Die API bietet einen Mechanismus zum Finden anderer Geräte in der Nähe. 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 sich in der Reichweite des WLANs des Publishers befindet, erhält der Abonnent eine Benachrichtigung, dass ein passender Publisher gefunden wurde. Nachdem der Abonnent einen Publisher gefunden hat, kann er entweder eine Kurznachricht senden oder eine Netzwerkverbindung mit dem gefundenen Gerät herstellen. Geräte können gleichzeitig Publisher und Abonnenten sein.

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

Wi‑Fi Aware-Netzwerkverbindungen unterstützen höhere Durchsatzraten über größere Entfernungen als Bluetooth-Verbindungen. Diese Arten von Verbindungen eignen sich für Apps, bei denen große Datenmengen zwischen Nutzern geteilt werden, z. B. Foto-Teilen-Apps.

Verbesserungen unter Android 13 (API-Level 33)

Auf Geräten mit Android 13 (API-Level 33) und höher, die den Instant-Kommunikationsmodus unterstützen, können Apps die Methoden PublishConfig.Builder.setInstantCommunicationModeEnabled() und SubscribeConfig.Builder.setInstantCommunicationModeEnabled() verwenden, um den Instant-Kommunikationsmodus für eine Discovery-Sitzung von Publishern oder Abonnenten zu aktivieren oder zu deaktivieren. Der Modus für die sofortige Kommunikation beschleunigt den Nachrichtenaustausch, die Diensterkennung und alle Datenpfade, die im Rahmen einer Discovery-Sitzung für Publisher oder Abonnenten eingerichtet wurden. Mit der Methode isInstantCommunicationModeSupported() kannst du feststellen, ob ein Gerät den Modus für die sofortige Kommunikation unterstützt.

Verbesserungen in Android 12 (API-Level 31)

In Android 12 (API-Level 31) wurden einige Verbesserungen an Wi‑Fi Aware vorgenommen:

  • Auf Geräten mit Android 12 (API-Level 31) oder höher können Sie den onServiceLost()-Callback verwenden, um benachrichtigt zu werden, wenn Ihre App einen erkannten Dienst verloren hat, weil der Dienst beendet wird oder sich außerhalb des Bereichs befindet.
  • Die Einrichtung von Wi-Fi Aware-Datenpfaden wurde vereinfacht. Bei früheren Versionen wurde die MAC-Adresse des Initiators über L2-Messaging angegeben, was zu einer Latenz führte. Auf Geräten mit Android 12 und höher kann der Responder (Server) so konfiguriert werden, dass er jeden Peer akzeptiert. Das bedeutet, dass er die MAC-Adresse des Initiators nicht im Voraus kennen muss. Dadurch wird die Inbetriebnahme des Datenpfads beschleunigt und es können mehrere Punkt-zu-Punkt-Links mit nur einer Netzwerkanfrage erstellt werden.
  • Apps mit Android 12 oder höher können die Methode WifiAwareManager.getAvailableAwareResources() verwenden, um die Anzahl der derzeit verfügbaren Datenpfade sowie Veröffentlichungs- und Abositzungen abzurufen. So kann die App feststellen, ob genügend Ressourcen verfügbar sind, um die gewünschten Funktionen auszuführen.

Ersteinrichtung

So richten Sie Ihre App für die Suche und Vernetzung mit Wi‑Fi Aware ein:

  1. Fordern Sie die folgenden Berechtigungen im Manifest Ihrer App 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üfen Sie mit der PackageManager API, ob das Gerät Wi‑Fi Aware unterstützt, wie unten gezeigt:

    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. Wi‑Fi Aware ist möglicherweise auf dem Gerät vorhanden, aber derzeit nicht verfügbar, weil der Nutzer WLAN oder Standort deaktiviert hat. Je nach Hardware- und Firmwarefunktionen unterstützen einige Geräte möglicherweise keine WLAN-Aware-Funktion, wenn Wi‑Fi Direct, SoftAP oder Tethering verwendet wird. Wenn Sie prüfen möchten, ob WLAN Aware derzeit verfügbar ist, rufen Sie isAvailable() an.

    Die Verfügbarkeit von Wi‑Fi Aware kann sich jederzeit ändern. Ihre App sollte ein BroadcastReceiver registrieren, um ACTION_WIFI_AWARE_STATE_CHANGED zu empfangen, das gesendet wird, wenn sich die Verfügbarkeit ändert. Wenn Ihre App die Broadcastabsicht empfängt, sollten alle vorhandenen Sitzungen verworfen werden (angenommen, der Wi‑Fi Aware-Dienst wurde unterbrochen). Prüfen Sie dann den aktuellen Verfügbarkeitsstatus und passen Sie das Verhalten entsprechend an. Beispiel:

    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 Übertragungen.

Sitzung abrufen

Damit Sie Wi‑Fi Aware verwenden können, muss Ihre App eine WifiAwareSession abrufen, indem sie attach() aufruft. Mit dieser Methode wird Folgendes ausgeführt:

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

Wenn die App erfolgreich angehängt wurde, führt das System den onAttached()-Callback aus. Dieser Callback stellt ein WifiAwareSession-Objekt bereit, das von deiner App für alle weiteren Sitzungsvorgänge verwendet werden sollte. Eine Anwendung kann die Sitzung verwenden, um einen Dienst zu veröffentlichen oder einen Dienst zu abonnieren.

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

Dienst veröffentlichen

Wenn Sie einen Dienst für andere sichtbar machen möchten, rufen Sie die Methode publish() auf. Verwenden Sie dazu folgende Parameter:

  • PublishConfig gibt den Namen des Dienstes und andere Konfigurationseigenschaften wie den Abgleichsfilter an.
  • Mit DiscoverySessionCallback werden die Aktionen festgelegt, die ausgeführt werden sollen, wenn Ereignisse auftreten, z. B. wenn der Abonnent eine Nachricht erhält.

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

Wenn die Veröffentlichung erfolgreich war, wird die Callback-Methode onPublishStarted() aufgerufen.

Wenn Geräte, auf denen übereinstimmende Abonnenten-Apps ausgeführt werden, nach der Veröffentlichung in den WLAN-Bereich des Publishing-Geräts wechseln, erkennen die Abonnenten den Dienst. Wenn ein Abonnent einen Verlag oder Webpublisher findet, erhält der Verlag oder Webpublisher keine Benachrichtigung. Wenn der Abonnent dem Verlag oder Webpublisher jedoch eine Nachricht sendet, erhält der Verlag oder Webpublisher eine Benachrichtigung. In diesem Fall wird die Callback-Methode onMessageReceived() aufgerufen. Mit dem Argument PeerHandle dieser Methode kannst du eine Nachricht an den Abonnenten zurücksenden oder eine Verbindung zu ihm herstellen.

Wenn Sie die Veröffentlichung des Dienstes beenden möchten, rufen Sie DiscoverySession.close() auf. Discovery-Sitzungen sind mit ihrer übergeordneten WifiAwareSession verknüpft. Wenn die übergeordnete Sitzung geschlossen wird, werden auch die zugehörigen Erkennungssitzungen geschlossen. Auch verworfene Objekte werden geschlossen. Das System kann jedoch nicht garantieren, dass nicht mehr gültige Sitzungen geschlossen werden. Wir empfehlen daher, die close()-Methoden explizit aufzurufen.

Einen Dienst abonnieren

Wenn Sie einen Dienst abonnieren möchten, rufen Sie die Methode subscribe() auf. Verwenden Sie dazu folgende Parameter:

  • SubscribeConfig gibt den Namen des zu abonnierenden Dienstes und andere Konfigurationseigenschaften wie den Abgleichsfilter an.
  • Mit DiscoverySessionCallback werden die Aktionen angegeben, die ausgeführt werden sollen, wenn Ereignisse auftreten, z. B. wenn ein Publisher gefunden 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 Vorgang zum Abonnieren erfolgreich war, ruft das System den onSubscribeStarted()-Callback in deiner App auf. Da du mit dem Argument SubscribeDiscoverySession im Callback mit einem Publisher kommunizieren kannst, nachdem deine App einen gefunden hat, solltest du diese Referenz speichern. Du kannst die Abositzung jederzeit aktualisieren, indem du updateSubscribe() in der Discovery-Sitzung aufrufst.

In diesem Fall wartet dein Abo darauf, dass übereinstimmende Publisher in Reichweite des WLANs 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 herzustellen.

Wenn Sie ein Abo nicht mehr nutzen möchten, rufen Sie DiscoverySession.close() auf. Discovery-Sitzungen sind mit ihrer übergeordneten WifiAwareSession verknüpft. Wenn die übergeordnete Sitzung geschlossen wird, werden auch die zugehörigen Erkennungssitzungen geschlossen. Auch verworfene Objekte werden geschlossen. Das System kann jedoch nicht garantieren, dass nicht mehr gültige Sitzungen geschlossen werden. Wir empfehlen daher, die close()-Methoden explizit aufzurufen.

Eine Nachricht posten

Wenn Sie eine Nachricht an ein anderes Gerät senden möchten, benötigen Sie folgende Objekte:

Wenn Sie eine Nachricht senden möchten, wählen Sie sendMessage(). Die folgenden Callbacks können dann auftreten:

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

Die PeerHandle ist zwar erforderlich, um mit Peers zu kommunizieren, sollte aber nicht als dauerhafte Kennung von Peers verwendet werden. Die Anwendung kann auch Kennungen höherer Ebene verwenden, die im Discovery-Dienst selbst oder in nachfolgenden Nachrichten eingebettet sind. Sie können eine Kennung mit der Methode setMatchFilter() oder setServiceSpecificInfo() von PublishConfig oder SubscribeConfig in den Discovery-Dienst einbetten. Die Methode setMatchFilter() wirkt sich auf die Erkennung aus, die Methode setServiceSpecificInfo() hingegen nicht.

Wenn Sie eine Kennung in eine Nachricht einbetten, müssen Sie das Byte-Array der Nachricht so ändern, dass es eine Kennung enthält (z. B. als erste paar Bytes).

Verbindung erstellen

Wi‑Fi Aware unterstützt die Client-Server-Netzwerkverbindung zwischen zwei Wi‑Fi Aware-Geräten.

So richten Sie die Client-Server-Verbindung ein:

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

  2. Sobald der Abonnent den Publisher gefunden hat, sende eine Nachricht vom Abonnenten an den Publisher.

  3. Starte einen ServerSocket auf dem Publisher-Gerät und lege den Port fest oder erhalte ihn:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Verwende ConnectivityManager, um ein Wi‑Fi Aware-Netzwerk beim Publisher mit einer WifiAwareNetworkSpecifier anzufordern. Gib dabei die Discovery-Sitzung und die PeerHandle des Abonnenten an, die du aus der vom Abonnenten gesendeten 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. Sobald der Publisher ein Netzwerk anfordert, sollte er dem Abonnenten eine Nachricht senden.

  6. Sobald der Abonnent die Nachricht vom Publisher erhalten hat, fordere mit derselben Methode wie beim Publisher ein WLAN-Aware-Netzwerk beim Abonnenten an. Geben Sie beim Erstellen der NetworkSpecifier keinen Port an. Die entsprechenden Rückrufmethoden werden aufgerufen, wenn die Netzwerkverbindung verfügbar, geändert oder getrennt ist.

  7. Sobald die Methode onAvailable() auf dem Abonnenten aufgerufen wurde, ist ein Network-Objekt verfügbar, mit dem Sie ein Socket öffnen können, um mit dem ServerSocket auf dem Verlag oder Webpublisher zu kommunizieren. Sie müssen jedoch die IPv6-Adresse und den IPv6-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 Sie die Netzwerkverbindung hergestellt haben, rufen Sie unregisterNetworkCallback() auf.

Rangfolge von Peers und standortbezogene Suche

Ein Gerät mit Wi‑Fi RTT-Standortfunktionen kann die Entfernung zu Peers direkt messen und diese Informationen verwenden, um die Wi‑Fi Aware-Diensterkennung einzuschränken.

Die Wi-Fi RTT API ermöglicht die direkte Standortermittlung zu einem Wi-Fi Aware-Peer entweder über dessen MAC-Adresse oder über seinen PeerHandle.

Die Wi-Fi Aware-Erkennung kann so eingeschränkt werden, dass nur Dienste 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,das sich nicht näher als 3 Meter (3.000 mm) und nicht weiter als 10 Meter (10.000 mm) entfernt befindet.

Damit Geofencing aktiviert werden kann, müssen sowohl der Publisher als auch der Abonnent Maßnahmen ergreifen:

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

    Wenn der Publisher kein Bereichserkennung aktiviert, werden alle vom Abonnenten angegebenen Geofence-Einschränkungen ignoriert und eine normale Erkennung durchgeführt, wobei die Entfernung ignoriert wird.

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

    Bei beiden Werten bedeutet eine nicht angegebene Entfernung, dass es kein Limit gibt. Wenn Sie nur den maximalen Abstand angeben, wird ein Mindestabstand von 0 vorausgesetzt. Wenn Sie nur die Mindestentfernung angeben, gibt es kein Maximum.

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