10.4 基于WiFi连接的P2P通信
基于WiFi连接的P2P(Peer-to-Peer,点对点)通信,指的在同一WiFi网络下的设备彼此间通过WiFi网络直接建立连接,进行数据交换。其概念与Windows的网络邻居非常相似。在Windows网络邻居中,用户可以看到同一网络下的其他用户分享出来的文件资源,通过建立Socket连接从其他用户那里获取资源或上传数据。
对于基于Android的移动设备而言,无法在彼此间建立有线的物理连接,但在同一网络内部的不同设备,可以基于WiFi建立连接,进行点对点(P2P)地数据交互。与前面讨论的蓝牙和NFC相比,基于WiFi连接进行设备间点对点的数据交换,设备间有效的传输距离更远,可以是几米、几十米甚至上百米;并且基于WiFi的数据传输速度更快,对设备硬件的要求更低,是一种非常廉价并且高效的互联互通方式。
但是,基于WiFi的P2P通信,受到网络环境的约束。它要求设备需要同时连接到同一个WiFi网络下,因此比较难实现随时地数据交换。
在老的Android版本中,基于WiFi连接的P2P通信实现起来非常复杂,需要开发者自行监听端口,发送广播或组播数据包来发现彼此,然后建立TCP或者UDP连接进行数据交换。为了使得设备能够快速地发现彼此,开发者需要解决大量的实际问题,包括设备的兼容性、耗电量、后台运行,等等,严重影响了P2P连接的稳定性、实时性和便利性。
为了帮助开发者更好地在WiFi下建立P2P连接,在最新的Android 4.0中,Android对基于WiFi连接进行了系统级别的支持,使开发者无需关注设备发现、设备兼容等问题,而专注在连接建立和数据交换上,大大节省了开发成本。
10.4.1 Android 4.0的P2P连接实现
在Android 4.0中,Android对基于WiFi的P2P连接提供了全面的支持[1]。在框架层,Android提供了android.net.wifi.p2p.WifiP2pManager来进行P2P连接,WifiP2pManager提供了扫描设备、连接设备、创建连接分组等接口,开发者对这些接口的调用,会被统一转发到android.net.wifi.p2p.WifiP2pService中。WifiP2pService是一个系统服务,运行在核心进程的独立线程中,它会将请求序列化成特殊的指令,通过预设的接口传递给底层实现。解析这些指令的底层实现,由硬件厂商提供(或者说为Android定制ROM的开发商或开发者提供),不同的设备提供商可以根据该设备的硬件特点最高效地支持基于WiFi的P2P发现和连接[2]。一旦扫描发现了设备的变化、设备信息的变更等,都会通过事件广播出去,监听者可以根据这些事件妥善处理P2P连接问题。
在这样的架构模式下,WifiManager提供的接口大都是异步接口,调用时需要构建对应的回调对象,来处理返回结果。比如,使用WifiManager.discoverPeers接口,可以促使系统开始扫描同一WiFi网络下的其他设备,如果要了解该次调用是否使系统开始扫描设备,就需要实现android.net.wifi.p2p.WifiP2pManager.ActionListener接口来处理调用的返回值:
//获取P2P连接服务
WifiP2pManager manager=(WifiP2pManager)
getSystemService(Context.WIFI_P2P_SERVICE);
//构造Channel,包含当前的消息循环
Channel channel=manager.initialize(this, getMainLooper(),null);
//开始扫描设备,注册回调对象
//该回调对象的相关函数,会在给定Channel的消息循环中执行
manager.discoverPeers(channel,
new WifiP2pManager.ActionListener(){
@Override
public void onSuccess(){
//如果系统接收请求,开始扫描,会回调该函数
…
}
@Override
public void onFailure(int reasonCode){
//如果系统无法接收请求,或者无法扫描,会回调该函数
…
}
});
其中,在调用WifiManager的相关接口时,需要传入android.net.wif.p2p.WifP2p-Manager.Channel对象。Channel,顾名思义,就是收听回调的频道,其中封装了给定的消息循环对象。WifiP2pManager保证所有的回调都发生在给定Channel所封装的消息循环上,使得调用者可以线程安全地处理各个回调事件。
当开发者调用WifiP2pManager提供的各个接口驱动P2P设备发现和连接后,系统会通过不断的广播事件告知最新的设备变化信息。比如,当WifiP2pManager.discoverPeers函数调用完毕后,系统发现同一WiFi网络下的设备发生变更时,会发出Action信息为android.net.wifi.p2p.PEERS_CHANGED的广播事件。开发者可以注册触发器组件来监听该事件,重新获取设备列表:
//监听Wifi P2P的事件变更
public class WiFiP2pReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent){
String action=intent.getAction();
if(WifiP2pManager
.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)){
//构造相关对象
…
//重新请求设备列表
manager.requestPeers(channel, listener);
}else{
//处理其他的相关事件
…
}
}
}
触发器组件可以以不同的插拔方式注册监听,如果只是当用户与应用进行交互时需要监听设备的变更,那可以动态热插拔进行监听。
通过上述接口的扫描,系统就可以获得周边设备的信息android.net.wif.p2p.WifP2pDevice。WifiP2pDevice对象中,包含各个设备的名称、Mac地址、设备状态等信息,可以帮助用户找到需要连接的设备。
发现了目标设备后,系统就可以通过其WifiP2pDevice对象中包含的信息,发起连接。每次连接相关的信息,可以通过连接配置对象android.net.wifi.p2p.WifiP2pConfig来进行描述,依据WifiP2pConfig对象,可以通过WifiP2pManager.connect接口向目标设备发起连接请求。
如果目标设备在经过必要的验证后,允许了此次连接,WiFi P2P连接服务会广播Action名为android.net.wifi.p2p.CONNECTION_STATE_CHANGE的事件,告知所有的连接事件的监听者,该设备与目标设备间已经交换了必要信息,建立了连接,可以进行数据传输了。
而后,通过调用WifiP2pManager.requestConnectionInfo接口,该设备可以获取到目标设备的android.net.wif.p2p.WifP2pInfo信息,其中包含了目标设备的IP地址信息等信息。有了这些信息,就可以这两个设备间建立Socket连接,进行具体的数据交换。
[1]关于Android Wifi连接P2P的介绍,可以参见SDK文档:http://developer.android.com/guide/topics/wireless/wifip2p.html。
[2]但这样的策略同样也给实际开发带来了很大的困扰。由于各个设备厂商对相关接口实现的技术跟进不够,导致市面上大部分设备还未能支持该功能。