Android 中运用 WI-FI 直连/P2P 完成高速数据传输

原作者:Tans5

一个大文件极速传输使用,支撑android端,以及mac,windows。大大解决了android手机和mac之间传输文件的繁琐问题。

0 简略介绍

就算做过好几年的 Android 开发老油条,有很多人都不知道 WI-FI 直连。简略来说便是能够将两台支撑 WI-FI 直连设备树立一条高速的通讯衔接,衔接树立后咱们就能够经过 TCP/UDP 协议来传输咱们想要的数据。相对于蓝牙的数据传输,WI-FI 直连的传输速度、距离、延迟和稳定性都大幅度优于蓝牙;相对于一般的局域网内的通讯速度和稳定性也是有优势,只是在某些比较好的局域网内才干够与 WI-FI 直连 比美。

WI-FI 直连一般用在需求传递很多数据的场景,比方大文件的共享、无人机图传等等场景。Android 从 4.0 版别就已经支撑 WI-FI 直连,基本便是有 WI-FI 的 Android 设备都支撑。条件要求特别低只需求两台手机翻开 WI-FI 就能创建衔接。(不需求手机接入 WLAN 哦)

给一个我自己开源的基于 WI-FI 直连的文件传输使用截图:

感兴趣的能够看看源码完成:tFileTransfer WI-FI 直连相关代码, 其间代码我用协程封装了一下。

Tips: 需求到达上面的传输速度,还得经过多线程创建多条衔接传输数据,才干到达网络带宽的上限。

1 权限增加

1.1 一般权限

// ...
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
// ...

1.2 灵敏权限

灵敏权限在 Android 6 今后需在运行时动态再恳求一次,动态恳求权限的代码就不再贴了。

在 Android 13 及其今后的版别需求 NEARBY_WIFI_DEVICES 权限:

// ...
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="tiramisu" />
// ...

在 Android 12 及其以前的版别需求 ACCESS_FINE_LOCATION 权限:

// ...
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="32"/>
// ...

1.3 注意

在 Android 上要让 WI-FI 直连正常工作,一定得翻开 WI-FI 和 GPS 开关

2 构建 WI-FI 直连衔接

2.1 预备工作

获取 WifiP2pManager 和 初始化 WifiP2pChannel:

val wifiP2pManager = context?.getSystemService(Context.WIFI_P2P_SERVICE) as? WifiP2pManager // 获取 WiFiP2pManager
val wifiChannel = wifiP2pManager?.initialize(requireActivity(), requireActivity().mainLooper, null) // 初始化 WifiChannel

动态注册 BroadcastReceiver 来监听后续操作中 WI-FI 直连中需求用到的各种状况:


 val intentFilter = IntentFilter().apply {
     addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) // Wifi 直连可用状况改动
     addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) // Wifi 直连发现的设备改动
     addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) // Wifi 直连的衔接状况改动
 }
 requireActivity().registerReceiver(wifiReceiver, intentFilter)
    private val wifiReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            when (intent?.action) {
                WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
                    val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
                    if (state == WifiP2pManager.WIFI_P2P_STATE_DISABLED) {
                        AndroidLog.e(TAG, "Wifi p2p disabled.")
                    } else {
                        AndroidLog.d(TAG, "Wifi p2p enabled.")
                    }
                }
                WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
                    val wifiDevicesList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                        intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, WifiP2pDeviceList::class.java)
                    } else {
                        intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST)
                    }
                    AndroidLog.d(TAG, "WIFI p2p devices: ${wifiDevicesList?.deviceList?.joinToString { "${it.deviceName} -> ${it.deviceAddress}" }}")
                }
                WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
                    AndroidLog.d(TAG, "Connection state change.")
                }
            }
        }
    }

其间 WIFI_P2P_STATE_CHANGED_ACTION 表明 WI-FI 直连能够状况改动,比方说这时开关 WI-FI。
WIFI_P2P_PEERS_CHANGED_ACTION 表明发现的设备产生改动,后续会再讲到。
WIFI_P2P_CONNECTION_CHANGED_ACTION 表明衔接状况改动,后续会再讲到。

2.2 查询邻近设备

经过前面预备工作初始化的 WifiManagerWifiChannel 实例恳求查询邻近设备,一般能够经过用户触发查询,也能够敞开循环任务定时查询,取决于自己的需求。


 wifiP2pManager.discoverPeers(wifiChannel, object : ActionListener {
     override fun onSuccess() {
         TODO("Not yet implemented")
     }
     override fun onFailure(p0: Int) {
         TODO("Not yet implemented")
     }
 })

如果查询成功会触发能够触发预备工作中注册的播送监听,经过以下办法能够拿到对应的设备信息。

// ...
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
    val wifiDevicesList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, WifiP2pDeviceList::class.java)
    } else {
        intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST)
    }
    // ...
}
// ...

查询设备成功后能够依据返回的成果,经过以下办法获取发现的设备的姓名和硬件地址。


for (d in wifiDevicesList?.deviceList ?: emptyList()) {
    val deviceName = d.deviceName
    val macAddress = d.deviceAddress
}

Tips: 从 Android 10 开端,Google 渐渐禁掉了能够定位用户设备的办法,类似于这儿的硬件地址,获取到的地址是假的,不过能够获取到其他设备的硬件地址。

查询成功后也能够手动触发查询设备。


wifiP2pManager.requestPeers(wifiChannel, object : PeerListListener {
    override fun onPeersAvailable(p0: WifiP2pDeviceList?) {
        TODO("Not yet implemented")
    }
})

2.3 恳求与方针设备衔接


val config = WifiP2pConfig()
config.deviceAddress = data.macAddress
wifiP2pManager.connect(wifiChannel, config, object : ActionListener {
    override fun onSuccess() {
        TODO("Not yet implemented")
    }
    override fun onFailure(p0: Int) {
        TODO("Not yet implemented")
    }
})

其间的 MAC 地址,是在发现阶段获取到的。

相同的查询成功后也会触发上面动态注册的播送接收器。


// ....
 WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
     // ....
 }
 // ...

经过以下办法查询衔接信息。


 wifiP2pManager.requestConnectionInfo(wifiChannel) {
     val ownerIpAddress = it.groupOwnerAddress
     val isOwner = it.isGroupOwner
 }

这个时分衔接就能够运用了,如果当时设备是 owner 那么,owner ip address 便是当时设置的 IP 地址,一般这时 owner 就能够敞开一个 TCP / UDP 服务,非 owner 的设备就能够衔接这个敞开的服务,这样一个网络衔接就树立完成了。能够传输你需求的数据了。

2.4 收回资源

如果不再运用,需求收回。

断开衔接,如果需求重新衔接其他设备,也能够调用这个办法把当时的衔接关闭。

wifiP2pManager.cancelConnect(wifiChannel, null)
wifiP2pManager.removeGroup(wifiChannel, null)

撤销注册播送。

 requireActivity().unregisterReceiver(wifiReceiver)