Wi-Fi Aware – Übersicht

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

Wi-Fi Aware-Netzwerke funktionieren, indem Cluster mit benachbarten Geräten gebildet werden oder ein neuer Cluster erstellt wird, 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 die folgenden Vorgänge ausführen:

  • Andere Geräte finden:Die API bietet einen Mechanismus, um andere 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 die WLAN-Reichweite des Publishers gelangt, erhält der Abonnent eine Benachrichtigung, dass ein passender Publisher gefunden wurde. Nachdem der Abonnent einen Publisher gefunden hat, kann er entweder eine kurze Nachricht senden oder eine Netzwerkverbindung mit dem gefundenen 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 höhere Durchsatzraten über größere Entfernungen als Bluetooth-Verbindungen. Diese Arten von Verbindungen sind nützlich für Apps, die große Datenmengen zwischen Nutzern teilen, z. B. Apps zum Teilen von Fotos.

Verbesserungen in Android 13 (API-Level 33)

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

Verbesserungen in Android 12 (API-Level 31)

In Android 12 (API-Level 31) wurden einige Verbesserungen für Wi-Fi Aware hinzugefügt:

  • Auf Geräten mit Android 12 (API-Level 31) oder höher können Sie den Callback onServiceLost() verwenden, um benachrichtigt zu werden, wenn Ihre App einen gefundenen Dienst verloren hat, weil der Dienst beendet wurde oder sich außerhalb der Reichweite befindet.
  • Die Einrichtung von Wi-Fi Aware-Datenpfaden wurde vereinfacht. In früheren Versionen wurden L2-Nachrichten verwendet, um die MAC-Adresse des Initiators bereitzustellen, was zu 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 Bereitstellung des Datenpfads beschleunigt und mehrere Punkt-zu-Punkt-Verbindungen mit nur einer Netzwerkanfrage ermöglicht.
  • Apps, die unter Android 12 oder höher ausgeführt werden, können die Methode WifiAwareManager.getAvailableAwareResources() verwenden, um die Anzahl der derzeit verfügbaren Datenpfade, Veröffentlichungssitzungen und Abositzungen abzurufen. So kann die App feststellen, ob genügend Ressourcen verfügbar sind, um die gewünschte Funktion auszuführen.

Ersteinrichtung

So richten Sie Ihre App für die Verwendung von Wi-Fi Aware-Erkennung und -Netzwerken ein:

  1. Fordern Sie im Manifest Ihrer 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 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, 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 die Standortermittlung deaktiviert hat. Je nach Hardware- und Firmwarefunktionen unterstützen einige Geräte Wi-Fi Aware möglicherweise nicht, wenn Wi-Fi Direct, SoftAP oder Tethering verwendet wird. Mit isAvailable() kannst du prüfen, ob Wi-Fi Aware derzeit verfügbar ist.

    Die Verfügbarkeit von Wi-Fi Aware kann sich jederzeit ändern. Deine App sollte einen BroadcastReceiver registrieren, um ACTION_WIFI_AWARE_STATE_CHANGED zu empfangen, der immer dann gesendet wird, wenn sich die Verfügbarkeit ändert. Wenn Ihre App den Broadcast-Intent empfängt, sollte sie alle vorhandenen Sitzungen verwerfen (unter der Annahme, dass der Wi-Fi Aware-Dienst unterbrochen wurde), dann den aktuellen Verfügbarkeitsstatus prüfen und ihr Verhalten entsprechend anpassen. 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 Broadcasts.

Sitzung abrufen

Damit Sie Wi-Fi Aware verwenden können, muss Ihre App durch Aufrufen von attach() ein WifiAwareSession-Objekt abrufen. Diese Methode bewirkt Folgendes:

  • Aktiviert die Wi-Fi Aware-Hardware.
  • Tritt einem Wi-Fi Aware-Cluster bei oder bildet einen.
  • 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 Callback onAttached() aus. Dieser Callback stellt ein WifiAwareSession-Objekt bereit, das Ihre App für alle weiteren Sitzungsvorgänge verwenden sollte. Eine App kann die Sitzung verwenden, um einen Dienst zu veröffentlichen oder einen Dienst zu abonnieren.

Ihre App sollte attach() nur einmal aufrufen. Wenn Ihre App attach() mehrmals aufruft, erhält sie für jeden Aufruf eine andere Sitzung, jeweils mit einem eigenen Namespace. Dies kann in 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() mit den folgenden Parametern auf:

  • PublishConfig gibt den Namen des Dienstes und andere Konfigurationseigenschaften an, z. B. den Abgleichsfilter.
  • DiscoverySessionCallback gibt die Aktionen an, die ausgeführt werden sollen, wenn Ereignisse auftreten, 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);

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

Wenn Geräte mit passenden Abonnenten-Apps nach der Veröffentlichung in die WLAN-Reichweite des veröffentlichenden Geräts gelangen, erkennen die Abonnenten den Dienst. Wenn ein Abonnent einen Publisher findet, erhält der Publisher keine Benachrichtigung. Wenn der Abonnent jedoch eine Nachricht an den Publisher sendet, erhält der Publisher eine Benachrichtigung. In diesem Fall wird die Callback-Methode onMessageReceived() aufgerufen. Sie können das Argument PeerHandle aus dieser Methode verwenden, um eine Nachricht an den Abonnenten zurückzusenden oder eine Verbindung zu ihm herzustellen.

