บทเรียนแรกในคลาสนี้คือ การใช้การสำรวจบริการเครือข่าย ซึ่งแสดงวิธี ค้นหาบริการที่เชื่อมต่อกับเครือข่ายภายใน อย่างไรก็ตาม การใช้การสำรวจบริการ Wi-Fi Direct (P2P) จะช่วยให้คุณค้นหาบริการของอุปกรณ์ที่อยู่ใกล้เคียงได้โดยตรงโดยไม่ต้องเชื่อมต่อกับเครือข่าย นอกจากนี้ คุณยังโฆษณาบริการที่ทำงานบนอุปกรณ์ได้ด้วย ความสามารถเหล่านี้ช่วยให้คุณสื่อสารระหว่างแอปได้แม้ว่าจะไม่มีเครือข่ายภายในหรือฮอตสปอต
แม้ว่าชุด API นี้จะมีวัตถุประสงค์คล้ายกับ API การสำรวจบริการเครือข่ายที่ระบุไว้ในบทเรียนก่อนหน้า แต่การนำ API เหล่านี้ไปใช้ในโค้ดนั้นแตกต่างกันมาก บทเรียนนี้จะแสดงวิธีค้นหาบริการที่พร้อมให้บริการจากอุปกรณ์อื่นๆ โดยใช้ Wi-Fi Direct บทเรียนนี้ถือว่าคุณคุ้นเคยกับ Wi-Fi Direct API อยู่แล้ว
ตั้งค่าไฟล์ Manifest
หากต้องการใช้ Wi-Fi P2P ให้เพิ่มสิทธิ์ CHANGE_WIFI_STATE, ACCESS_WIFI_STATE,
ACCESS_FINE_LOCATION,
และ INTERNET
ลงในไฟล์ Manifest หากแอปของคุณกำหนดเป้าหมายเป็น Android 13 (ระดับ API 33) ขึ้นไป ให้เพิ่มสิทธิ์ NEARBY_WIFI_DEVICES ลงในไฟล์ Manifest ด้วย แม้ว่า 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 ต่อไปนี้ยังกำหนดให้ต้องเปิดใช้โหมดตำแหน่งด้วย
เพิ่มบริการในพื้นที่
หากคุณให้บริการในพื้นที่ คุณต้องลงทะเบียนบริการสำหรับ Service Discovery เมื่อลงทะเบียนบริการในพื้นที่แล้ว เฟรมเวิร์กจะตอบสนองต่อคำขอ Service Discovery จากแอปเทียบเท่าโดยอัตโนมัติ
วิธีสร้างบริการในพื้นที่
- สร้างออบเจ็กต์
WifiP2pServiceInfo - ป้อนข้อมูลเกี่ยวกับบริการ
- เรียกใช้
addLocalService()เพื่อลงทะเบียนบริการในพื้นที่สำหรับ Service Discovery
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 กับข้อมูลบริการที่เกี่ยวข้อง เมื่อใช้เครื่องมือฟังทั้ง 2 รายการแล้ว ให้เพิ่มเครื่องมือฟังเหล่านั้นลงใน 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()
เมธอดนี้ยังใช้ Listener เพื่อรายงานความสำเร็จหรือความล้มเหลวด้วย
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 - การดำเนินการล้มเหลวเนื่องจากข้อผิดพลาดภายใน