A primeira lição nesta aula, Como usar a descoberta de serviços de rede, mostrou como descobrir serviços que estão conectados a uma rede local. No entanto, a descoberta de serviços via Wi-Fi Direct (P2P) permite que você descubra os serviços de dispositivos próximos diretamente, sem precisar se conectar a uma rede. Você também pode anunciar os serviços em execução no seu dispositivo. Esses recursos ajudam você a se comunicar entre apps, mesmo quando nenhuma rede local ou ponto de acesso está disponível.
Embora esse conjunto de APIs tenha uma finalidade semelhante à das APIs de descobertas de serviço de rede descritas em uma lição anterior, a implementação delas no código é muito diferente. Esta lição mostra como descobrir serviços disponíveis em outros dispositivos usando o Wi-Fi Direct. A lição pressupõe que você já conhece a API Wi-Fi Direct.
Configurar o manifesto
Para usar o Wi-Fi P2P, adicione as permissões CHANGE_WIFI_STATE
, ACCESS_WIFI_STATE
,
ACCESS_FINE_LOCATION
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. Embora o Wi-Fi Direct não precise de uma
conexão de Internet, ele usa soquetes Java padrão. Para usá-los no Android, as permissões solicitadas são necessárias.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.nsdchat" ... <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"/> <!-- 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" /> ...
Além das permissões anteriores, as seguintes APIs também exigem que o "Modo de localização" esteja ativado:
Adicionar um serviço local
Se você estiver fornecendo um serviço local, será necessário registrá-lo na descoberta de serviços. Depois que o serviço local for registrado, o framework vai responder automaticamente às solicitações de descoberta de serviços dos apps semelhantes.
Para criar um serviço local:
- Crie um
objeto
WifiP2pServiceInfo
. - Preencha-o com informações sobre seu serviço.
- Chame
addLocalService()
para registrar o serviço local na descoberta de serviços.
Kotlin
private fun startRegistration() { // Create a string map containing information about your service. val record: Map<String, String> = mapOf( "listenport" to SERVER_PORT.toString(), "buddyname" to "John Doe${(Math.random() * 1000).toInt()}", "available" to "visible" ) // Service information. Pass it an instance name, service type // _protocol._transportlayer , and the map containing // information other devices will want once they connect to this one. val serviceInfo = WifiP2pDnsSdServiceInfo.newInstance("_test", "_presence._tcp", record) // Add the local service, sending the service info, network channel, // and listener that will be used to indicate success or failure of // the request. manager.addLocalService(channel, serviceInfo, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Command successful! Code isn't necessarily needed here, // Unless you want to update the UI or add logging statements. } override fun onFailure(arg0: Int) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY } }) }
Java
private void startRegistration() { // Create a string map containing information about your service. Map record = new HashMap(); record.put("listenport", String.valueOf(SERVER_PORT)); record.put("buddyname", "John Doe" + (int) (Math.random() * 1000)); record.put("available", "visible"); // Service information. Pass it an instance name, service type // _protocol._transportlayer , and the map containing // information other devices will want once they connect to this one. WifiP2pDnsSdServiceInfo serviceInfo = WifiP2pDnsSdServiceInfo.newInstance("_test", "_presence._tcp", record); // Add the local service, sending the service info, network channel, // and listener that will be used to indicate success or failure of // the request. manager.addLocalService(channel, serviceInfo, new ActionListener() { @Override public void onSuccess() { // Command successful! Code isn't necessarily needed here, // Unless you want to update the UI or add logging statements. } @Override public void onFailure(int arg0) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY } }); }
Descobrir serviços nas proximidades
O Android usa métodos de callback para notificar seu aplicativo sobre os serviços disponíveis. Portanto, a primeira coisa a fazer é configurá-los. Crie um WifiP2pManager.DnsSdTxtRecordListener
para detectar
registros de entrada. Esse registro pode ser transmitido de forma opcional por outros
dispositivos. Quando algum surgir, copie o endereço do dispositivo e qualquer outra
informação relevante que você quiser para uma estrutura de dados externa ao método
atual para que você possa acessá-lo posteriormente. O exemplo a seguir pressupõe que o
registro contém um campo "buddyname", preenchido com a identidade do usuário.
Kotlin
private val buddies = mutableMapOf<String, String>() ... private fun discoverService() { /* Callback includes: * fullDomain: full domain name: e.g. "printer._ipp._tcp.local." * record: TXT record dta as a map of key/value pairs. * device: The device running the advertised service. */ val txtListener = DnsSdTxtRecordListener { fullDomain, record, device -> Log.d(TAG, "DnsSdTxtRecord available -$record") record["buddyname"]?.also { buddies[device.deviceAddress] = it } } }
Java
final HashMap<String, String> buddies = new HashMap<String, String>(); ... private void discoverService() { DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() { @Override /* Callback includes: * fullDomain: full domain name: e.g. "printer._ipp._tcp.local." * record: TXT record dta as a map of key/value pairs. * device: The device running the advertised service. */ public void onDnsSdTxtRecordAvailable( String fullDomain, Map record, WifiP2pDevice device) { Log.d(TAG, "DnsSdTxtRecord available -" + record.toString()); buddies.put(device.deviceAddress, record.get("buddyname")); } }; }
Para acessar as informações de serviço, crie um WifiP2pManager.DnsSdServiceResponseListener
. Ele
recebe a descrição real e as informações de conexão. O snippet de código
anterior implementou um objeto Map
para parear o endereço de um dispositivo com o nome
do amigo. O listener de resposta do serviço usa isso para vincular o registro DNS às
informações de serviço correspondentes. Depois que os dois
listeners forem implementados, adicione-os ao WifiP2pManager
usando o método setDnsSdResponseListeners()
.
Kotlin
private fun discoverService() { ... val servListener = DnsSdServiceResponseListener { instanceName, registrationType, resourceType -> // Update the device name with the human-friendly version from // the DnsTxtRecord, assuming one arrived. resourceType.deviceName = buddies[resourceType.deviceAddress] ?: resourceType.deviceName // Add to the custom adapter defined specifically for showing // wifi devices. val fragment = fragmentManager .findFragmentById(R.id.frag_peerlist) as WiFiDirectServicesList (fragment.listAdapter as WiFiDevicesAdapter).apply { add(resourceType) notifyDataSetChanged() } Log.d(TAG, "onBonjourServiceAvailable $instanceName") } manager.setDnsSdResponseListeners(channel, servListener, txtListener) ... }
Java
private void discoverService() { ... DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() { @Override public void onDnsSdServiceAvailable(String instanceName, String registrationType, WifiP2pDevice resourceType) { // Update the device name with the human-friendly version from // the DnsTxtRecord, assuming one arrived. resourceType.deviceName = buddies .containsKey(resourceType.deviceAddress) ? buddies .get(resourceType.deviceAddress) : resourceType.deviceName; // Add to the custom adapter defined specifically for showing // wifi devices. WiFiDirectServicesList fragment = (WiFiDirectServicesList) getFragmentManager() .findFragmentById(R.id.frag_peerlist); WiFiDevicesAdapter adapter = ((WiFiDevicesAdapter) fragment .getListAdapter()); adapter.add(resourceType); adapter.notifyDataSetChanged(); Log.d(TAG, "onBonjourServiceAvailable " + instanceName); } }; manager.setDnsSdResponseListeners(channel, servListener, txtListener); ... }
Agora, crie uma solicitação de serviço e chame addServiceRequest()
.
Esse método também usa um listener para informar o sucesso ou a falha.
Kotlin
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance() manager.addServiceRequest( channel, serviceRequest, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Success! } override fun onFailure(code: Int) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY } } )
Java
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance(); manager.addServiceRequest(channel, serviceRequest, new ActionListener() { @Override public void onSuccess() { // Success! } @Override public void onFailure(int code) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY } });
Por fim, faça a chamada para discoverServices()
.
Kotlin
manager.discoverServices( channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Success! } override fun onFailure(code: Int) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY when (code) { WifiP2pManager.P2P_UNSUPPORTED -> { Log.d(TAG, "Wi-Fi Direct isn't supported on this device.") } } } } )
Java
manager.discoverServices(channel, new ActionListener() { @Override public void onSuccess() { // Success! } @Override public void onFailure(int code) { // Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY if (code == WifiP2pManager.P2P_UNSUPPORTED) { Log.d(TAG, "Wi-Fi Direct isn't supported on this device."); else if(...) ... } });
Se tudo correr bem, você já terminou. Se houver problemas, lembre-se de que
as chamadas assíncronas que você fez precisam de um
WifiP2pManager.ActionListener
como argumento, e
isso fornece callbacks indicando sucesso ou falha. Para diagnosticar
problemas, coloque a depuração do código em onFailure()
. O código de erro
fornecido pelo método indica o problema. Confira os possíveis valores de erro
e o que eles significam
-
P2P_UNSUPPORTED
- O Wi-Fi Direct não é compatível com o dispositivo que executa o app.
-
BUSY
- O sistema está ocupado demais para processar a solicitação.
-
ERROR
- A operação falhou devido a um erro interno.