このクラスの最初のレッスンであるネットワーク サービス ディスカバリを使用するでは、ローカル ネットワークに接続されているサービスを検出する方法を説明しました。しかし、Wi-Fi Direct(P2P)サービス ディスカバリを使用すると、ネットワークに接続せずに、周辺のデバイスのサービスを直接検出できます。デバイスで実行中のサービスをアドバタイズすることもできます。これらの機能は、ローカル ネットワークやアクセス ポイントが利用できない場合でもアプリ間の通信を行うのに役立ちます。
この API のセットの目的は前のレッスンで紹介した Network Service Discovery 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」フィールドがあり、ユーザーの ID が入力されていると仮定しています。
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
オブジェクトを実装して、デバイスのアドレスと buddyname をペアにしていました。サービス レスポンス リスナーは、これを使用して 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
- 内部エラーが原因でオペレーションが失敗しました。