走進Android WiFi P2P技術(shù),一探設(shè)備間點對點通信實現(xiàn)細(xì)節(jié)
WiFi P2P技術(shù)
WiFi P2P(Peer-to-Peer),也被稱為WiFi Direct,是WiFi聯(lián)盟發(fā)布的一個協(xié)議。允許無線網(wǎng)絡(luò)中的設(shè)備在無需無線路由器的情況下相互連接,通過WiFi直接實現(xiàn)兩臺設(shè)備之間的無線點對點通信。原理與基于AP(接入點)的通信方式類似,支持P2P的設(shè)備可以在同一個小組內(nèi)互傳數(shù)據(jù),實現(xiàn)同屏功能。
WiFi P2P被廣泛應(yīng)用于移動設(shè)備之間的文件共享、游戲聯(lián)機、音樂播放等應(yīng)用場景中。相較于藍(lán)牙,WiFi P2P具有更快的搜索速度和傳輸速度,以及更遠(yuǎn)的傳輸距離。而且只需要打開WiFi即可,不需要加入任何網(wǎng)絡(luò)或AP,即可實現(xiàn)對等點連接通訊。對于需要在用戶之間共享數(shù)據(jù)的應(yīng)用,如多人游戲或照片共享非常有用。
WiFi P2P也存在一些安全性問題,如用戶隱私泄露、惡意軟件和病毒傳播,以及侵權(quán)和違法內(nèi)容的傳播。為了保護用戶的安全和隱私,一些P2P網(wǎng)絡(luò)提供了匿名化處理功能,使用安全搜索引擎,以及設(shè)置過濾器來阻止違法和侵權(quán)內(nèi)容的共享。
Android WiFi P2P架構(gòu)
在P2P架構(gòu)中,定義了兩種主要角色:P2P Group Owner(簡稱GO)和P2P Client(簡稱GC)。GO的作用類似于Infrastructure BSS中的AP(接入點),而GC的作用類似于Infrastructure BSS中的STA(站點)。當(dāng)兩臺設(shè)備通過P2P連接后,會隨機(也可以手動指定)指派其中一臺設(shè)備為組擁有者(GO),相當(dāng)于一臺服務(wù)器,另一臺設(shè)備為組成員(GC)。其他設(shè)備可以通過與GO設(shè)備連接加入組,但不能直接和GC設(shè)備連接。
圖片
在Android系統(tǒng)中,WiFi P2P功能是在Android 4.0及更高版本系統(tǒng)中加入的。它可以通過WifiP2pManager類進行實現(xiàn),這個類提供了許多方法來掃描可用設(shè)備、建立P2P連接并傳輸數(shù)據(jù)等功能。開發(fā)者可以通過這些方法來實現(xiàn)設(shè)備之間的文件傳輸?shù)炔僮鳌?/p>
在設(shè)備發(fā)現(xiàn)階段,Android WiFi P2P使用Probe Request和Probe Response幀來交換設(shè)備信息。在2.4GHz的1、6、11頻段上發(fā)送Probe Request幀,這幾個頻段被稱為Social Channels。一旦Listen Channel選擇好后,在整個P2P Discovery階段就不能更改,用于快速發(fā)現(xiàn)周圍的Group。
盡管Android WiFi P2P功能強大,目前在Android系統(tǒng)中只是內(nèi)置了設(shè)備的搜索和鏈接功能,并沒有像藍(lán)牙那樣有許多應(yīng)用。在實際開發(fā)中,可能需要通過軟件手段解決一些邏輯和權(quán)限問題。
Android應(yīng)用WiFi P2P實現(xiàn)數(shù)據(jù)傳輸
在Android中,WiFi P2P可以通過WifiP2pManager類進行實現(xiàn)。開發(fā)者可以通過獲取WifiP2pManager實例,并進行廣播接受者的創(chuàng)建和注冊,調(diào)用其他WiFi P2P的API,實現(xiàn)設(shè)備間的搜索、連接和數(shù)據(jù)傳輸?shù)裙δ?。例如,指定某一臺設(shè)備為服務(wù)器,創(chuàng)建群組并等待客戶端的連接請求,而客戶端則可以主動搜索附近的設(shè)備并加入群組,向服務(wù)器發(fā)起文件傳輸請求。
圖片
添加權(quán)限
<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斷開連接")
}
服務(wù)端創(chuàng)建群組
//服務(wù)端創(chuàng)建群組
mWifiP2pManager?.createGroup(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "創(chuàng)建群組成功")
}
override fun onFailure(reason: Int) {
Log.w(TAG, "創(chuàng)建群組失敗$reason")
}
})
客戶端搜索對等設(shè)備
//客戶端搜索對等設(shè)備
mWifiP2pManager?.discoverPeers(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "搜索成功")
}
override fun onFailure(reason: Int) {
Log.d(TAG, "搜索失敗:$reason")
}
})
//使用異步方法(推薦通過廣播監(jiān)聽) 獲取設(shè)備列表
mWifiP2pManager?.requestPeers(mChannel) {
mDeviceList.addAll(it.deviceList)
if (mDeviceList.isEmpty()) {
//沒有設(shè)備
runOnUiThread { Toast.makeText(this, "沒有發(fā)現(xiàn)設(shè)備", Toast.LENGTH_SHORT).show() }
} else {
//刷新列表
runOnUiThread { mDeviceAdapter.notifyDataSetChanged() }
}
}
連接設(shè)備
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")
}
})
服務(wù)端創(chuàng)建Socket進行數(shù)據(jù)讀寫
// 將數(shù)據(jù)發(fā)送給客戶端
//需要創(chuàng)建子線程 否則在主線程網(wǎng)絡(luò)操作直接閃退
val serverSocket = ServerSocket(8888)
val socket = serverSocket.accept()
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發(fā)送數(shù)據(jù)
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數(shù)據(jù)$text")
}
客戶端創(chuàng)建Socket進行數(shù)據(jù)讀寫
//需要創(chuàng)建子線程 否則在主線程網(wǎng)絡(luò)操作直接閃退
val address: InetAddress = info.groupOwnerAddress
val socket = Socket(address, 8888)
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發(fā)送數(shù)據(jù)
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數(shù)據(jù)$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斷開連接")
}
//服務(wù)端部分
//服務(wù)端創(chuàng)建群組
mWifiP2pManager?.createGroup(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "創(chuàng)建群組成功")
}
override fun onFailure(reason: Int) {
Log.w(TAG, "創(chuàng)建群組失敗$reason")
}
})
//客戶端部分
//客戶端搜索對等設(shè)備
mWifiP2pManager?.discoverPeers(mChannel, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
Log.d(TAG, "搜索成功")
}
override fun onFailure(reason: Int) {
Log.d(TAG, "搜索失敗:$reason")
}
})
//使用異步方法(推薦通過廣播監(jiān)聽) 獲取設(shè)備列表
mWifiP2pManager?.requestPeers(mChannel) {
mDeviceList.addAll(it.deviceList)
if (mDeviceList.isEmpty()) {
//沒有設(shè)備
runOnUiThread { Toast.makeText(this, "沒有發(fā)現(xiàn)設(shè)備", Toast.LENGTH_SHORT).show() }
} else {
//刷新列表
runOnUiThread { mDeviceAdapter.notifyDataSetChanged() }
}
}
}
/**
* 連接設(shè)備
*/
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) {
//請求設(shè)備連接信息
mWifiP2pManager?.requestConnectionInfo(mChannel) { info ->
if (info.groupFormed && info.isGroupOwner) {
// 將數(shù)據(jù)發(fā)送給客戶端
val serverSocket = ServerSocket(8888)
val socket = serverSocket.accept()
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發(fā)送數(shù)據(jù)
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數(shù)據(jù)$text")
}
} else {
//設(shè)備是客戶端
val address: InetAddress = info.groupOwnerAddress
val socket = Socket(address, 8888)
val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream()
//發(fā)送數(shù)據(jù)
outputStream?.write(data)
//此處為了方便 實際需要開啟線程讀取 并且要有合適的延遲
while (!mQuitReadData) {
val reader = inputStream.bufferedReader(StandardCharsets.UTF_8)
val text = reader.readLine()
Log.d(TAG, "讀取到的數(shù)據(jù)$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
// 鏈接狀態(tài)變化回調(diào)
// 此廣播 會和 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 同時回調(diào)
// 注冊廣播、連接成功、連接失敗 三種時機都會調(diào)用
// 應(yīng)用可使用 requestConnectionInfo()、requestNetworkInfo() 或 requestGroupInfo() 來檢索當(dāng)前連接信息。
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
// Respond to this device's wifi state changing
// 此設(shè)備的WiFi狀態(tài)更改回調(diào)
// 此廣播 會和 WIFI_P2P_CONNECTION_CHANGED_ACTION 同時回調(diào)
// 注冊廣播、連接成功、連接失敗 三種時機都會調(diào)用
// 應(yīng)用可使用 requestDeviceInfo() 來檢索當(dāng)前連接信息。
}
}
}
override fun onDestroy() {
super.onDestroy()
//移除群組
mWifiP2pManager?.removeGroup(mChannel, null)
//取消鏈接
mWifiP2pManager?.cancelConnect(mChannel, null)
}
}
Android WiFi P2P使用流程總結(jié):
- 「權(quán)限聲明」:
在AndroidManifest.xml中聲明必要的權(quán)限,包括網(wǎng)絡(luò)訪問權(quán)限和文件讀寫權(quán)限。
- 「初始化」:
在Android應(yīng)用中,首先需要獲取WifiP2pManager實例,并通過調(diào)用其initialize方法進行初始化。這將注冊應(yīng)用并準(zhǔn)備使用Wi-Fi P2P功能。
初始化完成后,會獲得一個Channel對象,它是后續(xù)操作的關(guān)鍵。
3.「廣播接收與處理」:
在整個過程中,應(yīng)用需要注冊并監(jiān)聽特定的廣播,以處理Wi-Fi P2P狀態(tài)變化、設(shè)備發(fā)現(xiàn)、連接變化等事件。
這些廣播會通知應(yīng)用有關(guān)Wi-Fi P2P操作的狀態(tài)和結(jié)果,以便應(yīng)用可以做出相應(yīng)的響應(yīng)。
4.「設(shè)備發(fā)現(xiàn)」:
使用WifiP2pManager的discoverPeers方法開始搜索附近的Wi-Fi P2P設(shè)備。
設(shè)備會在特定的頻段(如2.4GHz的1、6、11頻段)上發(fā)送Probe Request幀來尋找其他設(shè)備。
搜索到的設(shè)備會作為列表展示在應(yīng)用界面上,用戶可以從中選擇想要連接的設(shè)備。
5.「建立連接」:
選定一個設(shè)備后,作為客戶端或服務(wù)端(Group Owner,GO)發(fā)起連接請求。
通過WifiP2pConfig對象配置連接參數(shù),如目標(biāo)設(shè)備的地址和WPS(Wi-Fi Protected Setup)設(shè)置。
使用WifiP2pManager的connect方法嘗試建立連接。
6.「連接確認(rèn)與數(shù)據(jù)傳輸」:
一旦連接建立成功,設(shè)備之間就可以開始數(shù)據(jù)傳輸了。
可以通過Socket編程在設(shè)備之間建立連接,并傳輸文件或其他數(shù)據(jù)。
根據(jù)應(yīng)用需求,可以創(chuàng)建服務(wù)端套接字監(jiān)聽客戶端的連接請求,也可以作為客戶端主動連接到服務(wù)端。
7.「數(shù)據(jù)傳輸完成與斷開連接」:
數(shù)據(jù)傳輸完成后,應(yīng)用需要適當(dāng)?shù)仃P(guān)閉套接字和斷開Wi-Fi P2P連接。
使用WifiP2pManager的相關(guān)方法來斷開連接,并釋放相關(guān)資源。