O Wi-Fi Direct (também conhecido como ponto a ponto ou P2P) permite que seu app encontre dispositivos próximos rapidamente e interaja com eles, com um alcance superior aos recursos do Bluetooth.
As APIs do Wi-Fi Direct (P2P) permitem que os apps se conectem a dispositivos próximos sem precisar se conectar a uma rede ou ponto de acesso. Caso seu app tenha sido projetado para fazer parte de uma rede segura e de curto alcance, o Wi-Fi Direct é uma opção mais adequada que a rede Wi-Fi ad-hoc tradicional pelos seguintes motivos:
- O Wi-Fi Direct é compatível com criptografia WPA2. Algumas redes ad-hoc são compatíveis apenas com criptografia WEP.
- Os dispositivos podem transmitir os serviços que fornecem, o que ajuda outros dispositivos a descobrirem apps semelhantes adequados com mais facilidade.
- Ao determinar qual dispositivo precisa ser o proprietário do grupo da rede, o Wi-Fi Direct examina os recursos de gerenciamento de energia, IU e serviço de cada dispositivo e usa essas informações para escolher aquele que pode lidar com as responsabilidades do servidor de forma mais eficiente.
- O Android não é compatível com o modo Wi-Fi ad-hoc.
Esta lição mostra como encontrar dispositivos próximos e conectar-se a eles usando o Wi-Fi P2P.
Configurar permissões de apps
Para usar o Wi-Fi Direct, adicione as permissões
ACCESS_FINE_LOCATION
,
CHANGE_WIFI_STATE
,
ACCESS_WIFI_STATE
e
INTERNET
ao manifesto.
Caso o app seja destinado ao Android 13 (nível 33 da API) ou versões mais recentes, adicione também a
permissão
NEARBY_WIFI_DEVICES
ao manifesto. O Wi-Fi
Direct não precisa de uma conexão de Internet, mas usa soquetes Java
padrão, que precisam da permissão
INTERNET
. Portanto, você precisa das seguintes permissões para usar o Wi-Fi Direct:
<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"/> ...
Além das permissões anteriores, as seguintes APIs também exigem que o "Modo de localização" esteja ativado:
Configurar um broadcast receiver e um administrador de ponto a ponto
Para usar o Wi-Fi Direct, é preciso detectar intents de transmissão que informam ao
app quando determinados eventos ocorreram. No seu app, instancie um IntentFilter
e configure-o para detectar o seguinte:
WIFI_P2P_STATE_CHANGED_ACTION
- Indica se o Wi-Fi Direct está ativado.
WIFI_P2P_PEERS_CHANGED_ACTION
- Indica que a lista de apps semelhantes disponíveis mudou.
WIFI_P2P_CONNECTION_CHANGED_ACTION
-
Indica que o estado da conectividade do Wi-Fi Direct mudou. A partir do Android 10, essa opção não é fixa. Caso seu app dependia do recebimento dessas transmissões no momento de registro porque elas eram fixas, use o método
get
apropriado na inicialização para conseguir as informações. WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
- Indica que os detalhes de configuração deste dispositivo mudaram. A partir do Android 10, essa opção não é fixa. Caso seu app dependia do recebimento dessas transmissões no momento de registro porque elas eram fixas, use o método
get
apropriado na inicialização para conseguir as informações.
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); ... }
No final do método onCreate()
, receba uma instância do WifiP2pManager
e chame o método initialize()
. Esse método retorna um objeto WifiP2pManager.Channel
, que você usará depois para
conectar seu app ao framework do 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); }
Agora, crie uma nova classe BroadcastReceiver
, que você usará para detectar as mudanças
feitas no estado do Wi-Fi do sistema. No método onReceive()
, adicione uma condição para processar cada mudança de estado listada acima.
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)); } }
Por fim, adicione um código para registrar o filtro de intent e o broadcast receiver quando sua atividade principal estiver ativa e cancele o registro quando a atividade estiver pausada.
O melhor lugar para fazer isso são nos métodos 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); }
Iniciar descoberta de pontos
Para começar a procurar dispositivos próximos com Wi-Fi P2P, chame discoverPeers()
. Esse método tem os seguintes argumentos:
- O
WifiP2pManager.Channel
que você recebeu ao inicializar o mManager ponto a ponto - Uma implementação de
WifiP2pManager.ActionListener
com métodos que o sistema invoca para uma descoberta bem-sucedida ou não.
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. } });
Lembre-se de que isso apenas inicia a descoberta de apps semelhantes. O método discoverPeers()
inicia o processo de descoberta e retorna imediatamente. O sistema notifica se o processo de descoberta de apps semelhantes foi iniciado com êxito, chamando métodos no listener de ação fornecido.
Além disso, a descoberta permanece ativa até que uma conexão seja iniciada ou que um grupo P2P seja formado.
Buscar a lista de apps semelhantes
Agora, escreva o código que busca e processa a lista de pontos. Primeiro,
implemente a interface WifiP2pManager.PeerListListener
,
que fornece informações sobre os pares que o Wi-Fi Direct detectou. Essas informações também permitem que seu app determine quando os apps semelhantes entram ou saem da rede. O snippet de código a seguir ilustra essas operações relacionadas a apps semelhantes.
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; } } }
Agora, modifique o método onReceive()
do seu broadcast receiver para chamar requestPeers()
quando uma intent com a ação WIFI_P2P_PEERS_CHANGED_ACTION
for recebida. Você precisa transmitir esse listener ao receiver de alguma forma. Uma maneira é enviá-lo como argumento ao construtor do broadcast receiver.
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"); }... }
Agora, um intent com o intent de ação WIFI_P2P_PEERS_CHANGED_ACTION
aciona uma solicitação de uma lista atualizada de apps semelhantes.
Conectar-se a um app semelhante
Para se conectar a um app semelhante, crie um novo objeto WifiP2pConfig
e copie nele os dados do WifiP2pDevice
que representa o dispositivo ao qual você quer se conectar. Depois, chame o método 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 cada um dos dispositivos do seu grupo for compatível com Wi-Fi Direct, você não precisará pedir explicitamente a senha do grupo ao se conectar. No entanto, para permitir que um dispositivo incompatível com Wi-Fi Direct participe de um grupo, chame requestGroupInfo()
, conforme mostrado no snippet de código a seguir.
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(); } });
Observe que o WifiP2pManager.ActionListener
implementado no método connect()
notificará você apenas quando a iniciação for bem-sucedida ou não. Para detectar alterações no estado da conexão, implemente a interface WifiP2pManager.ConnectionInfoListener
.
Seu callback onConnectionInfoAvailable()
notifica quando o estado da conexão muda. Nos casos em que vários dispositivos serão conectados a um único dispositivo (como um jogo com três ou mais jogadores ou um app de bate-papo), um dispositivo é designado "proprietário do grupo". É possível designar um dispositivo específico como o proprietário do grupo da rede seguindo as etapas descritas na seção Criar um grupo.
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. } }
Agora, volte para o método onReceive()
do broadcast receiver e modifique a seção que detecta uma intent WIFI_P2P_CONNECTION_CHANGED_ACTION
.
Quando essa intent for recebida, chame requestConnectionInfo()
. Como essa é uma chamada assíncrona, os resultados são recebidos pelo listener de informações da conexão que você fornece como parâmetro.
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); } ...
Criar um grupo
Para que o dispositivo que está executando seu app seja o proprietário do grupo de uma rede com dispositivos legados, ou seja, não compatíveis com Wi-Fi Direct, siga a mesma sequência de etapas da seção Conectar-se a um app semelhante, a menos que você crie um novo WifiP2pManager.ActionListener
usando createGroup()
em vez de connect()
. O processamento de callback é o mesmo no WifiP2pManager.ActionListener
, conforme mostrado no snippet de código a seguir.
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(); } });
Observação: se todos os dispositivos de uma rede forem compatíveis com o Wi-Fi Direct, você poderá usar o método connect()
em cada um deles, porque esse método cria o grupo e seleciona um proprietário de grupo automaticamente.
Depois de criar um grupo, você pode chamar requestGroupInfo()
para recuperar detalhes sobre os apps semelhantes da rede, incluindo nomes de dispositivos e status de conexões.