Wi-Fi Direct (noto anche come peer-to-peer o P2P) consente alla tua applicazione di trovare rapidamente dispositivi nelle vicinanze e interagire con loro, a una distanza superiore alle funzionalità del Bluetooth.
Le API Wi-Fi Direct (P2P) consentono alle applicazioni di connettersi ai dispositivi nelle vicinanze senza dover connettersi a una rete o a un hotspot. Se la tua app è progettata per far parte di una rete sicura a corto raggio, Wi-Fi Direct è un'opzione più adatta rispetto al networking Wi-Fi ad hoc tradizionale per i seguenti motivi:
- Wi-Fi Direct supporta la crittografia WPA2. (Alcune reti ad hoc supportano solo la crittografia WEP.)
- I dispositivi possono trasmettere i servizi che forniscono, il che aiuta gli altri dispositivi a scoprire più facilmente i peer adatti.
- Quando determina quale dispositivo deve essere il proprietario del gruppo per la rete, Wi-Fi Direct esamina la gestione dell'alimentazione, l'interfaccia utente e le funzionalità di servizio di ciascun dispositivo e utilizza queste informazioni per scegliere il dispositivo in grado di gestire le responsabilità del server nel modo più efficace.
- Android non supporta la modalità Wi-Fi ad hoc.
Questa lezione mostra come trovare e connettersi a dispositivi nelle vicinanze utilizzando il Wi-Fi P2P.
Configurare le autorizzazioni dell'applicazione
Per utilizzare Wi-Fi Direct, aggiungi le autorizzazioni
ACCESS_FINE_LOCATION
,
CHANGE_WIFI_STATE
,
ACCESS_WIFI_STATE
e
INTERNET
al manifest.
Se la tua app ha come target Android 13 (livello API 33) o versioni successive, aggiungi anche l'autorizzazione
NEARBY_WIFI_DEVICES
al file manifest. Wi-Fi
Direct non richiede una connessione a internet, ma utilizza i socket Java
standard, che richiedono l'autorizzazione INTERNET
. Pertanto, per utilizzare Wi-Fi Direct devi disporre delle seguenti autorizzazioni:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.nsdchat" ... <!-- 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:required="true" 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" /> <uses-permission android:required="true" android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:required="true" android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:required="true" android:name="android.permission.INTERNET"/> ...
Oltre alle autorizzazioni precedenti, le seguenti API richiedono anche l'attivazione della modalità di localizzazione:
Configurare un ricevitore di trasmissione e un gestore peer-to-peer
Per utilizzare Wi-Fi Direct, devi ascoltare gli intent di trasmissione che comunicano alla tua
applicazione quando si sono verificati determinati eventi. Nella tua applicazione, crea un'istanza di
un IntentFilter
e impostalo in modo che ascolti quanto segue:
WIFI_P2P_STATE_CHANGED_ACTION
- Indica se Wi-Fi Direct è abilitato
WIFI_P2P_PEERS_CHANGED_ACTION
- Indica che l'elenco dei peer disponibili è cambiato.
WIFI_P2P_CONNECTION_CHANGED_ACTION
-
Indica che lo stato della connettività Wi-Fi Direct è cambiato. A partire da
Android 10, questa impostazione non è persistente. Se la tua app si è basata sulla ricezione di queste
trasmissioni alla registrazione perché erano permanenti, utilizza il metodo
get
appropriato all'inizializzazione per ottenere le informazioni. WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
-
Indica che i dettagli di configurazione di questo dispositivo sono stati modificati. A partire da
Android 10, questa impostazione non è persistente. Se la tua app si è basata sulla ricezione di queste
trasmissioni alla registrazione perché erano permanenti, utilizza il metodo
get
appropriato all'inizializzazione per ottenere le informazioni.
Kotlin
private val intentFilter = IntentFilter() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) // Indicates a change in the Wi-Fi Direct status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) // Indicates the state of Wi-Fi Direct connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) ... }
Java
private final IntentFilter intentFilter = new IntentFilter(); ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Indicates a change in the Wi-Fi Direct status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); // Indicates the state of Wi-Fi Direct connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
Alla fine del metodo onCreate()
, ottieni un'istanza di WifiP2pManager
e chiama il relativo metodo initialize()
. Questo metodo restituisce un oggetto WifiP2pManager.Channel
, che utilizzerai in un secondo momento per
connettere la tua app al framework Wi-Fi Direct.
Kotlin
private lateinit var channel: WifiP2pManager.Channel private lateinit var manager: WifiP2pManager override fun onCreate(savedInstanceState: Bundle?) { ... manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager channel = manager.initialize(this, mainLooper, null) }
Java
Channel channel; WifiP2pManager manager; @Override public void onCreate(Bundle savedInstanceState) { ... manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); channel = manager.initialize(this, getMainLooper(), null); }
Ora crea una nuova classe BroadcastReceiver
che utilizzerai per rilevare le modifiche
allo stato del Wi-Fi del sistema. Nel metodo onReceive()
, aggiungi una condizione per gestire ogni modifica dello stato elencata sopra.
Kotlin
override fun onReceive(context: Context, intent: Intent) { when(intent.action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { // Determine if Wi-Fi Direct mode is enabled or not, alert // the Activity. val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) activity.isWifiP2pEnabled = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED } WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // The peer list has changed! We should probably do something about // that. } WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Connection state changed! We should probably do something about // that. } WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { (activity.supportFragmentManager.findFragmentById(R.id.frag_list) as DeviceListFragment) .apply { updateThisDevice( intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice ) } } } }
Java
@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // Determine if Wi-Fi Direct mode is enabled or not, alert // the Activity. int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { activity.setIsWifiP2pEnabled(true); } else { activity.setIsWifiP2pEnabled(false); } } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // The peer list has changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Connection state changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager() .findFragmentById(R.id.frag_list); fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)); } }
Infine, aggiungi il codice per registrare il filtro per intent e il ricevitore di trasmissione quando
l'attività principale è attiva e annullane la registrazione quando l'attività è in pausa.
Il modo migliore per farlo è utilizzare i metodi onResume()
e
onPause()
.
Kotlin
/** register the BroadcastReceiver with the intent values to be matched */ public override fun onResume() { super.onResume() receiver = WiFiDirectBroadcastReceiver(manager, channel, this) registerReceiver(receiver, intentFilter) } public override fun onPause() { super.onPause() unregisterReceiver(receiver) }
Java
/** register the BroadcastReceiver with the intent values to be matched */ @Override public void onResume() { super.onResume(); receiver = new WiFiDirectBroadcastReceiver(manager, channel, this); registerReceiver(receiver, intentFilter); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); }
Avvia la scoperta dei peer
Per iniziare a cercare dispositivi nelle vicinanze con Wi-Fi P2P, chiama discoverPeers()
. Questo metodo accetta i seguenti argomenti:
- Il
WifiP2pManager.Channel
che hai ricevuto quando hai inizializzato mManager peer-to-peer - Un'implementazione di
WifiP2pManager.ActionListener
con metodi che il sistema richiama per la scoperta riuscita e non riuscita.
Kotlin
manager.discoverPeers(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } override fun onFailure(reasonCode: Int) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } })
Java
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } @Override public void onFailure(int reasonCode) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } });
Tieni presente che questa operazione avvia solo l'individuazione dei peer. Il metodo
discoverPeers()
avvia la procedura di rilevamento e poi
restituisce immediatamente. Il sistema ti avvisa se il processo di individuazione dei peer viene
avviato correttamente chiamando i metodi nel listener di azioni fornito.
Inoltre, il rilevamento rimane attivo finché non viene avviata una connessione o non viene formato un gruppo P2P.
Recuperare l'elenco dei peer
Ora scrivi il codice che recupera ed elabora l'elenco dei peer. Innanzitutto, implementa l'interfaccia WifiP2pManager.PeerListListener
, che fornisce informazioni sui peer rilevati da Wi-Fi Direct. Queste informazioni consentono inoltre all'app di determinare quando i peer entrano o
escono dalla rete. Il seguente snippet di codice illustra queste operazioni
relative ai peer:
Kotlin
private val peers = mutableListOf<WifiP2pDevice>() ... private val peerListListener = WifiP2pManager.PeerListListener { peerList -> val refreshedPeers = peerList.deviceList if (refreshedPeers != peers) { peers.clear() peers.addAll(refreshedPeers) // If an AdapterView is backed by this data, notify it // of the change. For instance, if you have a ListView of // available peers, trigger an update. (listAdapter as WiFiPeerListAdapter).notifyDataSetChanged() // Perform any other updates needed based on the new list of // peers connected to the Wi-Fi P2P network. } if (peers.isEmpty()) { Log.d(TAG, "No devices found") return@PeerListListener } }
Java
private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>(); ... private PeerListListener peerListListener = new PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peerList) { List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList(); if (!refreshedPeers.equals(peers)) { peers.clear(); peers.addAll(refreshedPeers); // If an AdapterView is backed by this data, notify it // of the change. For instance, if you have a ListView of // available peers, trigger an update. ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged(); // Perform any other updates needed based on the new list of // peers connected to the Wi-Fi P2P network. } if (peers.size() == 0) { Log.d(WiFiDirectActivity.TAG, "No devices found"); return; } } }
Ora modifica il metodo onReceive()
del ricevitore di trasmissione per chiamare requestPeers()
quando viene ricevuto un intent con l'azione WIFI_P2P_PEERS_CHANGED_ACTION
. Devi
passare questo listener al ricevitore in qualche modo. Un modo è inviarlo
come argomento al costruttore del ricevitore di trasmissione.
Kotlin
fun onReceive(context: Context, intent: Intent) { when (intent.action) { ... WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // Request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() mManager?.requestPeers(channel, peerListListener) Log.d(TAG, "P2P peers changed") } ... } }
Java
public void onReceive(Context context, Intent intent) { ... else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() if (mManager != null) { mManager.requestPeers(channel, peerListListener); } Log.d(WiFiDirectActivity.TAG, "P2P peers changed"); }... }
Ora, un intent con l'azione WIFI_P2P_PEERS_CHANGED_ACTION
intent
attiva una richiesta per un elenco di peer aggiornato.
Connettersi a un peer
Per connetterti a un peer, crea un nuovo oggetto WifiP2pConfig
e copia i dati da
WifiP2pDevice
che rappresenta il dispositivo a cui vuoi
connetterti. Quindi chiama il metodo connect()
.
Kotlin
override fun connect() { // Picking the first device found on the network. val device = peers[0] val config = WifiP2pConfig().apply { deviceAddress = device.deviceAddress wps.setup = WpsInfo.PBC } manager.connect(channel, config, object : WifiP2pManager.ActionListener { override fun onSuccess() { // WiFiDirectBroadcastReceiver notifies us. Ignore for now. } override fun onFailure(reason: Int) { Toast.makeText( this@WiFiDirectActivity, "Connect failed. Retry.", Toast.LENGTH_SHORT ).show() } }) }
Java
@Override public void connect() { // Picking the first device found on the network. WifiP2pDevice device = peers.get(0); WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; config.wps.setup = WpsInfo.PBC; manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { // WiFiDirectBroadcastReceiver notifies us. Ignore for now. } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.", Toast.LENGTH_SHORT).show(); } }); }
Se tutti i dispositivi del gruppo supportano Wi-Fi Direct, non è necessario
richiedere esplicitamente la password del gruppo durante la connessione. Per consentire a un dispositivo
che non supporta Wi-Fi Direct di entrare a far parte di un gruppo, devi
recuperare questa password chiamando
requestGroupInfo()
, come
mostrato nel seguente snippet di codice:
Kotlin
manager.requestGroupInfo(channel) { group -> val groupPassword = group.passphrase }
Java
manager.requestGroupInfo(channel, new GroupInfoListener() { @Override public void onGroupInfoAvailable(WifiP2pGroup group) { String groupPassword = group.getPassphrase(); } });
Tieni presente che WifiP2pManager.ActionListener
implementato nel metodo connect()
ti invia una notifica solo quando l'inizializzazione ha esito positivo o negativo. Per rilevare le modifiche nello stato della connessione, implementa l'interfaccia
WifiP2pManager.ConnectionInfoListener
.
Il suo callback onConnectionInfoAvailable()
ti avvisa quando lo stato della
connessione cambia. Nei casi in cui più dispositivi devono essere connessi a un singolo dispositivo (come un gioco con tre o più giocatori o un'app di chat), uno dei dispositivi viene designato come "proprietario del gruppo". Puoi designare un dispositivo specifico come
proprietario del gruppo della rete seguendo i passaggi descritti nella sezione
Creare un gruppo.
Kotlin
private val connectionListener = WifiP2pManager.ConnectionInfoListener { info -> // String from WifiP2pInfo struct val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress // After the group negotiation, we can determine the group owner // (server). if (info.groupFormed && info.isGroupOwner) { // Do whatever tasks are specific to the group owner. // One common case is creating a group owner thread and accepting // incoming connections. } else if (info.groupFormed) { // The other device acts as the peer (client). In this case, // you'll want to create a peer thread that connects // to the group owner. } }
Java
@Override public void onConnectionInfoAvailable(final WifiP2pInfo info) { // String from WifiP2pInfo struct String groupOwnerAddress = info.groupOwnerAddress.getHostAddress(); // After the group negotiation, we can determine the group owner // (server). if (info.groupFormed && info.isGroupOwner) { // Do whatever tasks are specific to the group owner. // One common case is creating a group owner thread and accepting // incoming connections. } else if (info.groupFormed) { // The other device acts as the peer (client). In this case, // you'll want to create a peer thread that connects // to the group owner. } }
Ora torna al metodo onReceive()
del broadcast receiver e modifica la sezione
che rileva un intent WIFI_P2P_CONNECTION_CHANGED_ACTION
.
Quando viene ricevuto questo intent, chiama requestConnectionInfo()
. Si tratta di una chiamata asincrona,
pertanto i risultati vengono ricevuti dal listener delle informazioni di connessione che fornisci come
parametro.
Kotlin
when (intent.action) { ... WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Connection state changed! We should probably do something about // that. mManager?.let { manager -> val networkInfo: NetworkInfo? = intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo if (networkInfo?.isConnected == true) { // We are connected with the other device, request connection // info to find group owner IP manager.requestConnectionInfo(channel, connectionListener) } } } ... }
Java
... } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { if (manager == null) { return; } NetworkInfo networkInfo = (NetworkInfo) intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); if (networkInfo.isConnected()) { // We are connected with the other device, request connection // info to find group owner IP manager.requestConnectionInfo(channel, connectionListener); } ...
Creare un gruppo
Se vuoi che il dispositivo su cui è in esecuzione la tua app funga da proprietario del gruppo per una rete che include dispositivi legacy, ovvero dispositivi che non supportano Wi-Fi Direct, segui la stessa sequenza di passaggi della sezione Connettersi a un peer, ma crea un nuovo WifiP2pManager.ActionListener
utilizzando createGroup()
anziché connect()
. La gestione del callback all'interno di
WifiP2pManager.ActionListener
è la stessa, come mostrato nel seguente snippet di codice:
Kotlin
manager.createGroup(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Device is ready to accept incoming connections from peers. } override fun onFailure(reason: Int) { Toast.makeText( this@WiFiDirectActivity, "P2P group creation failed. Retry.", Toast.LENGTH_SHORT ).show() } })
Java
manager.createGroup(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Device is ready to accept incoming connections from peers. } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.", Toast.LENGTH_SHORT).show(); } });
Nota: se tutti i dispositivi di una rete supportano Wi-Fi Direct, puoi utilizzare il metodo connect()
su ogni dispositivo perché il metodo crea il gruppo e seleziona automaticamente un proprietario del gruppo.
Dopo aver creato un gruppo, puoi chiamare
requestGroupInfo()
per recuperare i dettagli dei peer sulla
rete, inclusi i nomi dei dispositivi e gli stati della connessione.