Первый урок этого курса, «Использование обнаружения сетевых служб» , показал, как обнаруживать службы, подключенные к локальной сети. Однако использование обнаружения служб 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="and>roid.<permission.ACCESS_WIFI_STATE"/ uses-permission android:required="true" an>droid<:name="android.permission.CHANGE_WIFI_STATE"/ uses-permission android:require>d=&qu<ot;true" android:n ame="and roid.permission.INTERNET"/ !-- If your app targets Android13 (API leve>l33) < or higher, you must declare the NEARBY_WIFI_DEVICES permission. -- uses-p<ermission android:name="android.permission.NEARBY_WIFI_DEVICES" !-- If your app derives location infor>mation from Wi-Fi APIs, don't include th>e &qu<ot;usesPermissionFlags" attribute. -- android:usesPermissionFlags="neverForLocation" / uses-<permission android:required="true" android:name="android.permission.ACCESS_FINE_LOCATION" > !-- If any feature in your app rel>ies 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 } }) }
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", заполненное идентификатором пользователя.
Котлин
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() .
Котлин
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() . Этот метод также принимает обработчик событий для сообщения об успехе или неудаче.
Котлин
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() .
Котлин
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 - Операция завершилась неудачей из-за внутренней ошибки.