https://developer.android.com/training/connect-devices-wirelessly/wifi-direct
通過 Wi-Fi 直連創建點對點連接
通過 Wi-Fi 直連(也稱為對等連接或點對點連接),您的應用可以在超出藍牙功能的范圍內快速查找附近的設備並與之互動。
通過 Wi-Fi 對等連接(點對點連接)API,應用無需連接到網絡或熱點就可以連接到附近的設備。如果您的應用旨在成為安全、近距離網絡的一部分,則 Wi-Fi 直連選項比傳統 Wi-Fi 臨時網絡更合適,原因如下:
Wi-Fi 直連支持 WPA2 加密。(一些臨時網絡僅支持 WEP 加密。)
設備可以廣播其提供的服務,這有助於其他設備更容易地發現合適的對等設備。
在確定哪個設備應該是網絡的群組所有者時,Wi-Fi 直連會檢查各設備的電源管理、界面和服務功能,並使用該信息選擇可最有效處理服務器職責的設備。
Android 不支持 Wi-Fi 臨時模式。
本課介紹如何使用 Wi-Fi 點對點連接查找並連接附近的設備。
設置應用權限
如需使用 Wi-Fi 點對點,請在清單中添加 ACCESS_FINE_LOCATION、CHANGE_WIFI_STATE、ACCESS_WIFI_STATE 和 INTERNET 權限。盡管 Wi-Fi P2P 不需要互聯網連接,但它使用標准 Java 套接字,而這需要獲得 INTERNET 權限。因此,您需要以下權限才能使用 Wi-Fi 點對點連接:
<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_FINE_LOCATION"/>
<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 還需要啟用位置信息模式:
discoverPeers
discoverServices
requestPeers
設置廣播接收器和對等連接管理器
如需使用 Wi-Fi 點對點連接,您需要監聽廣播 intent,其告知您的應用某些事件是何時發生的。在您的應用中,實例化 IntentFilter 並將其設置為監聽以下內容:
WIFI_P2P_STATE_CHANGED_ACTION
指示是否啟用 Wi-Fi 點對點連接
WIFI_P2P_PEERS_CHANGED_ACTION
指示可用的對等設備列表已更改。
WIFI_P2P_CONNECTION_CHANGED_ACTION
指示 Wi-Fi 點對點連接的狀態已更改。從 Android 10 開始,這不是固定的。如果您的應用依賴於在注冊時接收這些廣播(因為其之前一直是固定的),請在初始化時使用適當的 get 方法獲取信息。
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
指示此設備的配置詳細信息已更改。從 Android 10 開始,這不是固定的。如果您的應用依賴於在注冊時接收這些廣播(因為其之前一直是固定的),請在初始化時使用適當的 get 方法獲取信息。
Kotlin
Java
private val intentFilter = IntentFilter()
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
// Indicates a change in the Wi-Fi P2P 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 P2P 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 點對點連接框架。
Kotlin
Java
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)
}
現在創建一個新的 BroadcastReceiver 類,您將使用該類監聽對系統的 Wi-Fi 點對點連接狀態進行的更改。在 onReceive() 方法中,添加一個條件以處理上面列出的每個點對點連接狀態更改。
Kotlin
Java
override fun onReceive(context: Context, intent: Intent) {
when(intent.action) {
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
// Determine if Wifi P2P 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
)
}
}
}
}
最后,添加代碼以在您的主 Activity 處於活動狀態時注冊 intent 過濾器和廣播接收器,並在該 Activity 暫停時取消注冊 intent 過濾器和廣播接收器。最好使用 onResume() 和 onPause() 方法。
Kotlin
Java
/** 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)
}
啟動對等設備發現
如需使用 Wi-Fi 點對點連接開始搜索附近的設備,請調用 discoverPeers()。此方法采用以下參數:
WifiP2pManager.Channel,在初始化對等設備 mManager 時收回的參數
WifiP2pManager.ActionListener 的實現,包含系統為成功和未成功發現調用的方法。
Kotlin
Java
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.
}
})
請記住,這僅啟動對等設備發現。discoverPeers() 方法啟動發現過程,然后立即返回。系統通過在提供的操作監聽器中調用方法通知您是否成功啟動對等設備發現過程。此外,在啟動某個連接或形成點對點連接群組之前,發現一直處於活躍狀態。
獲取對等設備列表
現在編寫獲取並處理對等設備列表的代碼。首先實現 WifiP2pManager.PeerListListener 接口,該接口提供有關 Wi-Fi 點對點連接檢測到的對等設備的信息。通過這些信息,您的應用還可以確定對等設備何時加入或離開網絡。以下代碼段展示了這些與對等設備相關的操作:
Kotlin
Java
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
}
}
現在修改廣播接收器的 onReceive() 方法,以便在收到具有操作 WIFI_P2P_PEERS_CHANGED_ACTION 的 intent 時調用 requestPeers()。您需要以某種方式將此監聽器傳遞給接收器。一種方式是將其作為一個參數發送給廣播接收器的構造函數。
Kotlin
Java
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")
}
...
}
}
現在,具有操作 WIFI_P2P_PEERS_CHANGED_ACTION intent 的 intent 觸發對更新的對等設備列表的請求。
連接到對等設備
如需連接到對等設備,請創建一個新的 WifiP2pConfig 對象,並從代表您要連接到的設備的 WifiP2pDevice 將數據復制到該對象中。然后調用 connect() 方法。
Kotlin
Java
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()
}
})
}
如果群組中的每個設備都支持 Wi-Fi 直連,則在連接時無需明確提示輸入群組的密碼。但如需允許不支持 Wi-Fi 直連的設備加入群組,則需要通過調用 requestGroupInfo() 檢索此密碼,如以下代碼段所示:
Kotlin
Java
manager.requestGroupInfo(channel) { group ->
val groupPassword = group.passphrase
}
請注意,在 connect() 方法中實現的 WifiP2pManager.ActionListener 僅在啟動成功或失敗時通知您。如需監聽連接狀態的更改,請實現 WifiP2pManager.ConnectionInfoListener 接口。其 onConnectionInfoAvailable() 回調在連接狀態更改時通知您。如果多個設備要連接到單個設備(就像有三個或更多玩家的游戲,或聊天應用),應將一個設備指定為“群組所有者”。您可以按照創建群組部分中的步驟,將特定設備指定為網絡的群組所有者。
Kotlin
Java
private val connectionListener = WifiP2pManager.ConnectionInfoListener { info ->
// InetAddress 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.
}
}
現在返回廣播接收器的 onReceive()方法,並修改監聽 WIFI_P2P_CONNECTION_CHANGED_ACTION intent 的部分。收到此 intent 時,調用 requestConnectionInfo()。這是一個異步調用,因此,由您作為參數提供的連接信息監聽器接收結果。
Kotlin
Java
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)
}
}
}
...
}
創建群組
如果您希望運行應用的設備充當包括傳統設備(即不支持 Wi-Fi 直連的設備)的網絡的群組所有者,則請遵循連接到對等設備部分中的相同步驟序列,除非您使用 createGroup() 而不是 connect() 創建新的 WifiP2pManager.ActionListener。WifiP2pManager.ActionListener 中的回調處理方式相同,如以下代碼段所示:
Kotlin
Java
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()
}
})
注意:如果網絡中的所有設備都支持 Wi-Fi 直連,則可在每個設備上使用 connect() 方法,因為該方法隨后將自動創建群組並選擇群組所有者。
創建群組后,可以調用 requestGroupInfo() 檢索有關網絡上對等設備的詳細信息,包括設備名稱和連接狀態。