走進Android WiFi P2P技術,一探設備間點對點通信實現細節
WiFi P2P技術
WiFi P2P(Peer-to-Peer),也被稱為WiFi Direct,是WiFi聯盟發布的一個協議。允許無線網絡中的設備在無需無線路由器的情況下相互連接,通過WiFi直接實現兩臺設備之間的無線點對點通信。原理與基于AP(接入點)的通信方式類似,支持P2P的設備可以在同一個小組內互傳數據,實現同屏功能。
WiFi P2P被廣泛應用于移動設備之間的文件共享、游戲聯機、音樂播放等應用場景中。相較于藍牙,WiFi P2P具有更快的搜索速度和傳輸速度,以及更遠的傳輸距離。而且只需要打開WiFi即可,不需要加入任何網絡或AP,即可實現對等點連接通訊。對于需要在用戶之間共享數據的應用,如多人游戲或照片共享非常有用。
WiFi P2P也存在一些安全性問題,如用戶隱私泄露、惡意軟件和病毒傳播,以及侵權和違法內容的傳播。為了保護用戶的安全和隱私,一些P2P網絡提供了匿名化處理功能,使用安全搜索引擎,以及設置過濾器來阻止違法和侵權內容的共享。
Android WiFi P2P架構
在P2P架構中,定義了兩種主要角色:P2P Group Owner(簡稱GO)和P2P Client(簡稱GC)。GO的作用類似于Infrastructure BSS中的AP(接入點),而GC的作用類似于Infrastructure BSS中的STA(站點)。當兩臺設備通過P2P連接后,會隨機(也可以手動指定)指派其中一臺設備為組擁有者(GO),相當于一臺服務器,另一臺設備為組成員(GC)。其他設備可以通過與GO設備連接加入組,但不能直接和GC設備連接。
圖片
在Android系統中,WiFi P2P功能是在Android 4.0及更高版本系統中加入的。它可以通過WifiP2pManager類進行實現,這個類提供了許多方法來掃描可用設備、建立P2P連接并傳輸數據等功能。開發者可以通過這些方法來實現設備之間的文件傳輸等操作。
在設備發現階段,Android WiFi P2P使用Probe Request和Probe Response幀來交換設備信息。在2.4GHz的1、6、11頻段上發送Probe Request幀,這幾個頻段被稱為Social Channels。一旦Listen Channel選擇好后,在整個P2P Discovery階段就不能更改,用于快速發現周圍的Group。
盡管Android WiFi P2P功能強大,目前在Android系統中只是內置了設備的搜索和鏈接功能,并沒有像藍牙那樣有許多應用。在實際開發中,可能需要通過軟件手段解決一些邏輯和權限問題。
Android應用WiFi P2P實現數據傳輸
在Android中,WiFi P2P可以通過WifiP2pManager類進行實現。開發者可以通過獲取WifiP2pManager實例,并進行廣播接受者的創建和注冊,調用其他WiFi P2P的API,實現設備間的搜索、連接和數據傳輸等功能。例如,指定某一臺設備為服務器,創建群組并等待客戶端的連接請求,而客戶端則可以主動搜索附近的設備并加入群組,向服務器發起文件傳輸請求。
圖片
添加權限
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
獲取WifiP2pManager和WifiP2pManager.Channel對象
mWifiP2pManager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
mWifiP2pManager?.initialize(this, Looper.getMainLooper()) {
Log.d(TAG, "Channel斷開連接")
}
服務端創建群組
//服務端創建群組
mWifiP2pManager?.createGroup(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "創建群組成功")
}
override fun onFailure(reason: Int) {
Log.w(TAG, "創建群組失敗$reason")
}
})
客戶端搜索對等設備
//客戶端搜索對等設備
mWifiP2pManager?.discoverPeers(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "搜索成功")
}
override fun onFailure(reason: Int) {
Log.d(TAG, "搜索失敗:$reason")
}
})
//使用異步方法(推薦通過廣播監聽) 獲取設備列表
mWifiP2pManager?.requestPeers(mChannel) {
mDeviceList.addAll(it.deviceList)
if (mDeviceList.isEmpty()) {
//沒有設備
runOnUiThread { Toast.makeText(this, "沒有發現設備", Toast.LENGTH_SHORT).show() }
} else {
//刷新列表
runOnUiThread { mDeviceAdapter.notifyDataSetChanged() }
}
}
連接設備
val config = WifiP2pConfig().apply {
this.deviceAddress = wifiP2pDevice.deviceAddress
this.wps.setup = WpsInfo.PBC
}
mWifiP2pManager?.connect(mChannel, config, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "連接成功")
}
override fun onFailure(reason: Int) {
Log.w(TAG, "連接失敗$reason")
}
})
服務端創建Socket進行數據讀寫
// 將數據發送給客戶端
//需要創建子線程 否則在主線程網絡操作直接閃退
val serverSocket = ServerSocket(8888)
val socket = serverSocket.accept()
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發送數據
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數據$text")
}
客戶端創建Socket進行數據讀寫
//需要創建子線程 否則在主線程網絡操作直接閃退
val address: InetAddress = info.groupOwnerAddress
val socket = Socket(address, 8888)
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發送數據
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數據$text")
}
class MainActivity : AppCompatActivity() {
private val TAG = MainActivity::class.java.simpleName
private lateinit var mBinding: ActivityMainBinding
private var mWifiP2pManager: WifiP2pManager? = null
private var mChannel: WifiP2pManager.Channel? = null
private var mDeviceList = arrayListOf<WifiP2pDevice>()
private lateinit var mDeviceAdapter: DeviceAdapter
private var mQuitReadData = true
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
ViewCompat.setOnApplyWindowInsetsListener(mBinding.main) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val intentFilter = IntentFilter()
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
registerReceiver(mReceiver, intentFilter)
mDeviceAdapter = DeviceAdapter(mDeviceList)
mBinding.rvDeviceList.adapter = mDeviceAdapter
mDeviceAdapter.mOnItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val wifiP2pDevice = mDeviceList[position]
connect(wifiP2pDevice)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
//通用步驟
mWifiP2pManager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
mChannel = mWifiP2pManager?.initialize(this, Looper.getMainLooper()) {
Log.d(TAG, "Channel斷開連接")
}
//服務端部分
//服務端創建群組
mWifiP2pManager?.createGroup(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "創建群組成功")
}
override fun onFailure(reason: Int) {
Log.w(TAG, "創建群組失敗$reason")
}
})
//客戶端部分
//客戶端搜索對等設備
mWifiP2pManager?.discoverPeers(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "搜索成功")
}
override fun onFailure(reason: Int) {
Log.d(TAG, "搜索失敗:$reason")
}
})
//使用異步方法(推薦通過廣播監聽) 獲取設備列表
mWifiP2pManager?.requestPeers(mChannel) {
mDeviceList.addAll(it.deviceList)
if (mDeviceList.isEmpty()) {
//沒有設備
runOnUiThread { Toast.makeText(this, "沒有發現設備", Toast.LENGTH_SHORT).show() }
} else {
//刷新列表
runOnUiThread { mDeviceAdapter.notifyDataSetChanged() }
}
}
}
/**
* 連接設備
*/
private fun connect(wifiP2pDevice: WifiP2pDevice) {
val config = WifiP2pConfig().apply {
this.deviceAddress = wifiP2pDevice.deviceAddress
this.wps.setup = WpsInfo.PBC
}
mWifiP2pManager?.connect(mChannel, config, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "連接成功")
mQuitReadData = false
transferData("Hello".toByteArray())
}
override fun onFailure(reason: Int) {
Log.w(TAG, "連接失敗$reason")
mQuitReadData = true
}
})
}
private fun transferData(data: ByteArray) {
//請求設備連接信息
mWifiP2pManager?.requestConnectionInfo(mChannel) { info ->
if (info.groupFormed && info.isGroupOwner) {
// 將數據發送給客戶端
val serverSocket = ServerSocket(8888)
val socket = serverSocket.accept()
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發送數據
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數據$text")
}
} else {
//設備是客戶端
val address: InetAddress = info.groupOwnerAddress
val socket = Socket(address, 8888)
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發送數據
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數據$text")
}
}
}
}
private val mReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action;
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// Check to see if Wi-Fi is enabled and notify appropriate activity
// 檢查 Wi-Fi P2P 是否已啟用
val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
val isEnabled = (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED)
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Call WifiP2pManager.requestPeers() to get a list of current peers
//異步方法
// mWifiP2pManager?.requestPeers();
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// Respond to new connection or disconnections
// 鏈接狀態變化回調
// 此廣播 會和 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 同時回調
// 注冊廣播、連接成功、連接失敗 三種時機都會調用
// 應用可使用 requestConnectionInfo()、requestNetworkInfo() 或 requestGroupInfo() 來檢索當前連接信息。
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
// Respond to this device's wifi state changing
// 此設備的WiFi狀態更改回調
// 此廣播 會和 WIFI_P2P_CONNECTION_CHANGED_ACTION 同時回調
// 注冊廣播、連接成功、連接失敗 三種時機都會調用
// 應用可使用 requestDeviceInfo() 來檢索當前連接信息。
}
}
}
override fun onDestroy() {
super.onDestroy()
//移除群組
mWifiP2pManager?.removeGroup(mChannel, null)
//取消鏈接
mWifiP2pManager?.cancelConnect(mChannel, null)
}
}
Android WiFi P2P使用流程總結:
- 「權限聲明」:
在AndroidManifest.xml中聲明必要的權限,包括網絡訪問權限和文件讀寫權限。
- 「初始化」:
在Android應用中,首先需要獲取WifiP2pManager實例,并通過調用其initialize方法進行初始化。這將注冊應用并準備使用Wi-Fi P2P功能。
初始化完成后,會獲得一個Channel對象,它是后續操作的關鍵。
3.「廣播接收與處理」:
在整個過程中,應用需要注冊并監聽特定的廣播,以處理Wi-Fi P2P狀態變化、設備發現、連接變化等事件。
這些廣播會通知應用有關Wi-Fi P2P操作的狀態和結果,以便應用可以做出相應的響應。
4.「設備發現」:
使用WifiP2pManager的discoverPeers方法開始搜索附近的Wi-Fi P2P設備。
設備會在特定的頻段(如2.4GHz的1、6、11頻段)上發送Probe Request幀來尋找其他設備。
搜索到的設備會作為列表展示在應用界面上,用戶可以從中選擇想要連接的設備。
5.「建立連接」:
選定一個設備后,作為客戶端或服務端(Group Owner,GO)發起連接請求。
通過WifiP2pConfig對象配置連接參數,如目標設備的地址和WPS(Wi-Fi Protected Setup)設置。
使用WifiP2pManager的connect方法嘗試建立連接。
6.「連接確認與數據傳輸」:
一旦連接建立成功,設備之間就可以開始數據傳輸了。
可以通過Socket編程在設備之間建立連接,并傳輸文件或其他數據。
根據應用需求,可以創建服務端套接字監聽客戶端的連接請求,也可以作為客戶端主動連接到服務端。
7.「數據傳輸完成與斷開連接」:
數據傳輸完成后,應用需要適當地關閉套接字和斷開Wi-Fi P2P連接。
使用WifiP2pManager的相關方法來斷開連接,并釋放相關資源。