本课程的第一课:使用网络服务 发现,为您显示了 如何发现连接到本地网络的服务。不过,如果使用 借助 Wi-Fi 直连(点对点)服务发现功能,您可以发现附近设备的服务 而无需连接到网络您还可以宣传这些服务 运行状态这些功能可帮助您在应用之间 即使没有可用的本地网络或热点也无妨。
虽然这组 API 在用途上与网络服务发现相似, 上一课中介绍的 API 和在代码中实现它们的方式截然不同。 本课将介绍如何发现其他设备提供的服务 使用 Wi-Fi 直连。本课假定您已熟悉 Wi-Fi Direct API。
设置清单
如需使用 Wi-Fi 点对点连接,请添加 CHANGE_WIFI_STATE
、ACCESS_WIFI_STATE
、
ACCESS_FINE_LOCATION
,
和INTERNET
对清单的权限。如果您的应用以 Android 13(API 级别 33)或更高版本为目标平台,也请添加
NEARBY_WIFI_DEVICES
,
权限。即使 Wi-Fi 直连不需要
连接互联网时,它使用标准 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 记录与
相应的服务信息。将
监听器已实现,请使用 setDnsSdResponseListeners()
方法将它们添加到 WifiP2pManager
。
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
- 运行此应用的设备不支持 WLAN 直连。
-
BUSY
- 系统太忙,无法处理请求。
-
ERROR
- 由于内部错误,操作失败。