Wi-Fi Direct (P2P) permite que los dispositivos con el hardware adecuado se conecten directamente entre sí a través de Wi-Fi sin un punto de acceso intermedio. Con estas APIs, puedes descubrir otros dispositivos y conectarte a ellos cuando cada dispositivo admite Wi-Fi P2P y, luego, comunicarte a través de una conexión rápida en distancias mucho más largas que una conexión Bluetooth. Esto es útil para las aplicaciones que comparten datos entre los usuarios, como un juego multijugador o una aplicación para compartir fotos.
Las APIs de Wi-Fi P2P constan de las siguientes partes principales:
- Métodos que te permiten descubrir pares, solicitarlos y conectarte a ellos, que se
definen en la
WifiP2pManagerclase. - Objetos de escucha que te permiten recibir notificaciones sobre el éxito o el fracaso de las llamadas al método
WifiP2pManagerCuando llamas a los métodosWifiP2pManager, cada método puede recibir un objeto de escucha específico que se pasa como parámetro. - Intents que te notifican sobre eventos específicos detectados por el framework de Wi-Fi P2P, como una conexión interrumpida o un par recién descubierto
A menudo, usarás estos tres componentes principales de las APIs en conjunto. Por
ejemplo, puedes proporcionar un
WifiP2pManager.ActionListener
a una llamada a
discoverPeers()
para que los métodos
ActionListener.onSuccess()
y
ActionListener.onFailure()
puedan notificarte. También se transmite un
WIFI_P2P_PEERS_CHANGED_ACTION
intent si el método discoverPeers() descubre que cambió la
lista de pares.
Descripción general de la API
La clase WifiP2pManager proporciona métodos para permitirte interactuar con el hardware de Wi-Fi en tu dispositivo para realizar acciones como descubrir pares y conectarte a ellos.
Puedes realizar las siguientes acciones:
Tabla 1: Métodos de Wi-Fi P2P
| Método | Descripción |
initialize()
|
Registra la aplicación con el framework de Wi-Fi. Llama a este método antes de llamar a cualquier otro método de Wi-Fi P2P. |
connect()
|
Inicia una conexión entre pares con un dispositivo con la configuración especificada. |
cancelConnect()
|
Cancela cualquier negociación de grupo entre pares en curso. |
requestConnectInfo()
|
Solicita la información de conexión de un dispositivo. |
createGroup()
|
Crea un grupo entre pares con el dispositivo actual como propietario del grupo. |
removeGroup()
|
Quita el grupo entre pares actual. |
requestGroupInfo()
|
Solicita información del grupo entre pares. |
discoverPeers()
|
Inicia el descubrimiento de pares. |
requestPeers()
|
Solicita la lista actual de pares descubiertos. |
Los métodos WifiP2pManager te permiten pasar un objeto de escucha para que el framework de Wi-Fi P2P pueda notificar a tu actividad el estado de una llamada. Las interfaces de escucha disponibles y las llamadas al método WifiP2pManager correspondientes que usan los objetos de escucha se describen en la tabla 2.
Tabla 2: Objetos de escucha de Wi-Fi P2P
| Interfaz de escucha | Acciones asociadas |
WifiP2pManager.ActionListener
|
connect(), cancelConnect(),
createGroup(), removeGroup(), and
discoverPeers()
|
WifiP2pManager.ChannelListener
|
initialize()
|
WifiP2pManager.ConnectionInfoListener
|
requestConnectInfo()
|
WifiP2pManager.GroupInfoListener
|
requestGroupInfo()
|
WifiP2pManager.PeerListListener
|
requestPeers()
|
Las APIs de Wi-Fi P2P definen intents que se transmiten cuando ocurren ciertos eventos de Wi-Fi P2P, como cuando se descubre un par nuevo o cuando cambia el estado de Wi-Fi de un dispositivo. Puedes registrarte para recibir estos intents en tu aplicación by creando un receptor de transmisión que los controle:
Tabla 3: Intents de Wi-Fi P2P
| Intent | Descripción |
WIFI_P2P_CONNECTION_CHANGED_ACTION
|
Se transmite cuando cambia el estado de la conexión Wi-Fi del dispositivo. |
WIFI_P2P_PEERS_CHANGED_ACTION
|
Se transmite cuando llamas a discoverPeers(). Por lo general, llamarás a
requestPeers() para obtener una lista actualizada de pares si
controlas este intent en tu aplicación.
|
WIFI_P2P_STATE_CHANGED_ACTION
|
Se transmite cuando Wi-Fi P2P está habilitado o inhabilitado en el dispositivo. |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
|
Se transmite cuando cambian los detalles de un dispositivo, como el nombre del dispositivo. |
Crea un receptor de transmisión para intents de Wi-Fi P2P
Un receptor de transmisión te permite recibir intents transmitidos por el sistema Android para que tu aplicación pueda responder a los eventos que te interesan. Los pasos básicos para crear un receptor de transmisión para controlar intents de Wi-Fi P2P son los siguientes:
Crea una clase que extienda la
BroadcastReceiverclase. Para el constructor de la clase, usarás parámetros para elWifiP2pManager,WifiP2pManager.Channely la actividad en la que se registrará este receptor de transmisión. Esto permite que el receptor de transmisión envíe actualizaciones a la actividad y tenga acceso al hardware de Wi-Fi y a un canal de comunicación si es necesario.En el receptor de transmisión, verifica los intents que te interesan en el
onReceive()método. Realiza las acciones necesarias según el intent que se reciba. Por ejemplo, si el receptor de transmisión recibe un intentWIFI_P2P_PEERS_CHANGED_ACTION, puedes llamar al métodorequestPeers()para obtener una lista de los pares descubiertos actualmente.
En el siguiente código, se muestra cómo crear un receptor de transmisión típico. El receptor de transmisión toma un objeto WifiP2pManager y una actividad como argumentos, y usa estas dos clases para llevar a cabo de manera adecuada las acciones necesarias cuando el receptor de transmisión recibe un intent:
Kotlin
/** * A BroadcastReceiver that notifies of important Wi-Fi p2p events. */ class WiFiDirectBroadcastReceiver( private val manager: WifiP2pManager, private val channel: WifiP2pManager.Channel, private val activity: MyWifiActivity ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action: String = intent.action when (action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { // Check to see if Wi-Fi is enabled and notify appropriate activity } WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // Call WifiP2pManager.requestPeers() to get a list of current peers } WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Respond to new connection or disconnections } WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { // Respond to this device's wifi state changing } } } }
Java
/** * A BroadcastReceiver that notifies of important Wi-Fi p2p events. */ public class WiFiDirectBroadcastReceiver extends BroadcastReceiver { private WifiP2pManager manager; private Channel channel; private MyWiFiActivity activity; public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel, MyWifiActivity activity) { super(); this.manager = manager; this.channel = channel; this.activity = activity; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // Check to see if Wi-Fi is enabled and notify appropriate activity } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Call WifiP2pManager.requestPeers() to get a list of current peers } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Respond to new connection or disconnections } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { // Respond to this device's wifi state changing } } }
En los dispositivos que ejecutan Android 10 y versiones posteriores, los siguientes intents de transmisión no son persistentes:
WIFI_P2P_CONNECTION_CHANGED_ACTION- Las aplicaciones pueden usar
requestConnectionInfo(),requestNetworkInfo(), orequestGroupInfo()para recuperar la información de conexión actual. WIFI_P2P_THIS_DEVICE_CHANGED_ACTION- Las aplicaciones pueden usar
requestDeviceInfo()para recuperar la información de conexión actual.
Crea una aplicación de Wi-Fi P2P
Para crear una aplicación de Wi-Fi P2P, debes crear y registrar un receptor de transmisión para tu aplicación, descubrir pares, conectarte a un par y transferir datos a un par. En las siguientes secciones, se explica cómo hacerlo.
Configuración inicial
Antes de usar las APIs de Wi-Fi P2P, debes asegurarte de que tu aplicación pueda acceder al hardware y de que el dispositivo admita el protocolo de Wi-Fi P2P. Si se admite Wi-Fi P2P, puedes obtener una instancia de WifiP2pManager, crear y registrar tu receptor de transmisión, y comenzar a usar las APIs de Wi-Fi P2P.
Solicita permiso para usar el hardware de Wi-Fi en el dispositivo y declara que tu aplicación tiene la versión mínima correcta del SDK en el manifiesto de Android:
<uses-sdk android:minSdkVersion="14" /> <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" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 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" />Además de los permisos anteriores, las siguientes API también requieren que se habilite el Modo de ubicación:
Verifica si Wi-Fi P2P está activado y es compatible. Un buen lugar para verificar esto es en tu receptor de transmisión cuando recibe el intent
WIFI_P2P_STATE_CHANGED_ACTION. Notifica a tu actividad el estado de Wi-Fi P2P y reacciona según corresponda:Kotlin
override fun onReceive(context: Context, intent: Intent) { ... val action: String = intent.action when (action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) when (state) { WifiP2pManager.WIFI_P2P_STATE_ENABLED -> { // Wifi P2P is enabled } else -> { // Wi-Fi P2P is not enabled } } } } ... }
Java
@Override public void onReceive(Context context, Intent intent) { ... String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { // Wifi P2P is enabled } else { // Wi-Fi P2P is not enabled } } ... }
En el método
onCreate()de tu actividad, obtén una instancia deWifiP2pManagery registra tu aplicación con el framework de Wi-Fi P2P llamando ainitialize(). Este método muestra unWifiP2pManager.Channel, que se usa para conectar tu aplicación al framework de Wi-Fi P2P. También debes crear una instancia de tu receptor de transmisión con los objetosWifiP2pManageryWifiP2pManager.Channel, junto con una referencia a tu actividad. Esto permite que tu receptor de transmisión notifique a tu actividad sobre eventos interesantes y la actualice según corresponda. También te permite manipular el estado de Wi-Fi del dispositivo si es necesario:Kotlin
val manager: WifiP2pManager? by lazy(LazyThreadSafetyMode.NONE) { getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? } var channel: WifiP2pManager.Channel? = null var receiver: BroadcastReceiver? = null override fun onCreate(savedInstanceState: Bundle?) { ... channel = manager?.initialize(this, mainLooper, null) channel?.also { channel -> receiver = WiFiDirectBroadcastReceiver(manager, channel, this) } }
Java
WifiP2pManager manager; Channel channel; BroadcastReceiver receiver; ... @Override protected void onCreate(Bundle savedInstanceState){ ... manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); channel = manager.initialize(this, getMainLooper(), null); receiver = new WiFiDirectBroadcastReceiver(manager, channel, this); ... }
Crea un filtro de intents y agrega los mismos intents que verifica tu receptor de transmisión:
Kotlin
val intentFilter = IntentFilter().apply { addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) }
Java
IntentFilter intentFilter; ... @Override protected void onCreate(Bundle savedInstanceState){ ... intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
Registra el receptor de transmisión en el
onResume()método de tu actividad y anula su registro en elonPause()método de tu actividad:Kotlin
/* register the broadcast receiver with the intent values to be matched */ override fun onResume() { super.onResume() receiver?.also { receiver -> registerReceiver(receiver, intentFilter) } } /* unregister the broadcast receiver */ override fun onPause() { super.onPause() receiver?.also { receiver -> unregisterReceiver(receiver) } }
Java
/* register the broadcast receiver with the intent values to be matched */ @Override protected void onResume() { super.onResume(); registerReceiver(receiver, intentFilter); } /* unregister the broadcast receiver */ @Override protected void onPause() { super.onPause(); unregisterReceiver(receiver); }
Cuando obtengas un
WifiP2pManager.Channely configures un receptor de transmisión, tu aplicación podrá realizar llamadas al método de Wi-Fi P2P y recibir intents de Wi-Fi P2P.Implementa tu aplicación con las funciones de Wi-Fi P2P llamando a los métodos en
WifiP2pManager.
En las siguientes secciones, se describe cómo realizar acciones comunes, como descubrir pares y conectarse a ellos.
Descubre pares
Llama a discoverPeers() para detectar los pares disponibles que están dentro del alcance y disponibles para la conexión. La llamada a esta función es asíncrona y se comunica un éxito o un fracaso a tu aplicación con onSuccess() y onFailure() si creaste un WifiP2pManager.ActionListener. El método onSuccess() solo te notifica que el proceso de descubrimiento se realizó correctamente y no proporciona información sobre los pares reales que descubrió, si los hay. En el siguiente ejemplo de código, se muestra cómo configurar esto.
Kotlin
manager?.discoverPeers(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { ... } override fun onFailure(reasonCode: Int) { ... } })
Java
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { ... } @Override public void onFailure(int reasonCode) { ... } });
Si el proceso de descubrimiento se realiza correctamente y detecta pares, el sistema transmite el intent WIFI_P2P_PEERS_CHANGED_ACTION, que puedes escuchar en un receptor de transmisión para obtener una lista de pares. Cuando tu aplicación recibe el intent WIFI_P2P_PEERS_CHANGED_ACTION, puedes solicitar una lista de los pares descubiertos con requestPeers(). En el siguiente código, se muestra cómo configurar esto.
Kotlin
override fun onReceive(context: Context, intent: Intent) { val action: String = intent.action when (action) { ... WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { manager?.requestPeers(channel) { peers: WifiP2pDeviceList? -> // Handle peers list } } ... } }
Java
PeerListListener myPeerListListener; ... 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 (manager != null) { manager.requestPeers(channel, myPeerListListener); } }
El método requestPeers() también es asíncrono y puede notificar a tu actividad
cuando una lista de pares está disponible con
onPeersAvailable(),
que se define en la interfaz WifiP2pManager.PeerListListener. El
onPeersAvailable() método te proporciona un
WifiP2pDeviceList, que
puedes iterar para encontrar el par al que deseas conectarte.
Conéctate a pares
Una vez que obtengas una lista de pares posibles y selecciones un dispositivo al que conectarte, llama al método connect() para conectarte al dispositivo. Esta llamada de método
requiere un WifiP2pConfig
objeto que contenga información sobre el dispositivo al que deseas conectarte.
WifiP2pManager.ActionListener puede notificarte sobre el éxito o el fracaso de una conexión. En el siguiente código, se muestra cómo crear una conexión a un dispositivo.
Kotlin
val device: WifiP2pDevice = ... val config = WifiP2pConfig() config.deviceAddress = device.deviceAddress channel?.also { channel -> manager?.connect(channel, config, object : WifiP2pManager.ActionListener { override fun onSuccess() { //success logic } override fun onFailure(reason: Int) { //failure logic } } )}
Java
//obtain a peer from the WifiP2pDeviceList WifiP2pDevice device; WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { //success logic } @Override public void onFailure(int reason) { //failure logic } });
Transferir datos
Una vez que se establece una conexión, puedes transferir datos entre los dispositivos con sockets. Los pasos básicos para transferir datos son los siguientes:
- Crea un
ServerSocket. Este socket espera una conexión de un cliente en un puerto especificado y se bloquea hasta que sucede, por lo que debes hacerlo en un subproceso en segundo plano. - Crea un cliente
Socket. El cliente usa la dirección IP y el puerto del socket del servidor para conectarse al dispositivo del servidor. - Envía datos del cliente al servidor. Cuando el socket del cliente se conecta correctamente al socket del servidor, puedes enviar datos del cliente al servidor con flujos de bytes.
- El socket del servidor espera una conexión del cliente (con el
accept()método). Esta llamada se bloquea hasta que se conecta un cliente, por lo que debes llamarla en otro subproceso. Cuando se produce una conexión, el dispositivo del servidor puede recibir los datos del cliente.
En el siguiente ejemplo, modificado de la demostración de Wi-Fi P2P, se muestra cómo crear esta comunicación de socket cliente-servidor y transferir imágenes JPEG de un cliente a un servidor con un servicio. Para obtener un ejemplo funcional completo, compila y ejecuta la demostración.
Kotlin
class FileServerAsyncTask( private val context: Context, private var statusText: TextView ) : AsyncTask<Void, Void, String?>() { override fun doInBackground(vararg params: Void): String? { /** * Create a server socket. */ val serverSocket = ServerSocket(8888) return serverSocket.use { /** * Wait for client connections. This call blocks until a * connection is accepted from a client. */ val client = serverSocket.accept() /** * If this code is reached, a client has connected and transferred data * Save the input stream from the client as a JPEG file */ val f = File(Environment.getExternalStorageDirectory().absolutePath + "/${context.packageName}/wifip2pshared-${System.currentTimeMillis()}.jpg") val dirs = File(f.parent) dirs.takeIf { it.doesNotExist() }?.apply { mkdirs() } f.createNewFile() val inputstream = client.getInputStream() copyFile(inputstream, FileOutputStream(f)) serverSocket.close() f.absolutePath } } private fun File.doesNotExist(): Boolean = !exists() /** * Start activity that can handle the JPEG image */ override fun onPostExecute(result: String?) { result?.run { statusText.text = "File copied - $result" val intent = Intent(android.content.Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse("file://$result"), "image/*") } context.startActivity(intent) } } }
Java
public static class FileServerAsyncTask extends AsyncTask { private Context context; private TextView statusText; public FileServerAsyncTask(Context context, View statusText) { this.context = context; this.statusText = (TextView) statusText; } @Override protected String doInBackground(Void... params) { try { /** * Create a server socket and wait for client connections. This * call blocks until a connection is accepted from a client */ ServerSocket serverSocket = new ServerSocket(8888); Socket client = serverSocket.accept(); /** * If this code is reached, a client has connected and transferred data * Save the input stream from the client as a JPEG file */ final File f = new File(Environment.getExternalStorageDirectory() + "/" + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis() + ".jpg"); File dirs = new File(f.getParent()); if (!dirs.exists()) dirs.mkdirs(); f.createNewFile(); InputStream inputstream = client.getInputStream(); copyFile(inputstream, new FileOutputStream(f)); serverSocket.close(); return f.getAbsolutePath(); } catch (IOException e) { Log.e(WiFiDirectActivity.TAG, e.getMessage()); return null; } } /** * Start activity that can handle the JPEG image */ @Override protected void onPostExecute(String result) { if (result != null) { statusText.setText("File copied - " + result); Intent intent = new Intent(); intent.setAction(android.content.Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse("file://" + result), "image/*"); context.startActivity(intent); } } }
En el cliente, conéctate al socket del servidor con un socket del cliente y transfiere datos. En este ejemplo, se transfiere un archivo JPEG en el sistema de archivos del dispositivo cliente.
Kotlin
val context = applicationContext val host: String val port: Int val len: Int val socket = Socket() val buf = ByteArray(1024) ... try { /** * Create a client socket with the host, * port, and timeout information. */ socket.bind(null) socket.connect((InetSocketAddress(host, port)), 500) /** * Create a byte stream from a JPEG file and pipe it to the output stream * of the socket. This data is retrieved by the server device. */ val outputStream = socket.getOutputStream() val cr = context.contentResolver val inputStream: InputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg")) while (inputStream.read(buf).also { len = it } != -1) { outputStream.write(buf, 0, len) } outputStream.close() inputStream.close() } catch (e: FileNotFoundException) { //catch logic } catch (e: IOException) { //catch logic } finally { /** * Clean up any open sockets when done * transferring or if an exception occurred. */ socket.takeIf { it.isConnected }?.apply { close() } }
Java
Context context = this.getApplicationContext(); String host; int port; int len; Socket socket = new Socket(); byte buf[] = new byte[1024]; ... try { /** * Create a client socket with the host, * port, and timeout information. */ socket.bind(null); socket.connect((new InetSocketAddress(host, port)), 500); /** * Create a byte stream from a JPEG file and pipe it to the output stream * of the socket. This data is retrieved by the server device. */ OutputStream outputStream = socket.getOutputStream(); ContentResolver cr = context.getContentResolver(); InputStream inputStream = null; inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg")); while ((len = inputStream.read(buf)) != -1) { outputStream.write(buf, 0, len); } outputStream.close(); inputStream.close(); } catch (FileNotFoundException e) { //catch logic } catch (IOException e) { //catch logic } /** * Clean up any open sockets when done * transferring or if an exception occurred. */ finally { if (socket != null) { if (socket.isConnected()) { try { socket.close(); } catch (IOException e) { //catch logic } } } }