简介:Android官方Wifi P2P Demo用于帮助开发者理解并实现Wi-Fi Direct(Wi-Fi Peer-to-Peer)技术,适用于设备间直连、高速数据传输或流媒体共享等场景。压缩包共88个文件,包含7个Java源文件、14个XML资源文件、26个class文件及APK、JAR等编译产物,整体仅244KB,结构紧凑,便于对照学习。已有781人浏览学习。资源不只是一份示例代码,还附有详细的功能拆解,涵盖设备发现、群组创建与管理、连接建立、文件传输和状态更新等核心机制,并分析了WifiP2pManager等关键API的使用方法。通过研读源码与配套说明,开发者可以快速掌握Wi-Fi P2P的开发要点与注意事项,为文件共享、游戏联机、智能家居控制等应用场景打下基础。 不知道你有没有遇到过这种场景:两台手机之间要传一个几百MB的视频,蓝牙慢到能去泡杯咖啡,走社交软件又得绕服务器转一圈,而周围偏偏没有路由器也没有蜂窝信号。其实Android官方早就准备了一套方案——Wi-Fi P2P(也就是常说的Wi-Fi Direct),而最直观的入门教材,就是官方那个Wifi P2P Demo。这篇文章我会从这个官方Demo出发,结合我实际跑通、踩坑、再改造它的完整经历,把设备发现、连接建立、Socket传数据这条链路讲透,也顺带说说那些文档里没写、但真机会逼疯你的细节。
如果你是刚接触Android网络开发、或者准备做文件互传、离线局域网通信这类功能的开发者,这篇内容可以直接给你省掉至少一周的摸索时间。
1. 为什么非要用Wi-Fi P2P:没路由器也能组“局域网”的场景
1.1 什么是Wi-Fi P2P,和热点方式有什么本质区别
Wi-Fi P2P是Android从4.0(API 14)开始提供的原生点对点通信能力,它允许两台设备在没有无线接入点(AP)的情况下,通过Wi-Fi网卡直接建立连接。很多人第一反应是“这不就是开热点吗”,其实两者有本质区别。
开热点的方式是:一台设备变成软AP,另一台设备像连普通Wi-Fi一样去连接它。这种方式的缺点是明显的,热点设备要承担完整的AP功能,网卡工作模式切换慢、功耗高,而且对端需要手动输密码,连接体验非常糟糕。Wi-Fi P2P则是由系统底层协商出一个“组”(P2P Group),组里自动选出一台设备当Group Owner(简称GO),另一台当Client,整个过程对用户几乎透明,不需要输密码,也不需要手动切热点。
我当时之所以翻出官方Demo来研究,就是因为要在两个没有网络的环境下做设备间文件同步。最开始用的就是手机热点方案,结果被两个问题折腾得够呛,一个是华为手机开热点后自动关闭Wi-Fi导致无法同时上网,另一个是iOS设备连热点弹窗确认太慢。换成Wi-Fi P2P之后,这些问题基本都绕开了。
1.2 P2P、蓝牙、普通热点的横向对比
为了让你在选型时心里有数,我把我实测过的三种方案放在一起做了个对比:
| 项目 | Wi-Fi P2P | 蓝牙 | 手机热点 + Socket |
|---|---|---|---|
| 不需要路由器 | 支持 | 支持 | 支持 |
| 传输速度 | 实际可到10~50MB/s | 2~3MB/s封顶 | 取决于软AP性能 |
| 是否需要输密码 | 不需要 | 需要配对确认 | 需要输密码 |
| 功耗 | 中等 | 低 | 较高 |
| 连接建立时间 | 1~3秒 | 5秒以上 | 15秒以上 |
| 底层机制 | 系统协商GO/Client | 主从配对 | 软AP + DHCP |
从表格能看出来,P2P在速度、连接体验、功耗这三个维度上取得了相对均衡的优势。它尤其适合“短距离、大文件、无网络环境”的场景,比如会议室的临时文件分发、户外设备数据采集、两台执法记录仪之间的文件同步。
2. 跑通官方Demo前,先把这些前置条件准备好
2.1 硬件与工具准备
官方Demo的正式名称是WiFiDirectDemo,在Android开发者网站和GitHub的Android示例仓库里都能找到。下载之后用Android Studio直接打开就行,Java版本和Gradle版本按仓库说明配置即可。
硬件方面,别的都能凑合,唯独“两台真机”不能凑合。我见过太多人用模拟器跑这个Demo,跑起来之后设备列表永远是空的,因为模拟器的Wi-Fi网卡是虚拟的,根本不支持P2P发现。我自己最早也犯过这个错,浪费了半天时间。
两台真机最好是不同品牌,原因后面会讲,这里先留个悬念。系统版本建议一台Android 8~9、一台Android 10以上的组合,这样能同时覆盖新旧权限模型。
2.2 权限配置:三个容易被忽略的关键点
打开Demo的AndroidManifest.xml,可以看到它声明了这么几个权限:
<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.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />其中前四个属于普通权限,直接声明就行。但ACCESS_FINE_LOCATION是运行时权限,Android 6.0之后必须在代码里动态申请,只写manifest是不够的。这个权限的作用后来被很多人误解过,它并不是因为P2P功能本身需要定位,而是因为Wi-Fi扫描结果可能暴露设备的地理位置,所以系统强制要求先拿定位权限。
到了Android 13(API 33),Google又引入了NEARBY_WIFI_DEVICES这个运行时权限,专门用于管理附近Wi-Fi设备的连接。如果你的targetSdk已经到33以上,除了定位权限之外,还得把这个权限也加上并动态申请。
2.3 怎么判断Demo是否真的跑通了
很多新手把Demo装上之后,看到两台手机能互相发现就以为成功了,其实那只是第一步。我建议按这个清单逐步验证:
- 两台设备打开Wi-Fi,在系统设置中找到“Wi-Fi直连”入口,确认系统底层能互相发现。
- 进入App,点击搜索设备,列表出现对端设备的名称和MAC地址。
- 点击列表中的设备发起连接,对端收到邀请弹窗并接受。
- 连接成功后,两台设备都能看到对方的IP地址和“Group Owner”标识。
- 在Demo的测试界面发送一条消息,对端能收到。
只有走到第5步,才算完整跑通了官方Demo。
3. 官方Demo的核心链路拆解:发现、连接、传数据
3.1 五个广播Action是理解Demo的入口
整个官方Demo的骨架,是围绕WifiP2pManager和一组系统广播搭建起来的。WifiP2pManager是系统提供的P2P控制入口,所有操作比如搜索设备、发起连接、获取连接信息,都是通过这个类的异步方法触发的。
但这套API有个特点,它的方法调用结果和异步事件,很多不是通过回调直接返回的,而是通过广播通知上层。官方Demo里的WiFiDirectBroadcastReceiver就扮演了这个中间人角色,它监听以下几个关键Action:
| Action常量 | 含义 | Demo中的处理 |
|---|---|---|
WIFI_P2P_STATE_CHANGED_ACTION | P2P功能开关状态变化 | 关闭时提示用户 |
WIFI_P2P_PEERS_CHANGED_ACTION | 附近设备列表变化 | 调用requestPeers刷新列表 |
WIFI_P2P_CONNECTION_CHANGED_ACTION | 连接状态变化 | 获取连接信息并更新UI |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION | 本机设备信息变化 | 更新本机名称和状态 |
理解这五个广播,就等于拿到了读Demo代码的地图。WiFiDirectActivity负责把接收器注册到系统、把UI组件和WifiP2pManager串起来,DeviceListFragment负责显示设备列表,DeviceDetailFragment负责显示连接详情和发起传输操作,各司其职。
3.2 连接建立的完整时序:谁发起、谁当组长
把Demo跑起来之后,你可能会好奇一个问题:当两台手机建立P2P连接时,系统到底做了什么?我用自己的话把这条链路梳理一下:
- 发起方调用
discoverPeers(),系统开始搜索周围的P2P设备。这个过程并不是持续广播,而是一次有限时长的扫描。 - 搜索到设备后,系统发出
WIFI_P2P_PEERS_CHANGED_ACTION广播,App通过requestPeers()拿到设备列表。 - 用户点击某个设备,App调用
connect()传入WifiP2pConfig,系统开始协商。 - 协商的关键点在于:系统会根据信号强度、设备能力、电池状态等因素,自动决定谁是GO。GO相当于一个小型软AP,Client则像终端一样连接它。
- 连接成功后,系统发出
WIFI_P2P_CONNECTION_CHANGED_ACTION广播,App通过requestConnectionInfo()拿到WifiP2pInfo,里面有GO的IP地址和当前角色。
这里有一个概念容易混淆,GO和Client并不意味着“发起方一定是GO”。有时候发起连接的一方反而被协商成Client,所以在写代码时千万别写死“我是发起方所以我是GO”这种逻辑,一切以WifiP2pInfo.isGroupOwner字段为准。
3.3 传输通道不是自动的:ServerSocket端口协商
这是整个Demo里最反直觉的地方。P2P连接只是打通了网络链路,但这台设备上的某个App想和对端App通信,还需要自己建立Socket连接。换句话说,Wi-Fi P2P管的是“路”,不管“车上拉什么货”。
官方Demo的处理方式很经典:连接成功之后,GO角色的一方会在本机启动一个ServerSocket,监听一个固定端口(经典版本默认是8888),然后Client通过GO的IP地址和这个端口发起连接。GOaccept()到这个连接之后,并没有直接复用这条通道传数据,而是做了一次“二次建连”——再创建一个Socket,主动连回Client在8888端口监听的ServerSocket。
为什么设计这么绕?我个人的理解是,Demo的传输逻辑里收发两个方向需要独立的InputStream和OutputStream,如果只建一条连接,收发可能相互阻塞,代码写起来也别扭。两条独立连接之后,每台设备各自持有一条Socket作为发送通道,一条作为接收通道,逻辑上清爽很多。
理解了这一点,你再看DeviceDetailFragment里的代码,会发现它的数据发送其实只需要很短的代码量,核心逻辑就是拿到WifiP2pInfo之后判断角色、开ServerSocket、然后Socket读写。
4. 真机实测才暴露的四个坑,附完整排查思路
4.1 权限不足不是“弹窗拒绝”,而是静默失败
这个坑我印象太深了。有一次我在一台Android 10的设备上跑Demo,点击搜索设备后,设备列表一直空白,但没有任何报错。日志里只出现了权限拒绝的警告,不仔细看根本发现不了。
排查链路是这样的:我先是确认了两台设备都在同一个Wi-Fi环境里,排除了网络问题;然后又去系统设置里手动打开Wi-Fi直连,发现系统层面可以互相发现,这就把问题定位到了App层面。最后我无意中瞥见Logcat里有一条SecurityException,才想起来ACCESS_FINE_LOCATION是运行时权限,Demo的旧代码没有处理Android 10之后的动态申请逻辑。
解决方式很朴素:在onCreate里调用requestPermissions()动态申请定位权限,Android 13以上同时申请NEARBY_WIFI_DEVICES。这里提醒一句,权限授予后,如果之前已经调用过discoverPeers(),一定要重新调用一次扫描,否则权限白给了。
4.2 广播接收器的生命周期绑错了,收不到任何事件
官方Demo的WiFiDirectBroadcastReceiver是在onCreate里注册的,但如果你照葫芦画瓢写到自己项目里,很可能遇到一个问题:App退到后台再回来,或者跳转了其他页面,广播再也收不到了。
更合理的做法是把注册和解绑放在onResume和onPause里。这不是Demo代码本身的问题,而是Activity生命周期和广播注册时机的经典矛盾。官方Demo因为只有一个Activity、没有复杂跳转,所以onCreate注册问题不大;但真实项目里页面一多,你不放在onResume里,就会出现“点击了搜索但列表死活不刷新”的诡异现象。
这个坑排查起来也比较有迷惑性,因为问题不在P2P本身,而在Activity生命周期管理。
4.3 连接成功但Socket连不上:端口和ROM的双重陷阱
连接状态已经变成“Connected”了,两台设备的WifiP2pInfo也都拿到了,但Socket就是连不上。这个坑我前前后后踩了两天,最后发现是由两个叠加因素导致的。
第一个因素是端口。Demo默认用8888端口,但这个端口在不少设备的系统服务里已经被占用了。我在某台设备上测试时,ServerSocket绑定8888直接抛了BindException,而Demo的代码没做失败提示,所以表现就是“看起来一切正常但传不了数据”。这个必须改成高位随机端口,或者至少绑定前先检查端口可用性。
第二个因素更隐蔽,部分国内ROM(我遇到的尤其是某些华为和荣耀机型)对后台Socket连接有严格限制,特别是热点、P2P相关的传输容易被系统优化策略杀掉。表现是:App在前台时传输正常,锁屏或者切后台之后再传,连接立刻断开。解决办法是加前台服务保活,并且提示用户关闭对该应用的电池优化。
4.4 设备互相发现不了的排查清单
两台设备打开Demo之后,列表空空如也,这个问题的原因可能很多,我整理了一份排查顺序,每次遇到都按这个顺序走,能省下不少时间:
| 序号 | 检查项 | 操作建议 |
|---|---|---|
| 1 | 两台设备的Wi-Fi是否开启 | 移动数据开着不代表Wi-Fi开着 |
| 2 | 定位权限是否已授予 | 重新调用一次discoverPeers |
| 3 | 系统自带Wi-Fi直连能否发现 | 能则说明P2P底层正常,问题在App |
| 4 | 是否处于同一普通AP覆盖范围 | 复杂网络环境下发现会受影响 |
| 5 | 重启其中一台的Wi-Fi | 清除P2P状态机的残留状态 |
| 6 | 是否有一台设备已经连接了其他P2P组 | 一台设备同一时间只能加入一个组 |
还有一个经验是,两台同品牌、同型号的设备反而更容易出现发现不了的问题,因为系统有时会缓存之前的P2P协商状态,导致双方都在等对方“先开口”。跨品牌设备踩中这个概率要小很多。
5. 从Demo到能上线的项目,还差这几步改造
5.1 Demo到底缺了什么
官方Demo的定位是教学样例,它把P2P的核心能力串起来了,但离一个能用的产品还很远。我盘了一下,主要缺这几块:
一是没有文件传输的完整实现。严格说Demo只是建立连接后发送一条短文本消息,实际项目里传的是大文件,你要自己处理分包、校验、进度回调、断点续传。
二是配对体验差。Demo需要手动点击设备列表再弹窗确认,如果在户外场景让一个不太懂技术的人操作,基本劝退。商用解决方案大多用二维码或者碰一碰的方式来简化配对。
三是稳定性不足。P2P连接在弱信号和系统内存紧张时非常容易掉线,Demo没有任何重连机制。
5.2 稳定传输和功耗控制的改造
传大文件时,我建议应用层做两层处理:传输层用分块+CRC校验,一个块传输失败就重传这一块,而不是整个文件重新来;协议层用JSON定义消息头,包含文件名、总大小、块序号、校验值,这样对端能清晰判断数据完整性。
功耗方面,P2P虽然比热点模式好,但网卡一直全速收发数据时电量掉得也很快。建议在传输完成或空闲超过一定时间后主动调用removeGroup()断开P2P连接,别让它一直保持。另外,发送大文件时可以考虑把屏幕熄灭时间调短,减少亮屏耗电,这和保活逻辑并不冲突。
配对体验上,Android 10以上提供了WifiP2pConfig.toXml()和fromXml()方法,可以把连接参数打包成XML字符串,再用二维码或者NFC触碰传递过去,对端解析后直接发起连接。我试过用ZXing把XML编码成二维码,扫码到解析再到连接成功,整个过程3秒左右,体验比手动选设备好很多。
5.3 选型建议:P2P不是万能方案,但用对场景很香
最后结合我的实际体会给你一些选型建议。如果只是两台设备在局域网内传文件,其实用普通Socket连接就好,没必要上P2P,因为P2P协商本身有延迟,而且对系统版本的兼容性要求高。
如果场景是“多台设备无网络环境下的文件分发”,比如野外勘察、临时会议,那P2P就是非常合适的选择。它比热点方案省去配网步骤,比蓝牙方案快一个数量级。但要注意P2P组内的设备数量限制,GO承载多个Client时性能压力会上升,跨厂商设备混组时也可能出现兼容性问题。
最后说一个我自己的深有体会的点:用这个Demo做演示时一定要准备两台真机,并且电量都充足。P2P调试过程里最耽误时间的往往不是代码逻辑,而是环境因素,权限、系统版本、厂商ROM都可能带来意外表现。如果你只是想先把功能跑通,别急着改代码,先去系统设置里把Wi-Fi直连功能手工验证一遍,底层通不通先确认好,再回到App层调试。这个顺序能帮你避开至少一半的坑。
本文还有配套的精品资源,点击获取