Wi-Fi Direct (也稱為點對點或 P2P) 可讓應用程式快速尋找鄰近裝置並進行互動 裝置的藍牙功能
Wi-Fi Direct (P2P) API 可讓應用程式在沒有 Wi-Fi Direct (P2P) API 的情況下與附近的裝置連線。 需要連線至網路或無線基地台。 如果您的應用程式是專為安全、鄰近範圍的網路或 Wi-Fi 設計 比起傳統 Wi-Fi 臨時檔案,直接流量是更合適的選項 網路的原因如下:
- 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
直接連結不需要網際網路連線,但採用標準 Java
通訊端,需要 INTERNET
權限。因此,您必須具備下列權限,才能使用 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); }
現在請建立新的 BroadcastReceiver
類別,以便監聽變更
切換系統的 Wi-Fi 狀態在「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
您初始化點對點 mManager 後收到的回覆 - 使用方法實作
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()
的方法。個人中心
就需要將此事件監聽器傳遞至接收器其中一個方法是直接傳送
做為廣播接收器建構函式的引數。
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()
回呼會在
連線變更。需要同時連上多部裝置時
在單一裝置上 (例如有三名以上玩家的遊戲或即時通訊應用程式) 時,一部裝置就大功告成
即為「群組擁有者」您可以指定將特定裝置
確認網路的群組擁有者
「建立群組」部分。
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 功能,請按照
連結至同類應用程式部分,除非您建立新的
WifiP2pManager.ActionListener
使用 createGroup()
,而不是 connect()
。在
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
您可以直接在裝置上使用 connect()
方法,因為
方法建立群組並自動選取版主。
建立群組後,你可以撥打
requestGroupInfo()
:擷取以下資料夾中的對等項目詳細資料:
包括裝置名稱和連線狀態