На первом уроке этого курса, Использование обнаружения сетевых сервисов, вы узнали, как обнаруживать сервисы, подключенные к локальной сети. Однако с помощью обнаружения сервисов Wi-Fi Direct (P2P) вы можете обнаруживать сервисы устройств поблизости напрямую, без подключения к сети. Вы также можете рекламировать сервисы, работающие на вашем устройстве. Эти возможности позволяют обмениваться данными между приложениями, даже если локальная сеть или точка доступа недоступны.
Хотя этот набор API похож по назначению на API обнаружения сетевых сервисов, описанные в предыдущем уроке, их реализация в коде сильно отличается. В этом уроке показано, как обнаруживать сервисы, доступные на других устройствах, с помощью Wi-Fi Direct. Предполагается, что вы уже знакомы с API Wi-Fi Direct.
Настройка манифеста
Чтобы использовать Wi-Fi P2P, добавьте в манифест разрешения CHANGE_WIFI_STATE, ACCESS_WIFI_STATE, ACCESS_FINE_LOCATION и INTERNET. Если приложение предназначено для Android 13 (уровень API 33) или более поздней версии, добавьте в манифест разрешение NEARBY_WIFI_DEVICES. Хотя для Wi-Fi Direct не требуется подключение к интернету, эта технология использует стандартные сокеты Java, для работы с которыми в Android необходимы запрашиваемые разрешения.
<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" /> ...
Помимо перечисленных выше разрешений, для следующих API также требуется включить режим определения местоположения:
Добавить местную услугу
Если вы предоставляете локальный сервис, его нужно зарегистрировать для обнаружения сервисов. После регистрации локального сервиса фреймворк автоматически отвечает на запросы обнаружения сервисов от одноранговых узлов.
Чтобы создать местную услугу, выполните следующие действия:
- Создайте объект
WifiP2pServiceInfo. - Заполните его информацией о своем сервисе.
- Вызовите метод
addLocalService(), чтобы зарегистрировать локальный сервис для обнаружения сервисов.
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 } }); }
Поиск сервисов поблизости
Android использует методы обратного вызова, чтобы уведомлять приложение о доступных сервисах, поэтому в первую очередь нужно настроить их. Создайте WifiP2pManager.DnsSdTxtRecordListener, чтобы прослушивать входящие записи. Эту запись могут транслировать и другие устройства. Когда вы получите ответ, скопируйте адрес устройства и другую нужную информацию в структуру данных, внешнюю по отношению к текущему методу, чтобы вы могли получить к ней доступ позже. В примере ниже предполагается, что запись содержит поле buddyname, заполненное идентификатором пользователя.
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")); } }; }
Чтобы получить информацию о сервисе, создайте WifiP2pManager.DnsSdServiceResponseListener. Это
получает фактическое описание и информацию о подключении. В предыдущем фрагменте кода был реализован объект Map, который связывает адрес устройства с именем друга. Прослушиватель ответов сервиса использует его, чтобы связать запись DNS с соответствующей информацией о сервисе. После реализации обоих прослушивателей добавьте их в WifiP2pManager с помощью метода 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); ... }
Теперь создайте запрос на обслуживание и позвоните по номеру addServiceRequest().
Этот метод также принимает прослушиватель, чтобы сообщать об успехе или неудаче.
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 } });
Наконец, позвоните в 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(...) ... } });
Если все пройдет хорошо, то ура, вы закончили! Если у вас возникнут проблемы, помните, что асинхронные вызовы, которые вы сделали, принимают WifiP2pManager.ActionListener в качестве аргумента, и это предоставляет вам обратные вызовы, указывающие на успех или неудачу. Чтобы диагностировать проблемы, добавьте в onFailure() код отладки. Код ошибки, возвращаемый методом, указывает на проблему. Вот возможные значения ошибок
и их значения
-
P2P_UNSUPPORTED - Wi-Fi Direct не поддерживается на устройстве, на котором запущено приложение.
-
BUSY - Система слишком занята, чтобы обработать запрос.
-
ERROR - Не удалось выполнить операцию из-за внутренней ошибки.