本課程的第一堂課「使用網路服務探索功能」已說明如何探索連線至本機網路的服務。不過,使用 Wi-Fi Direct (P2P) 服務探索功能,您可以直接探索鄰近裝置的服務,而無須連線至網路。您也可以宣傳裝置上執行的服務。即使沒有可用的本機網路或無線基地台,這些功能仍可協助您在應用程式之間進行通訊。
雖然這組 API 的用途與上一課所述的網路服務探索 API 相似,但在程式碼中實作的方式卻截然不同。本課程將說明如何使用 Wi-Fi Direct 探索其他裝置提供的服務。本課程假設您已熟悉 Wi-Fi Direct API。
設定資訊清單
如要使用 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 記錄連結至相應的服務資訊。實作兩個事件監聽器後,請使用 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
- 執行應用程式的裝置不支援 Wi-Fi Direct。
-
BUSY
- 系統太忙,無法處理要求。
-
ERROR
- 發生內部錯誤,因此作業失敗。