简介:Android官方WiFiDirectDemo详解资源,面向需要掌握Wi-Fi Direct(P2P)技术的Android开发者,内容基于官方示例深入剖析,帮助理解无需传统接入点即可让设备直接建立高速连接的底层机制,适用于文件快传、局域网对战、无线投屏等典型场景。压缩包共包含88个文件,以PNG界面截图、Java源码和XML配置为主,也包含可直接运行的APK、编译产生的class文件以及工程配置文件,整体体积仅244KB,适合作为轻量级学习素材导入开发环境查看。已有781人学习。内容完整覆盖设备发现、群组创建、连接管理、Socket数据传输等核心模块,并结合实际代码讲解WifiP2pManager、WifiP2pDeviceList等关键API的调用流程,给出权限声明、异步回调、资源释放等常见注意事项。对照源码动手实践,可快速构建起Wi-Fi P2P应用的开发框架,也可用于排查连接异常等问题,支撑二次开发与技术创新。 在自己动手写过几台设备之间的文件互传、局域网聊天室之后,你大概率会碰到一个问题:在没有路由器、没有热点、完全没有外部网络的环境下,两台手机怎么直接通信?如果只靠蓝牙,速度慢、距离短、配对还麻烦。Android官方其实给了一套很成熟的方案,就是WiFi P2P(也叫WiFi Direct),而官方提供的Wifi P2P Demo,是理解这套机制最好的入门教材。
我最早接触这个Demo是在做车间设备巡检工具的时候,现场没有局域网,只有几台安卓平板需要互相传数据。翻了一圈方案,最后落到了WiFi P2P上。官方Demo虽然界面很朴素,但该有的核心流程全都有:设备发现、连接建立、Socket传输、断开清理。这篇文章就从我的实际使用角度出发,把这个Demo的架构、原理、跑通流程和踩坑记录都梳理一遍,哪怕你是刚接触安卓网络开发,照着走也能复现一套能用的P2P通信。
1. 项目概述与核心价值
1.1 官方Demo到底做了什么
先给不熟悉的读者一句话概括:Android官方Wifi P2P Demo是一个展示如何使用WiFi Direct API实现设备间直连通信的示例工程。它解决的核心问题是“无AP(无线路由器)环境下,两台设备怎么自动发现并完成数据交换”。
很多人第一次听到“WiFi P2P”会以为和蓝牙配对差不多,其实两者思路完全不同。蓝牙走的是“主从配对”模式,一个主设备带多个从设备;WiFi P2P走的是“协商角色”模式,设备之间先互相发现,再协商出谁是Group Owner(组主,相当于逻辑热点),谁做Client。虽然也有主从关系,但角色是动态协商出来的,而且传输带宽继承WiFi的物理能力,实测可以跑到几MB/s到几十MB/s,远不是蓝牙能比的。
Demo本身是一个完整的Android应用工程,代码量不大,但完整覆盖了以下核心能力点:
- 打开/关闭WiFi P2P功能的状态监听
- 发起设备发现(discoverPeers)并展示发现的设备列表
- 主动连接指定设备,或者作为服务端等待被连接
- 通过Socket建立TCP连接,实际传输数据
- 监听连接断开、本机设备信息变化等系统广播
换句话说,虽然它界面简陋,但它把WiFi P2P通信的全部生命周期都实现了。你在这个基础上做UI改造、做上层业务协议,就是一套可落地的方案。
1.2 这套方案适合什么场景
判断一个技术适不适合自己的项目,比学技术本身更重要。我理解WiFi P2P的最佳适用场景有这几个特征:
第一,现场没有可用的局域网基础设施。比如户外作业、临时会议室、车载环境、设备巡检,路由器不一定有,或即便有也不允许你接入。第二,数据量中等偏大,蓝牙实在扛不动。比如传输几十MB的图片、日志、离线地图包,用经典蓝牙能传到人心态崩溃,WiFi P2P则轻松许多。第三,交互对象有限,不是大规模组网。WiFi P2P设计上更偏向小范围多设备互连,最多建议十几台以内,这个量级下体验稳定;如果要做上百台设备同步,就得考虑其他方案。
还有一个很容易被忽略的价值点:WiFi P2P的数据链路是设备间直连,不经过服务器中转,所以它天然具备高隐私和低延迟特性。在某些数据敏感的场景下,不让数据出设备本身就是一种安全策略。这也是我后来老喜欢在项目里留一个P2P传输模块的原因——有备无患。
2. 核心原理拆解:WiFi P2P的完整工作链路
2.1 从设备发现到连接的四个阶段
WiFi P2P的一次完整通信链路,可以拆成四个阶段:设备发现、服务发现(可选)、连接协商、数据传输。官方Demo重在演示前三个阶段加最后一个阶段的Socket基础实现。
设备发现阶段,本机会发出P2P探测请求,周围开启了WiFi P2P功能的设备会响应,系统把这些设备以WifiP2pDevice列表形式回传给你。这个阶段有一个重要概念叫“Device Addressing”,每个设备都有一个P2P MAC地址和一组设备信息,WifiP2pDevice会封装这些字段供上层使用。
连接协商阶段是整个流程的灵魂。发起方通过connect()方法传入目标设备的deviceAddress,系统开始进入协商流程。此时两台设备会根据一些内部策略(比如信道状态、设备能力)决定谁做Group Owner,谁做Client。官方Demo里专门做了一个“断开连接”按钮,是因为这个协商结果不是永久绑定的,每次连接都可能动态变化,代码不能写死角色。
数据传输阶段就进入常规的Socket编程了。Group Owner会创建一个ServerSocket监听端口,Client通过connect()方法拿到Group Owner的IP地址后发起TCP连接。一旦TCP通道建立,后面跑HTTP协议、自定义二进制协议、文件分片传输,全都随你发挥,P2P只负责提供一条干净的点对点数据管道。
2.2 关键类与回调机制解读
官方Demo的代码结构其实清晰得像一本教材,核心类就这几个:
WifiP2pManager是总入口,所有P2P操作都从这里发起。它内部通过Binder与系统服务通信,所以拿到实例后第一件事是调用initialize()方法绑定Channel。Channel是应用与系统服务的通信管道,几乎所有异步方法都需要Channel参数。
WifiP2pManager.Channel是连接应用和WiFi P2P框架的桥梁。一个应用中通常只需要一个Channel,在onCreate或onResume阶段初始化,但要记住在onDestroy或onPause里做清理,防止内存泄漏。
BroadcastReceiver是接收系统P2P状态变化的主要途径。Demo里自定义了一个WiFiDirectBroadcastReceiver,监听四个关键Action:WIFI_P2P_STATE_CHANGED_ACTION(P2P开关状态变化)、WIFI_P2P_PEERS_CHANGED_ACTION(发现设备列表变化)、WIFI_P2P_CONNECTION_CHANGED_ACTION(连接状态变化)、WIFI_P2P_THIS_DEVICE_CHANGED_ACTION(本机设备信息变化)。这几个Action是理解整个Demo信息流的关键钥匙。
再往下是各种Listener接口。发起操作后,系统通过回调返回结果:ActionListener处理操作成功/失败;PeerListListener返回设备列表;ConnectionInfoListener返回建立成功的连接信息(包含Group Owner IP)。注意这些回调都发生在主线程,所以不能在回调里做耗时操作,需要开子线程处理网络IO。
如果你之前做过蓝牙开发,会发现这套机制和BluetoothAdapter的套路非常像:也是Manager加BroadcastReceiver加Listener的组合拳。这种设计模式在安卓系统服务中很常见,理解了一个,另一个也就通了。
3. 实操过程:把官方Demo跑起来
3.1 源码获取与工程导入
官方Demo在Android SDK的samples目录下就能找到。打开Android Studio,选择“New Project”里的“Import Sample”也可以直接拉取。如果是手动下载,路径一般是SDK安装目录下的Samples文件夹,按API Level和分类找到WiFiDirectDemo即可。
我用的是Android Studio Flamingo版本,SDK Level 33,导入工程后大概率会遇到一个老问题:Gradle版本太旧。新版本Studio对老工程的Gradle兼容性比较差,我建议直接新建一个空工程,然后把Demo里的三个Java文件、一个布局文件和Manifest配置拷贝过来,这样反而省事。Demo核心代码就几个文件:WiFiDirectActivity.java、WiFiDirectBroadcastReceiver.java、DeviceListFragment.java、DeviceDetailFragment.java、WiFiDirectServicesFragment.java,加起来不到两千行,手动搬运完全可控。
Manifest里必须配置以下权限和组件声明。特别提醒一下:WiFi P2P的权限分两部分,一部分是普通权限(ACCESS_WIFI_STATE、CHANGE_WIFI_STATE、INTERNET),另一部分是定位权限(ACCESS_FINE_LOCATION),后者在Android 6.0以上是危险权限,必须运行时动态申请,而且API 29以上的系统,没有定位权限时discoverPeers()会静默失败。这个坑我后面会单独展开讲。
<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_FINE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" />3.2 真机部署与权限配置
这个Demo必须使用真机调试,模拟器无法模拟真实的WiFi芯片行为。我建议准备两台Android设备,Android版本至少在8.0以上,不同厂商、不同系统版本混搭更能验证兼容性。
跑起来的第一步是动态权限申请。老版本Demo可能在Android 6.0之后的系统上不会主动弹权限框,需要在Activity的onCreate或onResume里手动检查并申请ACCESS_FINE_LOCATION。我习惯写一个简单粗暴的判断:如果checkSelfPermission不等于GRANTED,就requestPermissions。没有这个权限,后续所有P2P操作都会返回ERROR或者干脆没有回调,排查起来很诡异。
第二件事是确认两台设备都开启了WiFi。注意这里不是要求连上某个路由器,而是WiFi开关本身得打开。P2P功能复用了WiFi芯片的射频能力,WiFi开关关着,P2P是起不来的。另外,如果其中一台设备已经连接着某个5GHz热点,有些老设备的驱动会限制P2P并发,导致发现设备不稳定,这是硬件层面的兼容性问题,只能换设备规避。
权限和WiFi都就绪之后,两台设备打开App,点击搜索(Search)按钮,正常情况下几秒后就能互相看到对方。如果搜索不到,先不要怀疑代码,优先检查定位权限是不是真的给了、系统WiFi扫描是不是有节电策略限制。实际经验里,90%的“搜不到设备”问题都能归结到这两点。
3.3 Demo界面与交互逻辑走读
官方Demo的界面虽然朴素,但信息设计很规范。主界面主要有两个页面:设备列表页和设备详情页。
设备列表页(对应DeviceListFragment)顶部有一个“搜索”按钮,点击后调用discoverPeers()发起发现流程。发现结果通过onPeersAvailable()回调返回,WifiP2pDeviceList包含所有发现的设备,列表项展示设备名称、设备地址、设备状态(CONNECTED、INVITED、FAILED、AVAILABLE等)。这个列表还有一个细节:它会显示本机设备,因为WiFi P2P发现是双向广播,自己也参与了发现过程。
设备详情页(对应DeviceDetailFragment)展示了选中设备的完整信息,核心操作有两个按钮:连接(Connect)和取消(Cancel)。连接按钮调用的connect()方法会触发设备间的P2P协商,成功后左侧设备的Fragment会显示GroupOwnerIP信息,这个IP就是后续Socket连接的目标地址。整个交互链路清晰直观,很适合用来理解“操作-回调-UI刷新”的安卓异步模式。
4. 核心代码解析:从广播接收到连接建立
4.1 BroadcastReceiver的注册与处理
看这个Demo,我建议第一个精读的文件就是WiFiDirectBroadcastReceiver。它的onReceive方法是一个巨大的switch分支,分别处理四个Action。这个逻辑不复杂,但它是整个P2P功能的信息中枢,任何状态变化都必须经过它才能驱动UI更新。
public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { // P2P功能可用 } else { // P2P功能不可用 } } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { if (manager != null) { manager.requestPeers(channel, peerListListener); } } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); WifiP2pInfo p2pInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); if (networkInfo.isConnected()) { // 解析p2pInfo,拿到Group Owner IP } } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { // 更新本机设备信息 } }看到没有,PEERS_CHANGED_ACTION广播到达后,Receiver不是直接拿设备列表,而是调用manager.requestPeers()主动拉取。这个设计初看有点绕,其实是安卓系统服务的统一风格:广播只负责通知“事情变了”,具体数据要通过Manager的同步/异步方法去取。好处是数据始终是最新的,坏处是如果你忘记在Receiver里调用对应的request方法,UI就永远不会更新。
在Activity的生命周期管理上,Demo的处理也很规范:onResume里注册Receiver,onPause里注销Receiver。千万不要在onCreate里注册后就不管了,否则Activity不可见时还会收到广播,轻则空指针,重则更新已经不存在的UI组件导致崩溃。
4.2 connect阶段与Socket传输细节
连接逻辑是Demo最值得学习的部分。设备列表中点击一个设备,connect()方法会被调用:
manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { // 连接请求成功发出,等待WIFI_P2P_CONNECTION_CHANGED_ACTION广播 } @Override public void onFailure(int reason) { // 连接失败,reason是错误码,如P2P_UNSUPPORTED、BUSY等 } });这里有个关键点需要理解:onSuccess回调并不代表连接已经建立,它只表示“连接流程已发起”。真正连接成功的标志是收到WIFI_P2P_CONNECTION_CHANGED_ACTION广播,且NetworkInfo.isConnected()为true。很多人初学时会在这个回调里立刻去拿连接信息,拿到的是空的,就是因为没理解异步回调的时序。
连接建立后,WifiP2pInfo对象里有两个重要字段:groupOwnerAddress(Group Owner的IP地址)和isGroupOwner(本机是否是Group Owner)。如果本机是Group Owner,就创建ServerSocket监听端口;如果是Client,就用groupOwnerAddress去连接。官方Demo示例里ServerSocket绑定的是8888端口,具体使用时要根据业务调整端口,同时注意避免和其他应用冲突。
Socket数据传输这块,官方Demo用的是流式读写,一个线程负责往外写,一个线程负责从流里读。这部分在实际生产环境中需要做协议设计:至少要定义消息头、长度字段、校验和,否则你无法判断一次读取到的数据是否完整。我在自己项目里就采用了一个很简单的方案:前4个字节是消息长度,后面跟业务数据,接收端先读取长度再读取对应大小内容,这个设计虽然基础,但特别实用。
5. 常见问题与排查技巧实录
我把这段时间用WiFi P2P碰到的典型问题和排查思路整理成了一张速查表,希望能帮你少走弯路:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 设备搜索不到对方 | 未开启定位权限 | 检查ACCESS_FINE_LOCATION是否授予,Android 6.0+必须动态申请 |
| 设备搜索不到对方 | WiFi开关未开启 | P2P依赖WiFi射频,确认WiFi已开启,不要求连接热点 |
| 设备搜索不到对方 | 系统节电/扫描限制 | 部分厂商系统会限制后台扫描,尝试保持屏幕常亮或在设置里允许后台WiFi扫描 |
| connect返回BUSY | 上一次连接未完全释放 | 调用removeGroup()清理,或等待几秒重试 |
| connect返回ERROR | P2P状态异常 | 重启WiFi开关,或重新初始化Channel |
| 连接成功但Socket连不上 | 端口/地址不对 | 确认使用的是groupOwnerAddress,确认ServerSocket监听的是0.0.0.0 |
| 连接成功但Socket连不上 | 防火墙/路由器干扰 | P2P是直连模式,一般不受路由器影响,重点检查IP是否拿对 |
| 传输速度慢 | 信道拥挤或距离过远 | 尽量保持设备近距,避免中间有遮挡物 |
| 新系统上权限一直拿不到 | 未声明位置权限 | Manifest必须声明ACCESS_FINE_LOCATION,并动态申请 |
下面分享两个让我印象最深的实际案例。第一个是搜索设备无响应,查了很久才发现是系统省电策略把WiFi扫描挂起了,在测试机上关闭“智能省电”后立刻恢复,这个排查过程非常折磨人。第二个是Socket连接偶尔失败,最终定位到设备从P2P转到普通WiFi时IP段冲突,Socket连到了错误地址,解决办法是在建立连接前检查网络状态并做一次延迟重试。
还有一点要特别提醒:P2P连接结束或者页面销毁时,一定要调用manager.removeGroup()清理连接,否则下次连接大概率会出现BUSY状态。你可以在Activity的onDestroy里做这个清理操作,但要注意removeGroup是异步操作,最好配合ActionListener确认清理结果。
6. 最后的经验心得
跑通官方Demo只能算迈出了第一步,真正做产品时还需要补充很多东西。首先是UI层的状态管理,Demo的状态展示非常基础,真实产品需要明确区分“未开启WiFi”、“正在搜索”、“连接成功”、“数据传输中”等状态,并且要能响应用户的取消操作。其次是传输可靠性,Demo没有断线重连机制,没有传输确认机制,真实场景一定要有自己的心跳包和重传策略。
另外,对数据协议这层,如果你对跨端兼容性有要求,建议把Socket之上的通信格式设计成JSON或Protobuf,这样可以避免后续接入iOS或其他平台时的适配成本。P2P本身不限定操作系统,只要底层是TCP流,上层协议设计得合理,未来扩展非常容易。
在动手做业务之前,我还会建议你先把Demo完整地跑通一遍,并且用两台不同厂商的设备互相连接,亲身体验一遍连接建立的完整过程。这个“从点击搜索到两端看到IP”的体验,比读很多文档都有价值。我就是在多次调试中才真正理解Group Owner协商机制和各种回调时序,这些经验在之后的项目开发里帮了我大忙。
本文还有配套的精品资源,点击获取