Le funzionalità Wi-Fi Aware consentono ai dispositivi con Android 8.0 (livello API 26) e versioni successive di rilevare e connettersi direttamente l'uno all'altro senza alcun altro tipo di connettività tra loro. Wi-Fi Aware è anche noto come Nearby Awareness Networking (NAN).
Il networking Wi-Fi Aware funziona formando cluster con dispositivi vicini o creando un nuovo cluster se il dispositivo è il primo in un'area. Questo comportamento di clustering si applica all'intero dispositivo ed è gestito dal servizio di sistema Wi-Fi Aware; le app non hanno alcun controllo sul comportamento di clustering. Le app usano le API Wi-Fi Aware per comunicare con il servizio di sistema Wi-Fi Aware, che gestisce l'hardware Wi-Fi Aware sul dispositivo.
Le API Wi-Fi Aware consentono alle app di eseguire le seguenti operazioni:
Scopri altri dispositivi: l'API ha un meccanismo per trovare altri dispositivi nelle vicinanze. Il processo inizia quando un dispositivo pubblica uno o più servizi rilevabili. Quando un dispositivo si abbona a uno o più servizi ed entra nell'intervallo Wi-Fi dell'editore, l'abbonato riceve una notifica che indica che è stato rilevato un editore corrispondente. Dopo aver scoperto un editore, l'abbonato può inviare un breve messaggio o stabilire una connessione di rete con il dispositivo rilevato. I dispositivi possono essere contemporaneamente sia publisher sia abbonati.
Crea una connessione di rete: dopo che due dispositivi si sono rilevati l'uno con l'altro, possono creare una connessione di rete Wi-Fi Aware bidirezionale senza un punto di accesso.
Le connessioni di rete Wi-Fi Aware supportano velocità di velocità effettiva più elevate su distanze più lunghe rispetto alle connessioni Bluetooth. Questi tipi di connessioni sono utili per le app che condividono grandi quantità di dati tra gli utenti, ad esempio le app per la condivisione di foto.
Miglioramenti ad Android 12 (livello API 31)
Android 12 (livello API 31) aggiunge alcuni miglioramenti al Wi-Fi Aware:
- Sui dispositivi con Android 12 (livello API 31) o versioni successive, puoi utilizzare il callback
onServiceLost()
per ricevere un avviso quando la tua app ha perso un servizio rilevato a causa dell'arresto o dello spostamento del servizio fuori intervallo. - La configurazione dei percorsi dati di Wi-Fi Aware è stata semplificata. Le versioni precedenti utilizzavano la messaggistica L2 per fornire l'indirizzo MAC dell'iniziatore, il che comportava la latenza. Sui dispositivi con Android 12 e versioni successive, il risponditore (server) può essere configurato per accettare qualsiasi peer, ovvero non deve conoscere in anticipo l'indirizzo MAC dell'iniziatore. Ciò accelera la generazione dei percorsi dati e consente più link point-to-point con una sola richiesta di rete.
- Le app eseguite su Android 12 o versioni successive possono utilizzare il metodo
WifiAwareManager.getAvailableAwareResources()
per ottenere il numero di percorsi di dati attualmente disponibili, pubblicare sessioni e sottoscrivere sessioni. Ciò può aiutare l'app a determinare se sono disponibili risorse sufficienti per eseguire la funzionalità desiderata.
Configurazione iniziale
Per configurare l'app per l'utilizzo del rilevamento e della rete di Wi-Fi Aware, svolgi i seguenti passaggi:
Richiedi le autorizzazioni seguenti nel file manifest dell'app:
<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" />
Verifica se il dispositivo supporta il Wi-Fi Aware con l'API
PackageManager
, come mostrato di seguito:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
Verifica se il Wi-Fi Aware è attualmente disponibile. Il Wi-Fi Aware potrebbe essere presente sul dispositivo, ma potrebbe non essere attualmente disponibile perché l'utente ha disattivato il Wi-Fi o la geolocalizzazione. A seconda delle funzionalità hardware e firmware, alcuni dispositivi potrebbero non supportare il Wi-Fi Aware se è in uso il Wi-Fi Direct, SoftAP o il tethering. Per controllare se il servizio Wi-Fi Aware è attualmente disponibile, chiama il numero
isAvailable()
.La disponibilità di Wi-Fi Aware può cambiare in qualsiasi momento. La tua app deve registrare un
BroadcastReceiver
per ricevereACTION_WIFI_AWARE_STATE_CHANGED
, che viene inviato ogni volta che la disponibilità cambia. Quando l'app riceve l'intent di trasmissione, deve ignorare tutte le sessioni esistenti (supponendo che il servizio Wi-Fi Aware sia stato interrotto), quindi controllare lo stato attuale di disponibilità e regolarne il comportamento di conseguenza. Ecco alcuni esempi: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);
Per ulteriori informazioni, vedi Trasmissioni.
Ottenere una sessione
Per iniziare a utilizzare Wi-Fi Aware, la tua app deve ottenere un
WifiAwareSession
chiamando il
attach()
. Questo metodo consente di:
- Attiva l'hardware Wi-Fi Aware.
- Unisce o forma un cluster Wi-Fi Aware.
- Crea una sessione Wi-Fi Aware con uno spazio dei nomi univoco che funge da contenitore per tutte le sessioni di rilevamento create al suo interno.
Se l'app si collega correttamente, il sistema esegue il callback onAttached()
.
Questo callback fornisce un oggetto WifiAwareSession
che l'app deve utilizzare per tutte le ulteriori operazioni della sessione. Un'app può utilizzare la sessione per pubblicare un servizio o sottoscrivere un servizio.
L'app deve chiamare
attach()
una sola volta. Se l'app chiama attach()
più volte, l'app riceve una sessione diversa per ogni chiamata, ognuna con il proprio spazio dei nomi. Questo può essere utile in scenari complessi, ma in genere dovrebbe essere evitato.
Pubblicare un servizio
Per rendere un servizio rilevabile, chiama il metodo publish()
, che prevede i seguenti parametri:
PublishConfig
specifica il nome del servizio e altre proprietà di configurazione, come il filtro di corrispondenza.DiscoverySessionCallback
specifica le azioni da eseguire quando si verificano eventi, ad esempio quando il sottoscrittore riceve un messaggio.
Ecco un esempio:
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);
Se la pubblicazione ha esito positivo, viene chiamato il
metodo di callback onPublishStarted()
.
Dopo la pubblicazione, quando i dispositivi che eseguono app degli abbonati corrispondenti passano nel raggio d'azione del Wi-Fi del dispositivo di pubblicazione, gli abbonati scoprono il servizio. Quando un sottoscrittore rileva un editore, l'editore non riceve una notifica. Se il sottoscrittore invia un messaggio all'editore, tuttavia, l'editore riceve una notifica. In questo caso, viene richiamato il metodo di callback onMessageReceived()
. Puoi utilizzare l'argomento PeerHandle
di questo metodo per inviare un messaggio al sottoscrittore o creare una connessione al sottoscrittore.
Per interrompere la pubblicazione del servizio, chiama
DiscoverySession.close()
.
Le sessioni di rilevamento sono associate al relativo elemento padre
WifiAwareSession
. Se la sessione principale è chiusa, vengono chiuse anche le relative sessioni di rilevamento associate. Anche se gli oggetti eliminati vengono chiusi, il sistema non garantisce la chiusura delle sessioni fuori ambito, perciò ti consigliamo di chiamare esplicitamente i metodi close()
.
Abbonarsi a un servizio
Per sottoscrivere un servizio, chiama il metodo subscribe()
, che accetta i seguenti parametri:
-
SubscribeConfig
specifica il nome del servizio a cui sottoscrivere e altre proprietà di configurazione, come il filtro di corrispondenza. DiscoverySessionCallback
specifica le azioni da eseguire quando si verificano eventi, ad esempio quando viene rilevato un editore.
Ecco un esempio:
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);
Se l'operazione di abbonamento ha esito positivo, il sistema chiama il callback onSubscribeStarted()
nella tua app. Poiché puoi utilizzare l'argomento SubscribeDiscoverySession
nel callback per comunicare con un publisher dopo che l'app ne ha individuato uno, devi salvare questo riferimento. Puoi aggiornare la sessione di abbonamento in qualsiasi momento
chiamando
updateSubscribe()
durante la sessione di rilevamento.
A questo punto, l'abbonamento attende che gli editori corrispondenti entrino nel raggio del Wi-Fi. In questi casi, il sistema esegue il metodo di callback onServiceDiscovered()
. Puoi utilizzare l'argomento PeerHandle
di questo callback per inviare un messaggio o creare una connessione con quel publisher.
Per interrompere l'abbonamento a un servizio, chiama
DiscoverySession.close()
.
Le sessioni di rilevamento sono associate al relativo elemento padre
WifiAwareSession
. Se la sessione principale è chiusa, vengono chiuse anche le relative sessioni di rilevamento associate. Anche se gli oggetti eliminati vengono chiusi, il sistema non garantisce la chiusura delle sessioni fuori ambito, perciò ti consigliamo di chiamare esplicitamente i metodi close()
.
Inviare un messaggio
Per inviare un messaggio a un altro dispositivo, devi disporre dei seguenti oggetti:
Un
DiscoverySession
. Questo oggetto consente di chiamaresendMessage()
. La tua app riceve unDiscoverySession
pubblicando un servizio o abbonandosi a un servizio.PeerHandle
dell'altro dispositivo per instradare il messaggio. La tua app acquisiscePeerHandle
di un altro dispositivo in due modi:- L'app pubblica un servizio e riceve un messaggio da un abbonato.
La tua app riceve il valore
PeerHandle
dell'abbonato dal callbackonMessageReceived()
. - La tua app è abbonata a un servizio. Poi, quando scopre un publisher corrispondente, la tua app riceve il valore
PeerHandle
del publisher dal callback dionServiceDiscovered()
.
- L'app pubblica un servizio e riceve un messaggio da un abbonato.
La tua app riceve il valore
Per inviare un messaggio, chiama il numero
sendMessage()
. Potrebbero quindi verificarsi i seguenti callback:
- Quando il messaggio viene ricevuto correttamente dal peer, il sistema chiama il callback
onMessageSendSucceeded()
nell'app di invio. - Quando il peer riceve un messaggio, il sistema chiama il callback
onMessageReceived()
nell'app di ricezione.
Sebbene PeerHandle
sia necessario per comunicare con i peer, non dovresti
farlo come identificatore permanente dei peer. Gli identificatori di livello superiore possono essere utilizzati
dall'applicazione incorporata nel servizio di rilevamento stesso o nei
messaggi successivi. Puoi incorporare un identificatore nel servizio di rilevamento con il metodo
setMatchFilter()
o
setServiceSpecificInfo()
di PublishConfig
o
SubscribeConfig
. Il metodo setMatchFilter()
influisce sul rilevamento, mentre il metodo setServiceSpecificInfo()
non influisce sul rilevamento.
L'incorporamento di un identificatore in un messaggio implica la modifica dell'array di byte del messaggio per includere un identificatore (ad esempio, come primi due byte).
Creare una connessione
Wi-Fi Aware supporta la rete client-server tra due dispositivi Wi-Fi Aware.
Per configurare la connessione client-server:
Utilizza il rilevamento Wi-Fi Aware per pubblicare un servizio (sul server) e abbonarsi a un servizio (sul client).
Una volta che il sottoscrittore rileva il publisher, invia un messaggio dal sottoscrittore al publisher.
Avvia un elemento
ServerSocket
sul dispositivo dell'editore e imposta o recupera la porta:Kotlin
val ss = ServerSocket(0) val port = ss.localPort
Java
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
Utilizza
ConnectivityManager
per richiedere una rete Wi-Fi Aware sull'editore utilizzandoWifiAwareNetworkSpecifier
, specificando la sessione di rilevamento e ilPeerHandle
dell'abbonato, che hai ottenuto dal messaggio trasmesso dall'abbonato: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);
Quando l'editore richiede una rete, deve inviare un messaggio al sottoscrittore.
Quando l'abbonato riceve il messaggio dall'editore, richiedi una rete Wi-Fi Aware sull'abbonato utilizzando lo stesso metodo utilizzato per l'editore. Non specificare una porta durante la creazione di
NetworkSpecifier
. I metodi di callback appropriati vengono chiamati quando la connessione di rete è disponibile, modificata o persa.Dopo che il metodo
onAvailable()
viene chiamato sull'abbonato, è disponibile un oggettoNetwork
con il quale puoi aprire unaSocket
per comunicare conServerSocket
sul publisher, ma devi conoscere l'indirizzo IPv6 e la porta diServerSocket
. Puoi ottenere questi dati dall'oggettoNetworkCapabilities
fornito nel callbackonCapabilitiesChanged()
: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);
Al termine della connessione di rete, chiama il numero
unregisterNetworkCallback()
.
Gestione dei peer e rilevamento basato sulla posizione
Un dispositivo con funzionalità di geolocalizzazione Wi-Fi RTT può misurare direttamente la distanza dai peer e utilizzare queste informazioni per limitare il rilevamento del servizio Wi-Fi Aware.
L'API Wi-Fi RTT consente il routing diretto a un peer Wi-Fi Aware utilizzando l'indirizzo MAC o il suo PeerHandle.
Il rilevamento di Wi-Fi Aware può essere limitato a rilevare solo i servizi all'interno di un determinato recinto virtuale. Ad esempio, puoi configurare un recinto virtuale che consenta il rilevamento di un dispositivo che pubblica un servizio "Aware_File_Share_Service_Name"
che non superi i 3 metri di distanza (indicati come 3000 mm) e non oltre 10 metri (indicati come 10.000 mm).
Per attivare il geofencing, sia l'editore sia l'abbonato devono intervenire:
L'editore deve abilitare il raggio d'azione sul servizio pubblicato utilizzando setRangingEnabled(true).
Se il publisher non abilita il rilevamento, tutti i vincoli di recinto virtuale specificati dal sottoscrittore vengono ignorati e viene eseguito il rilevamento normale, ignorando la distanza.
L'abbonato deve specificare un recinto virtuale utilizzando una combinazione di setMinDistanceMm e setMaxDistanceMm.
Per entrambi i valori, una distanza non specificata non implica alcun limite. Specificare solo la distanza massima implica una distanza minima pari a 0. Specificare solo la distanza minima non implica alcun valore massimo.
Quando viene rilevato un servizio peer all'interno di un recinto virtuale, viene attivato il callback onServiceDiscovered WithinRange, che fornisce la distanza misurata dal peer. L'API RTT Wi-Fi diretta può quindi essere chiamata in base alle esigenze per misurare la distanza in un secondo momento.