Wi-Fi Direct(ピアツーピアまたは P2P とも呼ばれます)を使用すると、アプリは Bluetooth より広い範囲で、近くのデバイスをすばやく見つけて通信できます。
アプリは、Wi-Fi Direct(P2P)API を使用して、ネットワークまたはアクセス ポイントに接続する手間をかけずに、近くのデバイスに接続できます。アプリが安全な近距離ネットワークの一部として機能するように設計されている場合、従来の Wi-Fi アドホック ネットワークより Wi-Fi Direct のほうが適切です。それは、以下の理由によります。
- Wi-Fi Direct は WPA2 暗号化をサポートします(一部のアドホック ネットワークは WEP 暗号化のみをサポートします)。
- デバイスは、自らが提供するサービスをブロードキャストできます。これにより、他のデバイスは適切なピアをたやすく発見できます。
- Wi-Fi Direct は、どのデバイスをネットワークのグループ オーナーにするかを決定するにあたって、各デバイスの電源管理、UI、サービス機能を調べ、この情報を使用して、サーバーの責任を最も効果的に果たすことができるデバイスを選択します。
- Android は Wi-Fi アドホック モードをサポートしていません。
このレッスンでは、Wi-Fi P2P を使用して、近くのデバイスを見つけて接続する方法を示します。
アプリの権限をセットアップする
Wi-Fi Direct を使用するには、ACCESS_FINE_LOCATION
、CHANGE_WIFI_STATE
、ACCESS_WIFI_STATE
、INTERNET
権限をマニフェストに追加します。Android 13(API レベル 33)以降をターゲットとするアプリの場合は、NEARBY_WIFI_DEVICES
権限もマニフェストに追加します。Wi-Fi Direct はインターネット接続を必要としませんが、INTERNET
権限を必要とする標準の Java ソケットを使用します。したがって、Wi-Fi Direct を使用するには、次の権限が必要です。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.nsdchat" ... <!-- 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" /> <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"/> ...
次の API については、前述の権限に加えて、位置情報モードを有効にすることも必要です。
ブロードキャスト レシーバとピアツーピア マネージャーをセットアップする
Wi-Fi Direct を使用するには、特定のイベントが発生したときにアプリに知らせるブロードキャスト インテントをリッスンする必要があります。アプリ内で IntentFilter
をインスタンス化して、以下をリッスンするように設定します。
WIFI_P2P_STATE_CHANGED_ACTION
- Wi-Fi Direct が有効かどうかを示します。
WIFI_P2P_PEERS_CHANGED_ACTION
- 利用可能なピアのリストが変更されたことを示します。
WIFI_P2P_CONNECTION_CHANGED_ACTION
-
Wi-Fi Direct 接続の状態が変更されたことを示します。Android 10 以降、この情報はスティッキーではなくなりました。スティッキー ブロードキャストであることを前提として登録時にこれらのブロードキャストを受信する仕様のアプリの場合は、代わりに初期化時に適切な
get
メソッドを使用して情報を取得するようにしてください。 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
-
このデバイスの構成の詳細が変更されたことを示します。Android 10 以降、この情報はスティッキーではなくなりました。スティッキー ブロードキャストであることを前提として登録時にこれらのブロードキャストを受信する仕様のアプリの場合は、代わりに初期化時に適切な
get
メソッドを使用して情報を取得するようにしてください。
Kotlin
private val intentFilter = IntentFilter() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) // Indicates a change in the Wi-Fi Direct status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) // Indicates the state of Wi-Fi Direct connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) ... }
Java
private final IntentFilter intentFilter = new IntentFilter(); ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Indicates a change in the Wi-Fi Direct status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); // Indicates the state of Wi-Fi Direct connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
onCreate()
メソッドの終わりで WifiP2pManager
のインスタンスを取得し、その initialize()
メソッドを呼び出します。このメソッドは WifiP2pManager.Channel
オブジェクトを返します。このオブジェクトは、後でアプリを Wi-Fi Direct フレームワークに接続するために使用します。
Kotlin
private lateinit var channel: WifiP2pManager.Channel private lateinit var manager: WifiP2pManager override fun onCreate(savedInstanceState: Bundle?) { ... manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager channel = manager.initialize(this, mainLooper, null) }
Java
Channel channel; WifiP2pManager manager; @Override public void onCreate(Bundle savedInstanceState) { ... manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); channel = manager.initialize(this, getMainLooper(), null); }
次に、システムの Wi-Fi 状態の変化をリッスンするために使用する新しい BroadcastReceiver
クラスを作成します。onReceive()
メソッドに、上記の各状態の変化を処理するための条件を追加します。
Kotlin
override fun onReceive(context: Context, intent: Intent) { when(intent.action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { // Determine if Wi-Fi Direct mode is enabled or not, alert // the Activity. val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) activity.isWifiP2pEnabled = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED } WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // The peer list has changed! We should probably do something about // that. } WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Connection state changed! We should probably do something about // that. } WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { (activity.supportFragmentManager.findFragmentById(R.id.frag_list) as DeviceListFragment) .apply { updateThisDevice( intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice ) } } } }
Java
@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // Determine if Wi-Fi Direct mode is enabled or not, alert // the Activity. int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { activity.setIsWifiP2pEnabled(true); } else { activity.setIsWifiP2pEnabled(false); } } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // The peer list has changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Connection state changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager() .findFragmentById(R.id.frag_list); fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)); } }
最後に、メイン アクティビティがアクティブになったときにインテント フィルタとブロードキャスト レシーバを登録し、アクティビティが一時停止されたときに登録解除するコードを追加します。これを行うのに最適なメソッドは、onResume()
と onPause()
です。
Kotlin
/** register the BroadcastReceiver with the intent values to be matched */ public override fun onResume() { super.onResume() receiver = WiFiDirectBroadcastReceiver(manager, channel, this) registerReceiver(receiver, intentFilter) } public override fun onPause() { super.onPause() unregisterReceiver(receiver) }
Java
/** register the BroadcastReceiver with the intent values to be matched */ @Override public void onResume() { super.onResume(); receiver = new WiFiDirectBroadcastReceiver(manager, channel, this); registerReceiver(receiver, intentFilter); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); }
ピアの検出を開始する
Wi-Fi P2P を使用して近くのデバイスの検索を開始するには、discoverPeers()
を呼び出します。このメソッドは次の引数を取ります。
- ピアツーピア マネージャーを初期化したときに返信された
WifiP2pManager.Channel
- 検出の成功時と失敗時にシステムが呼び出すメソッドを使用する
WifiP2pManager.ActionListener
の実装
Kotlin
manager.discoverPeers(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } override fun onFailure(reasonCode: Int) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } })
Java
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } @Override public void onFailure(int reasonCode) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } });
このコードはピア検出を開始するのみであることにご注意ください。discoverPeers()
メソッドは、検出プロセスを開始すると、すぐに結果を返します。システムは、指定されたアクション リスナーのメソッドを呼び出すことにより、ピア検出プロセスが正常に開始されたかどうかをアプリに通知します。また、接続が開始されるか P2P グループが形成されるまで、検出はアクティブなままです。
ピアのリストを取得する
次に、ピアのリストを取得して処理するコードを作成します。まず、WifiP2pManager.PeerListListener
インターフェースを実装します。このインターフェースは、Wi-Fi Direct が検出したピアに関する情報を提供します。この情報により、アプリはピアがネットワークに参加するかまたはネットワークを離脱するタイミングを判断することもできます。次のコード スニペットは、ピアに関する上記の操作を示しています。
Kotlin
private val peers = mutableListOf<WifiP2pDevice>() ... private val peerListListener = WifiP2pManager.PeerListListener { peerList -> val refreshedPeers = peerList.deviceList if (refreshedPeers != peers) { peers.clear() peers.addAll(refreshedPeers) // If an AdapterView is backed by this data, notify it // of the change. For instance, if you have a ListView of // available peers, trigger an update. (listAdapter as WiFiPeerListAdapter).notifyDataSetChanged() // Perform any other updates needed based on the new list of // peers connected to the Wi-Fi P2P network. } if (peers.isEmpty()) { Log.d(TAG, "No devices found") return@PeerListListener } }
Java
private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>(); ... private PeerListListener peerListListener = new PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peerList) { List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList(); if (!refreshedPeers.equals(peers)) { peers.clear(); peers.addAll(refreshedPeers); // If an AdapterView is backed by this data, notify it // of the change. For instance, if you have a ListView of // available peers, trigger an update. ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged(); // Perform any other updates needed based on the new list of // peers connected to the Wi-Fi P2P network. } if (peers.size() == 0) { Log.d(WiFiDirectActivity.TAG, "No devices found"); return; } } }
次に、ブロードキャスト レシーバの onReceive()
メソッドを変更して、アクション WIFI_P2P_PEERS_CHANGED_ACTION
を含むインテントが受信されたときに requestPeers()
を呼び出すようにします。このリスナーをなんらかの方法でレシーバに渡す必要があります。その方法の 1 つは、ブロードキャスト レシーバのコンストラクタへの引数としてリスナーを送信することです。
Kotlin
fun onReceive(context: Context, intent: Intent) { when (intent.action) { ... WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // Request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() mManager?.requestPeers(channel, peerListListener) Log.d(TAG, "P2P peers changed") } ... } }
Java
public void onReceive(Context context, Intent intent) { ... else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() if (mManager != null) { mManager.requestPeers(channel, peerListListener); } Log.d(WiFiDirectActivity.TAG, "P2P peers changed"); }... }
これにより、アクション WIFI_P2P_PEERS_CHANGED_ACTION
を含むインテントが、更新されたピアリストに対するリクエストをトリガーします。
ピアに接続する
ピアに接続するには、新しい WifiP2pConfig
オブジェクトを作成し、接続先のデバイスを表す WifiP2pDevice
からそのオブジェクトにデータをコピーします。次に、connect()
メソッドを呼び出します。
Kotlin
override fun connect() { // Picking the first device found on the network. val device = peers[0] val config = WifiP2pConfig().apply { deviceAddress = device.deviceAddress wps.setup = WpsInfo.PBC } manager.connect(channel, config, object : WifiP2pManager.ActionListener { override fun onSuccess() { // WiFiDirectBroadcastReceiver notifies us. Ignore for now. } override fun onFailure(reason: Int) { Toast.makeText( this@WiFiDirectActivity, "Connect failed. Retry.", Toast.LENGTH_SHORT ).show() } }) }
Java
@Override public void connect() { // Picking the first device found on the network. WifiP2pDevice device = peers.get(0); WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; config.wps.setup = WpsInfo.PBC; manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { // WiFiDirectBroadcastReceiver notifies us. Ignore for now. } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.", Toast.LENGTH_SHORT).show(); } }); }
グループ内の各デバイスが Wi-Fi Direct をサポートしている場合、接続時にグループのパスワードを明示的に要求する必要はありません。ただし、Wi-Fi Direct をサポートしていないデバイスがグループに参加できるようにするには、requestGroupInfo()
を呼び出してこのパスワードを取得する必要があります。次のコード スニペットをご覧ください。
Kotlin
manager.requestGroupInfo(channel) { group -> val groupPassword = group.passphrase }
Java
manager.requestGroupInfo(channel, new GroupInfoListener() { @Override public void onGroupInfoAvailable(WifiP2pGroup group) { String groupPassword = group.getPassphrase(); } });
WifiP2pManager.ActionListener
(connect()
メソッドに実装されています)は、開始が成功または失敗したときにだけ通知することに注意してください。接続状態の変化をリッスンするには、WifiP2pManager.ConnectionInfoListener
インターフェースを実装します。このインターフェースの onConnectionInfoAvailable()
コールバックは、接続の状態が変化したときに通知します。複数のデバイスが 1 つのデバイスに接続される場合(3 人以上のプレイヤーが参加するゲームやチャットアプリなど)、1 つのデバイスが「グループ オーナー」に指名されます。特定のデバイスをネットワークのグループ オーナーに指名するには、グループを作成するセクションの手順に沿って操作します。
Kotlin
private val connectionListener = WifiP2pManager.ConnectionInfoListener { info -> // String from WifiP2pInfo struct val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress // After the group negotiation, we can determine the group owner // (server). if (info.groupFormed && info.isGroupOwner) { // Do whatever tasks are specific to the group owner. // One common case is creating a group owner thread and accepting // incoming connections. } else if (info.groupFormed) { // The other device acts as the peer (client). In this case, // you'll want to create a peer thread that connects // to the group owner. } }
Java
@Override public void onConnectionInfoAvailable(final WifiP2pInfo info) { // String from WifiP2pInfo struct String groupOwnerAddress = info.groupOwnerAddress.getHostAddress(); // After the group negotiation, we can determine the group owner // (server). if (info.groupFormed && info.isGroupOwner) { // Do whatever tasks are specific to the group owner. // One common case is creating a group owner thread and accepting // incoming connections. } else if (info.groupFormed) { // The other device acts as the peer (client). In this case, // you'll want to create a peer thread that connects // to the group owner. } }
次に、ブロードキャスト レシーバの onReceive()
メソッドに戻って、WIFI_P2P_CONNECTION_CHANGED_ACTION
インテントをリッスンするセクションを変更します。このインテントが受信されたら、requestConnectionInfo()
を呼び出します。これは非同期呼び出しであるため、パラメータとして指定した接続情報リスナーによって結果が受信されます。
Kotlin
when (intent.action) { ... WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Connection state changed! We should probably do something about // that. mManager?.let { manager -> val networkInfo: NetworkInfo? = intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo if (networkInfo?.isConnected == true) { // We are connected with the other device, request connection // info to find group owner IP manager.requestConnectionInfo(channel, connectionListener) } } } ... }
Java
... } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { if (manager == null) { return; } NetworkInfo networkInfo = (NetworkInfo) intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); if (networkInfo.isConnected()) { // We are connected with the other device, request connection // info to find group owner IP manager.requestConnectionInfo(channel, connectionListener); } ...
グループを作成する
アプリを実行しているデバイスを、以前のデバイス(Wi-Fi Direct をサポートしていないデバイス)を含むネットワークのグループ オーナーとして機能させるには、ピアに接続するセクションと同じ手順を行います。ただし、connect()
ではなく createGroup()
を使用して新しい WifiP2pManager.ActionListener
を作成します。次のコード スニペットに示すように、WifiP2pManager.ActionListener
内のコールバック処理は同じです。
Kotlin
manager.createGroup(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Device is ready to accept incoming connections from peers. } override fun onFailure(reason: Int) { Toast.makeText( this@WiFiDirectActivity, "P2P group creation failed. Retry.", Toast.LENGTH_SHORT ).show() } })
Java
manager.createGroup(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Device is ready to accept incoming connections from peers. } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.", Toast.LENGTH_SHORT).show(); } });
注: ネットワーク内のすべてのデバイスが Wi-Fi Direct に対応している場合は、各デバイスで connect()
メソッドを使用できます。このメソッドは、接続した後でグループを作成し、グループ オーナーを自動的に選択するからです。
グループを作成したら、requestGroupInfo()
を呼び出して、ネットワーク上のピアに関する詳細情報(デバイス名や接続ステータスなど)を取得できます。