Wifi_Direct是目前设备间最快的无线数据连接方式,速度可以达到40Mb/s。Google从Android 4.0(ICS)开始支持Wifi_Direct,而三星则更早些就在它自己的设备上支持了Wifi_Direct。几年来,Wifi_Direct的发展一直不温不火,但是目前市面上支持Wifi_Direct的设备并不是很多。
从目前接触过得设备来看,三星I9100的Wifi_Direct功能其实使用了Wifi的硬件,所以,它在使用Wifi_Direct功能时,无法使用wifi;nexus7、Padfone infinite(A80)则有独立的硬件来支持Wifi_Direct,所以,在使用Wifi_Direct功能的时候,Wifi仍旧可用。
Android framework提供了一个android.net.wifi.p2p包来提供对于Wifi_Direct的支持,其中包含了7个class和9个interface。其中WifiP2pManager为最核心的class,其他的class和interface都为它所用。
使用Wifi_P2p需要的Permission有两个:
public static final String ACCESS_WIFI_STATE
Added in API level 1
Allows applications to access information about Wi-Fi networks
Constant Value: "android.permission.ACCESS_WIFI_STATE"
public static final String CHANGE_WIFI_STATE
Added in API level 1
Allows applications to change Wi-Fi connectivity state
Constant Value: "android.permission.CHANGE_WIFI_STATE"
Wifi_Direct的大致配对流程如下:
1. WifiP2pManager.discoverPeers()开始扫描设备
2. 获取扫描到的设备,选择其中一个设备进行连接配对WifiP2pManager.connect
3. 配对成功后,根据WifiP2pInfo.isGroupOwner和WifiP2pInfo.groupOwnerAddress进行连接。
流程图如下:
个人认为Wifi_Direct配对需要注意的问题:
1. Setting中启用/关闭WifiP2p按钮,应该是和Wifi的启用/关闭按钮放在一起了(其实,有些设备的实现中,Wifip2p使用的就是wifi的硬件),所以使用WifiP2p功能需要开启Wifi。
2. Setting中BlueTooth有一个“让自己可见”的按钮,而Wifi_Direct没有这样的设置,仅提供了一个启动scan的按钮。本人尚未明确在未启动scan的情况下,设备对于其他wifi_direct是否是可见的,但是可以明确scan中的wifi_direct设备对其他设备来说是可见的。所以,建议需要进行配对的两台Wifi_Direct设备都进行scan。
3. 配对成功的前提条件是:进行配对的两台设备都必须能够扫描到对方。所以,两台设备都进行scan操作的根本原因在这里。
4. 开发者无法决定GroupOwner是哪台设备,但是可以通过WifiP2pConfig.groupOwnerIntent参数进行建议。
从测试的结果来说,Wifi_Direct的表现受具体设备的影响很大,配对的速度也有较大差异,从10秒到2分钟甚至更久。大概的来说,nexus7成功的概率较高,个人感觉可以达到70%的成功率,Padfone infinite(A80)的成功率在50%以下。
为了兼容传统的Wifi设备,Wifi_Direct其实还存在另一种使用方式,暂且称为兼容模式。兼容模式的特点在于,只需要担任GroupOwner的设备支持Wifi_Direct,而其他设备只需要支持传统的Wifi就可以了(个人觉得其实这种使用模式很像Android的便携热点功能)。
操作流程为:
1. 支持Wifi_Direct的设备创建group,WifiP2pManager.createGroup(),成为GroupOwner。
2. 其他设备扫描Wifi_Direct设备创建group后产生的Wifi热点并连接即可。
兼容模式存在的一个问题是:因为作为group member的设备是使用Wifi硬件接入到group中,所以会导致member进行wifi 热点切换以及网络中断,可能对正在进行的网络操作造成影响,而group owner则不存在这个问题。另外,而WifiP2p配对的使用方式,WifiP2p和Wifi可以独立运作,相互不受影响。
但是,兼容模式因为省去了扫描和配对的过程,所以建立连接的成功率明显提升,并且建立连接的速度要快不少(具体时间比较随机)。
从个人的使用感觉来讲,这WifiP2p这套API接口高度的异步化,API都需要以回调的方式获取操作结果(包内interface比较多的原因就在于此)。更加麻烦的是,几个关键API(例如WifiP2pManager.connect)的回调获取到的结果仅仅是执行是否开始,真正的结果还得注册broadcast receiver,通过监听广播来获得,才能进行下一步操作。异步的设计提高了代码的逻辑复杂度。
使用NFC来实现WifiP2p的连接:
1. 使用NFC将owner设备创建的group的SSID和密码传递给member设备
2. owner开始监听指定端口,等待member的连接
3. member接收到nfc传递过来的数据后,根据SSID和密码连接到group
4. 连接成功以后,过去owner设备的ip地址(获取gateway ip即可),连接到owner的指定端口
常见问题:
1. WifiP2p相关的广播有哪些,各自有哪些参数?
WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION:当WifiP2p扫描开始或者停止时,触发该广播
该广播包含一个int型extra, key为WifiP2pManager.EXTRA_DISCOVERY_STATE,其值为WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED或者WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED.
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ATIONIC:当WifiP2p状态发生变化时触发(如果WifiP2p可用,那么当BroadcastReceiverregister时,也会收到该广播)
该广播包含一个int型extra,key为WifiP2pManager.EXTRA_WIFI_STATE,其值为WifiP2pManager.WIFI_P2P_STATE_ENABLED或者WifiP2pManager.WIFI_P2P_STATE_DISABLED。
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:当设备的WifiP2p状态发生变化时触发广播(如果WifiP2p可用,那么当BroadcastReceiverregister时,也会收到该广播)
该广播包含一个类型为WifiP2pDevice的extra,key为WifiP2pManager.EXTRA_WIFI_P2P_DEVICE.
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION:当WifiP2p扫描时,发现device列表发生变化时,触发该广播
该广播不含extra,开发者应该接收到此广播后,调用WifiP2pManager.requestPeers()函数查询当前设别列表。
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:当WifiP2p的group发生变化时,触发该广播。
该广播包含两个extra:
key:WifiP2pManager.EXTRA_NETWORK_INFO,其值为NetworkInfo类型。
key:WifiP2pManager.EXTRA_P2P_INFO,其值为WifiP2pInfo类型。
PS:这里的WifiP2p group发生变化包含如下情况:
1. 建立group
2. member加入到group
3. member退出group
4. 关闭group
2. 如何获得WifiP2pGroupInfo,它有什么用?
WifiP2pManager.requestGroupInfo()函数,可以获取GroupInfo,较为有用的api有:
1. GroupInfo.getClientList()可以获得连接到group的member列表
2. GroupInfo.getNetWorkName()可以获得group的wifi热点名称(SSID)
3. GroupInfo.getPassphrase() 可以获得连接到wifi 热点的密码
3. 如何获得WifiP2pInfo?
可以从WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION广播中的extra中获取
也可以从WifiP2pManager.requestConnectionInfo()函数获取。
4. 如何防止配对产生的提示框?
在不修改framework的情况下,本人暂时为找到可行的方案。
这个提示狂是由系统提供的,具体表现视设备而定。nexus只在第一次配对的时候弹出,而A80每一次配对都会弹出。
但是,使用兼容模式使用Wifi_Direct是没有提示框的。
5. 如何实现wifi热点的连接?
经过测试,在A80上,如下代码可以实现连接到热点。
- // build a wifi config
- final WifiConfiguration config = new WifiConfiguration();
- config.allowedAuthAlgorithms.clear();
- config.allowedPairwiseCiphers.clear();
- config.allowedGroupCiphers.clear();
- config.allowedKeyManagement.clear();
- config.allowedProtocols.clear();
- config.SSID = "\"" + ssid + "\"";<span style="color:#009900;">//设定ssid</span>
- config.preSharedKey = "\"" + pw + "\"";<span style="color:#009900;">//设定密码</span>
- config.hiddenSSID = false;
- config.status = WifiConfiguration.Status.ENABLED;
- config.priority = 1;
- config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
- config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
- config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
- config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
- config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
- config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
- config.allowedPairwiseCiphers.set(3);
- config.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
- config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
- // connect to ap
- int id = WifiManager.addNetwork(config);
- config.networkId = id;
- if (id != -1 && mWifiManager.enableNetwork(id, true)) {
- ....
- }
6. 如何通过代码开启便携热点?(这个问题和Wifi_Direct无关,但是可以让不支持Wifi_Direct的Android设备获得类似模拟模式的效果,可能和快牙的实现方式相似)
正常情况下,开启便携热点的API因为hide隐藏的关系,无法被apk调用,仅原生app可以调用。但是用java的反射机制可以让普通app也能调用这个api,实现如下:
- // wifi热点开关
- public boolean setWifiApEnabled(boolean enabled) {
- if (enabled) { // disable WiFi in any case
- //wifi和热点不能同时打开,所以打开热点的时候需要关闭wifi
- wifiManager.setWifiEnabled(false);
- }
- try {
- //热点的配置类
- WifiConfiguration apConfig = new WifiConfiguration();
- //配置热点的名称(可以在名字后面加点随机数什么的)
- apConfig.SSID = "YRCCONNECTION";
- //配置热点的密码
- apConfig.preSharedKey="12122112";
- //通过反射调用设置热点
- Method method = wifiManager.getClass().getMethod(
- "setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);
- //返回热点打开状态
- return (Boolean) method.invoke(wifiManager, apConfig, enabled);
- } catch (Exception e) {
- return false;
- }
- }
7. WifiP2pManager.discovePeers仅仅返回附近有哪些设备开启了wifi p2p,而app的实际使用场景,往往希望寻找可以提供某些特定服务的设备。例如同一房间内,有A,B,C,D四台设备开启了wifi p2p,而A设备和B设备都安装了app1,C设备和D设备都安装了app2,使用者希望A设备能和B设备配对连接,而C设备与D设备连接,运行在A设备上的app1如何识别它应该连接的是B设备,而非C、D设备呢?
为了支持更加个性化的设备发现,WifiP2pManager支持UPNP和DNS两种方式的设备(服务?)发现。
App可以通过WifiP2pManager.addLocalService来向周边的设备广播自己支持哪些服务。
也可以通过如下步骤实现发现这些服务:
1. 通过WifiP2pManager.addServiceRequest()添加服务请求
2. 通过WifiP2pManager.discoverService()开始服务发现
3. 通过WifiP2pManager.setDnsSdResponseListener()或者WifiP2pManager.setUpnpServiceResponseListener()监听服务内容。