Wenn Sie die Veröffentlichung des Dienstes beenden möchten, rufen Sie DiscoverySession.close() auf. Discovery-Sitzungen sind mit dem übergeordneten WifiAwareSession verknüpft. Wenn die übergeordnete Sitzung geschlossen wird, werden auch die zugehörigen Erkennungssitzungen geschlossen. Verworfene Objekte werden zwar ebenfalls geschlossen, das System garantiert jedoch nicht, wann Sitzungen außerhalb des Gültigkeitsbereichs geschlossen werden. Daher empfehlen wir, die close()-Methoden explizit aufzurufen.

Dienst abonnieren

Wenn Sie einen Dienst abonnieren möchten, rufen Sie die Methode subscribe() mit den folgenden Parametern auf:

  • SubscribeConfig gibt den Namen des Dienstes an, den Sie abonnieren möchten, sowie andere Konfigurationseigenschaften, z. B. den Abgleichsfilter.
  • DiscoverySessionCallback gibt die Aktionen an, die ausgeführt werden sollen, wenn Ereignisse auftreten, 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 Abovorgang erfolgreich ist, ruft das System den Callback onSubscribeStarted() in Ihrer App auf. Da Sie das Argument SubscribeDiscoverySession im Callback verwenden können, um mit einem Publisher zu kommunizieren, nachdem Ihre App einen gefunden hat, sollten Sie diese Referenz speichern. Sie können die Abositzung jederzeit aktualisieren, indem Sie updateSubscribe() für die Erkennungssitzung aufrufen.

An diesem Punkt wartet Ihr Abo darauf, dass übereinstimmende Publisher in die WLAN-Reichweite 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 einen Dienst nicht mehr abonnieren möchten, rufen Sie DiscoverySession.close() auf. Discovery-Sitzungen sind mit dem übergeordneten Element WifiAwareSession verknüpft. Wenn die übergeordnete Sitzung geschlossen wird, werden auch die zugehörigen Erkennungssitzungen geschlossen. Verworfene Objekte werden zwar ebenfalls geschlossen, das System garantiert jedoch nicht, wann Sitzungen außerhalb des Gültigkeitsbereichs geschlossen werden. Daher empfehlen wir, die close()-Methoden explizit aufzurufen.

Nachricht senden

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

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

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

Obwohl die PeerHandle für die Kommunikation mit Peers erforderlich ist, sollten Sie sich nicht darauf als permanente Kennung von Peers verlassen. Übergeordnete Kennungen können von der Anwendung verwendet werden, die in den Discovery-Dienst selbst oder in nachfolgende 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() dagegen 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 die ersten paar 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. 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, senden Sie eine Nachricht vom Abonnenten an den Publisher.

  3. Starten Sie einen ServerSocket auf dem Publisher-Gerät und legen Sie den Port fest oder rufen Sie ihn ab:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. Verwenden Sie den ConnectivityManager, um ein Wi-Fi Aware-Netzwerk auf dem Publisher mit einem WifiAwareNetworkSpecifier anzufordern. Geben Sie dabei die Erkennungssitzung und die PeerHandle des Abonnenten an, die Sie aus der vom Abonnenten übertragenen Nachricht erhalten haben:

    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 eine Nachricht an den Abonnenten senden.

  6. Sobald der Abonnent die Nachricht vom Publisher empfängt, fordert er ein Wi-Fi Aware-Netzwerk an. Dazu wird dieselbe Methode wie beim Publisher verwendet. Geben Sie beim Erstellen des NetworkSpecifier keinen Port an. Die entsprechenden Callback-Methoden werden aufgerufen, wenn die Netzwerkverbindung verfügbar ist, geändert wird oder verloren geht.

  7. Sobald die Methode onAvailable() für den Abonnenten aufgerufen wurde, ist ein Network-Objekt verfügbar, mit dem Sie einen Socket öffnen können, um mit dem ServerSocket auf dem Publisher zu kommunizieren. Sie müssen jedoch die IPv6-Adresse und den Port des ServerSocket kennen. Diese erhalten Sie über das 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 nicht mehr benötigen, rufen Sie unregisterNetworkCallback() auf.

Entfernungsmessung zu Peers und standortbezogene Erkennung

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

Die Wi-Fi RTT API ermöglicht die direkte Entfernungsmessung zu einem Wi-Fi Aware-Peer über seine MAC-Adresse oder sein 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,der nicht näher als 3 Meter (angegeben als 3.000 mm) und nicht weiter als 10 Meter (angegeben als 10.000 mm) entfernt ist.

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

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

    Wenn der Publisher die Entfernungsmessung nicht aktiviert, werden alle vom Abonnenten angegebenen Geofence-Einschränkungen ignoriert und die normale Erkennung wird ohne Berücksichtigung der Entfernung durchgeführt.

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

    Wenn Sie keinen Wert angeben, gilt kein Limit. Wenn nur die maximale Entfernung angegeben wird, wird eine Mindestentfernung von 0 angenommen. Wenn Sie nur den Mindestabstand angeben, gibt es keinen Höchstabstand.

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