Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

Wi-Fi Direct(P2P)を使用してサービスを検出する

このクラスの最初のレッスンであるネットワーク サービス ディスカバリを使用するでは、ローカル ネットワークに接続されているサービスを検出する方法を説明しました。しかし、Wi-Fi ピアツーピア(P2P)サービス ディスカバリを使用すると、ネットワークに接続せずに、周辺のデバイスのサービスを直接検出できます。デバイスで実行中のサービスをアドバタイズすることもできます。これらの機能は、ローカル ネットワークやアクセス ポイントが利用できない場合でもアプリ間の通信を行うのに役立ちます。

この API のセットの目的は前のレッスンで紹介した Network Service Discovery API と似ていますが、コード内での実装はかなり異なります。このレッスンでは、Wi-Fi P2P を使用して他のデバイスから利用できるサービスを検出する方法を説明します。このレッスンは、Wi-Fi P2P API の知識がすでにあることを前提としています。

マニフェストをセットアップする

Wi-Fi P2P を使用するには、CHANGE_WIFI_STATE 権限、ACCESS_WIFI_STATE 権限、ACCESS_FINE_LOCATION 権限、INTERNET 権限をマニフェストに追加します。Wi-Fi P2P はインターネット接続を必要としませんが、標準の 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.ACCESS_FINE_LOCATION"/>
        <uses-permission
            android:required="true"
            android:name="android.permission.INTERNET"/>
        ...
    

次の API については、前述の権限に加えて、位置情報モードを有効にすることも必要です。

ローカル サービスを追加する

ローカル サービスを提供するには、サービス ディスカバリ用に登録する必要があります。ローカル サービスが登録されると、フレームワークはピアからのサービス ディスカバリ リクエストに自動的に応答します。

ローカル サービスを作成するには:

  1. WifiP2pServiceInfo オブジェクトを作成します。
  2. このオブジェクトにサービスに関する情報を指定します。
  3. 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, "P2P 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, "P2P isn't supported on this device.");
                    else if(...)
                        ...
                }
            });
    

うまくいったでしょうか。問題が生じた場合は、実行した非同期呼び出しが引数として WifiP2pManager.ActionListener を受け取ることと、このリスナーが成功または失敗を示すコールバックを提供することを思い出してください。問題を診断するには、onFailure() にデバッグコードを挿入します。このメソッドが提供するエラーコードは、問題解決のヒントになります。発生する可能性があるエラー値とその意味を以下に示します。

P2P_UNSUPPORTED
アプリを実行しているデバイスで Wi-Fi P2P がサポートされていません。
BUSY
システムが過度なビジー状態にあるためリクエストを処理できません。
ERROR
内部エラーが原因でオペレーションが失敗しました。