Первый урок в этом классе, Использование обнаружения сетевых служб , показал вам, как обнаружить службы, подключенные к локальной сети. Однако использование обнаружения служб 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()
, чтобы зарегистрировать локальную службу для обнаружения служб.
Котлин
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 } }) }
Ява
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», заполненное идентификатором пользователя.
Котлин
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 } } }
Ява
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()
.
Котлин
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) ... }
Ява
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()
. Этот метод также принимает прослушиватель для сообщения об успехе или неудаче.
Котлин
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 } } )
Ява
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()
.
Котлин
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.") } } } } )
Ява
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
- Операция не выполнена из-за внутренней ошибки.
Первый урок в этом классе, Использование обнаружения сетевых служб , показал вам, как обнаружить службы, подключенные к локальной сети. Однако использование обнаружения служб 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()
, чтобы зарегистрировать локальную службу для обнаружения служб.
Котлин
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 } }) }
Ява
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», заполненное идентификатором пользователя.
Котлин
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 } } }
Ява
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()
.
Котлин
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) ... }
Ява
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()
. Этот метод также принимает прослушиватель для сообщения об успехе или неудаче.
Котлин
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 } } )
Ява
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()
.
Котлин
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.") } } } } )
Ява
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
- Операция не выполнена из-за внутренней ошибки.