Las funciones de reconocimiento de Wi-Fi permiten que los dispositivos con Android 8.0 (nivel de API 26) y versiones posteriores se descubran y se conecten directamente entre sí sin ningún otro tipo de conectividad entre ellos. El reconocimiento de Wi-Fi también se conoce como Neighbor Awareness Networking (NAN).
Las redes de reconocimiento de Wi-Fi funcionan mediante la formación de clústeres con dispositivos vecinos o la creación de un clúster nuevo si el dispositivo es el primero en un área. Este comportamiento de agrupamiento en clústeres se aplica a todo el dispositivo y lo administra el servicio del sistema Wi-Fi Aware; las apps no tienen control sobre el comportamiento de agrupamiento en clústeres. Las apps usan las APIs de reconocimiento de Wi-Fi para comunicarse con el servicio del sistema correspondiente, que administra el hardware correspondiente en el dispositivo.
Las API de reconocimiento de Wi-Fi permiten que las apps realicen las siguientes operaciones:
Descubrir otros dispositivos: La API tiene un mecanismo para encontrar otros dispositivos cercanos. El proceso comienza cuando un dispositivo publica uno o más servicios detectables. Luego, cuando un dispositivo se suscribe a uno o más servicios y entra en el rango de Wi-Fi del publicador, el suscriptor recibe una notificación de que se descubrió un publicador coincidente. Después de que el suscriptor descubre un publicador, puede enviar un mensaje corto o establecer una conexión de red con el dispositivo descubierto. Los dispositivos pueden ser simultáneamente publicadores y suscriptores.
Crea una conexión de red: Después de que dos dispositivos se descubran, pueden crear una conexión de red bidireccional de reconocimiento de Wi-Fi sin un punto de acceso.
Las conexiones de red de reconocimiento de Wi-Fi admiten tasas de capacidad de procesamiento más altas en distancias más largas que las conexiones Bluetooth. Estos tipos de conexiones son útiles para las apps que comparten grandes cantidades de datos entre usuarios, como las apps para compartir fotos.
Mejoras de Android 13 (nivel de API 33)
En los dispositivos que ejecutan Android 13 (nivel de API 33) y versiones posteriores que admiten el modo de comunicación instantánea, las apps pueden usar los métodos PublishConfig.Builder.setInstantCommunicationModeEnabled()
y SubscribeConfig.Builder.setInstantCommunicationModeEnabled()
para habilitar o inhabilitar este modo para una sesión de descubrimiento de publicadores o suscriptores. El modo de comunicación instantánea acelera el intercambio de mensajes, el descubrimiento de servicios y cualquier configuración de ruta de datos como parte de una sesión de descubrimiento de publicador o suscriptor. Para determinar si un dispositivo admite el modo de comunicación instantánea, usa el método isInstantCommunicationModeSupported()
.
Mejoras en Android 12 (nivel de API 31)
Android 12 (nivel de API 31) agrega algunas mejoras a Reconocimiento de Wi-Fi:
- En dispositivos que ejecutan Android 12 (nivel de API 31) o versiones posteriores, puedes usar la devolución de llamada
onServiceLost()
para recibir alertas cuando tu app pierda un servicio detectado debido a que el servicio se detuvo o salió del rango. - Se simplificó la configuración de las rutas de datos de reconocimiento de Wi-Fi. Las versiones anteriores usaban mensajes L2 para proporcionar la dirección MAC del iniciador, que producía la latencia. En dispositivos que ejecutan Android 12 y versiones posteriores, la respuesta (servidor) se puede configurar para que acepte cualquier intercambio de tráfico; es decir, no es necesario que conozca la dirección MAC del iniciador por adelantado. Esto acelera la creación de la ruta de datos y habilita varios vínculos punto a punto con una sola solicitud de red.
- Las apps que se ejecutan en Android 12 o versiones posteriores pueden usar el método
WifiAwareManager.getAvailableAwareResources()
para obtener la cantidad de rutas de datos, sesiones de publicación y sesiones de suscripción disponibles actualmente. Esto puede ayudar a la app a determinar si hay suficientes recursos disponibles para ejecutar la funcionalidad deseada.
Configuración inicial
Si deseas configurar tu app para usar el descubrimiento y las redes de reconocimiento de Wi-Fi, sigue estos pasos:
Solicita los siguientes permisos en el manifiesto de tu 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" />
Comprueba si el dispositivo admite el reconocimiento de Wi-Fi con la API de
PackageManager
, como se muestra a continuación:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
Comprueba si el reconocimiento de Wi-Fi está disponible actualmente. Es posible que el dispositivo tenga reconocimiento de Wi-Fi, pero puede que no esté disponible en ese momento porque el usuario inhabilitó la conexión Wi-Fi o la ubicación. Según las capacidades de hardware y firmware, es posible que algunos dispositivos no admitan el reconocimiento de Wi-Fi si se está usando Wi-Fi directo, SoftAP o la conexión mediante dispositivo móvil. Para comprobar si el reconocimiento de Wi-Fi está disponible, llama a
isAvailable()
.La disponibilidad del reconocimiento de Wi-Fi puede cambiar en cualquier momento. Tu app debe registrar un
BroadcastReceiver
para recibirACTION_WIFI_AWARE_STATE_CHANGED
, que se envía cuando cambia la disponibilidad. Cuando tu app recibe el intent de transmisión, debe descartar todas las sesiones existentes (suponer que se interrumpió el servicio de reconocimiento de Wi-Fi) y, luego, verificar el estado actual de disponibilidad y ajustar su comportamiento en consecuencia. Por ejemplo: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);
Para obtener más información, consulta Transmisiones.
Obtén una sesión
Para comenzar a usar el reconocimiento de Wi-Fi, tu app debe obtener un WifiAwareSession
llamando a attach()
. Este método hace lo siguiente:
- Activa el hardware de reconocimiento de Wi-Fi.
- Se une a un clúster de reconocimiento de Wi-Fi o forma uno.
- Crea una sesión de reconocimiento de Wi-Fi con un espacio de nombres único que actúa como contenedor para todas las sesiones de descubrimiento creadas en él.
Si la app se conecta correctamente, el sistema ejecuta la devolución de llamada onAttached()
.
Esta devolución de llamada proporciona un objeto WifiAwareSession
que tu app debe usar para todas las demás operaciones de sesión. Una app puede usar la sesión para publicar un servicio o suscribirse a un servicio.
Tu app debe llamar a attach()
solo una vez. Si tu app llama a attach()
varias veces, recibe una sesión diferente para cada llamada, cada una con su propio espacio de nombres. Esto podría ser útil en situaciones complejas,
pero, por lo general, debe evitarse.
Publica un servicio
Para que un servicio sea detectable, llama al método publish()
, que incluye los siguientes parámetros:
PublishConfig
especifica el nombre del servicio y otras propiedades de configuración, como el filtro de coincidencias.DiscoverySessionCallback
especifica las acciones que se deben ejecutar cuando se producen eventos, como cuando el suscriptor recibe un mensaje.
Por ejemplo:
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);
Si la publicación se realiza correctamente, se llama al método de devolución de llamada onPublishStarted()
.
Después de la publicación, cuando los dispositivos que ejecutan apps de suscriptor coincidentes pasan al rango de Wi-Fi del dispositivo de publicación, los suscriptores descubren el servicio. Cuando un suscriptor descubre un publicador, el publicador no recibe una notificación. Sin embargo, si el suscriptor envía un mensaje al publicador, el publicador recibe una notificación. Cuando eso sucede, se llama al método de devolución de llamada onMessageReceived()
. Puedes usar el argumento PeerHandle
de este método para enviar un mensaje al suscriptor o crear una conexión con él.
Para dejar de publicar el servicio, llama a DiscoverySession.close()
.
Las sesiones de descubrimiento están asociadas a su WifiAwareSession
superior. Si se cierra la sesión superior, también se cierran sus sesiones de descubrimiento asociadas. Si bien los objetos descartados también están cerrados, el sistema no garantiza cuándo se cierran las sesiones fuera del alcance, por lo que te recomendamos que llames de manera explícita a los métodos close()
.
Suscríbete a un servicio
Para suscribirte a un servicio, llama al método subscribe()
, que toma los siguientes parámetros:
-
SubscribeConfig
especifica el nombre del servicio al cual suscribirse y otras propiedades de configuración, como el filtro de coincidencias. DiscoverySessionCallback
especifica las acciones que se deben ejecutar cuando se producen eventos, como cuando se descubre un publicador.
Por ejemplo:
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);
Si la operación de suscripción se ejecuta correctamente, el sistema llamará a la devolución de llamada onSubscribeStarted()
en tu app. Como puedes usar el argumento SubscribeDiscoverySession
en la devolución de llamada para comunicarte con un publicador después de que tu app haya descubierto uno, debes guardar esta referencia. Puedes actualizar la sesión de suscripción en cualquier momento llamando a updateSubscribe()
en la sesión de descubrimiento.
En este punto, tu suscripción espera a que los publicadores coincidentes lleguen al rango de Wi-Fi. Cuando esto sucede, el sistema ejecuta el método de devolución de llamada onServiceDiscovered()
. Puedes usar el argumento PeerHandle
de esta devolución de llamada para enviar un mensaje o crear una conexión con ese publicador.
Para detener la suscripción a un servicio, llama a DiscoverySession.close()
.
Las sesiones de descubrimiento están asociadas a su WifiAwareSession
superior. Si se cierra la sesión superior, también se cierran sus sesiones de descubrimiento asociadas. Si bien los objetos descartados también están cerrados, el sistema no garantiza cuándo se cierran las sesiones fuera del alcance, por lo que te recomendamos que llames de manera explícita a los métodos close()
.
Cómo enviar un mensaje
Para enviar un mensaje a otro dispositivo, necesitas los siguientes objetos:
Un objeto
DiscoverySession
. Este objeto te permite llamar asendMessage()
. Tu app obtiene unaDiscoverySession
mediante la publicación de un servicio o la suscripción a un servicio.El
PeerHandle
del otro dispositivo, para enrutar el mensaje. Tu app obtiene elPeerHandle
de otro dispositivo de dos maneras:- Tu app publica un servicio y recibe un mensaje de un suscriptor.
Tu app obtiene el
PeerHandle
del suscriptor desde la devolución de llamadaonMessageReceived()
. - Tu app se suscribe a un servicio. Luego, cuando descubre un publicador que coincide, la app obtiene el
PeerHandle
del publicador de la devolución de llamadaonServiceDiscovered()
.
- Tu app publica un servicio y recibe un mensaje de un suscriptor.
Tu app obtiene el
Para enviar un mensaje, llama a
sendMessage()
. Entonces, pueden ocurrir las siguientes devoluciones de llamada:
- Cuando la app similar recibe correctamente el mensaje, el sistema llama a la devolución de llamada
onMessageSendSucceeded()
en la app que envía. - Cuando la app similar recibe un mensaje, el sistema llama a la devolución de llamada
onMessageReceived()
en la app receptora.
Si bien el PeerHandle
es necesario para comunicarse con los pares, no debes confiar en él como un identificador permanente de apps similares. La aplicación puede usar identificadores de nivel superior cuando está incorporado en el servicio de descubrimiento o en los mensajes posteriores. Puedes incorporar un identificador en el servicio de descubrimiento con el método setMatchFilter()
o setServiceSpecificInfo()
de PublishConfig
o SubscribeConfig
. El método setMatchFilter()
afecta el descubrimiento, mientras que el método setServiceSpecificInfo()
no lo hace.
Incorporar un identificador en un mensaje implica modificar el array de bytes del mensaje para incluir un identificador (por ejemplo, como el primer par de bytes).
Crea una conexión
El reconocimiento de Wi-Fi admite la red cliente-servidor entre dos dispositivos con esa función.
Para configurar la conexión cliente-servidor:
Usa el descubrimiento de reconocimiento de Wi-Fi para publicar un servicio (en el servidor) y suscribirte a un servicio (en el cliente).
Una vez que el suscriptor descubre al publicador, envíale un mensaje al publicador.
Inicia un
ServerSocket
en el dispositivo del publicador y configura u obtén su puerto:Kotlin
val ss = ServerSocket(0) val port = ss.localPort
Java
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
Usa
ConnectivityManager
para solicitar una red de reconocimiento de Wi-Fi en el publicador mediante unWifiAwareNetworkSpecifier
, que especifica la sesión de descubrimiento y elPeerHandle
del suscriptor, que obtuviste del mensaje transmitido por el suscriptor: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);
Una vez que el publicador solicita una red, debe enviar un mensaje al suscriptor.
Una vez que el suscriptor reciba el mensaje del publicador, solicítale una red de reconocimiento de Wi-Fi con el mismo método que en el publicador. No especifiques un puerto cuando crees el
NetworkSpecifier
. Se llama a los métodos de devolución de llamada adecuados cuando la conexión de red está disponible, cambia o se pierde.Una vez que se llama al método
onAvailable()
en el suscriptor, hay un objetoNetwork
disponible con el que puedes abrir unSocket
para comunicarte con elServerSocket
en el publicador, pero debes conocer la dirección IPv6 y el puerto deServerSocket
. Los obtienes del objetoNetworkCapabilities
proporcionado en la devolución de llamadaonCapabilitiesChanged()
: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);
Cuando termines con la conexión de red, llama a
unregisterNetworkCallback()
.
Rango de app similares y descubrimiento del conocimiento de la ubicación
Un dispositivo con capacidades de ubicación de Wi-Fi RTT puede medir directamente la distancia a pares y usar esta información para limitar el descubrimiento de servicios de reconocimiento de Wi-Fi.
La API de Wi-Fi RTT permite el rango directo a una app similar con reconocimiento de Wi-Fi usando su dirección MAC o su PeerHandle.
El descubrimiento de reconocimiento de Wi-Fi puede estar restringido a solo descubrir servicios dentro de un geovallado en particular. Por ejemplo, puedes configurar un geovallado que permita descubrir un dispositivo que publica un servicio "Aware_File_Share_Service_Name"
que no esté más cerca de 3 metros (especificado como 3,000 mm) y no más de 10 metros (especificado como 10,000 mm).
Para habilitar el geovallado, tanto el publicador como el suscriptor deben tomar medidas:
El editor debe habilitar el rango en el servicio publicado con setRangingEnabled(true).
Si el publicador no habilita el rango, se ignoran las restricciones de geovallas especificadas por el suscriptor y se realiza el descubrimiento normal, sin tener en cuenta la distancia.
El suscriptor debe especificar un geovallado con alguna combinación de setMinDistanceMm y setMaxDistanceMm.
Para cualquier valor, si no se especifica una distancia, esto implica que no hay un límite. Si solo se especifica la distancia máxima, esto implica una distancia mínima de 0. Si solo se especifica la distancia mínima, esto implica que no hay un máximo.
Cuando se descubre un servicio de apps similares dentro de un geovallado, se activa la devolución de llamada onServiceDiscoveredWithinRange, que proporciona la distancia medida a la app similar. Luego, se puede llamar a la API de Wi-Fi RTT directa según sea necesario para medir la distancia más adelante.