Android WLAN 直连(对等连接或 P2P)
概念
Wi-Fi Direct®支持Wi-Fi设备相互直接连接,并非Android设备独有的,是WIfi联盟提供的一种设备与设备之间直接联网通讯的技术,实现点对点的连接,我们常说的Wifi P2P连接。
最大的特点和优势就是无需接入网络就可连接设备
有了Wi-Fi Direct,手机、摄像头、打印机、个人电脑和游戏设备无需互联网连接,就能够建立自己的Wi-Fi网络。Wi-Fi Direct设备相互连接后,通过设备设置,就可快速简便地传送或显示内容、玩游戏以及分享应用。这些设备可以一对一地连接,或者几个设备形成的一组设备可以同时连接。由于无需接入点或互联网连接,因此设备在哪里,哪里就有Wi-Fi Direct网络。设备之间建立的Wi-Fi Direct连接对包括Miracast®在内的很多应用而言属于底层技术。诸如智能手机、摄像头、打印机、电视机、个人电脑、游戏机等成千上万的设备均已通过认证。
随时随地建立连接
即使附近没有Wi-Fi网络可用,Wi-Fi Direct设备之间也可以随时随地建立连接。Wi-Fi Direct设备向同一区域内的其他设备发送信号,让这些设备知道可以建立连接。用户可以查看可用设备并请求连接,也可以接收另一台设备发出的连接邀请。两个或更多个经过Wi-Fi Direct认证的设备直接连接时,会使用Wi-Fi Protected Setup™形成一个Wi-Fi Direct设备组。
Android设备如何使用WLAN P2P
Android API 概览
WifiP2pManager 类提供的方法使您可以在设备上与 WLAN 硬件交互,以执行发现和连接对等设备等操作。可执行的操作如下:
initialize() | 通过 WLAN 框架注册应用。必须先调用此方法,然后再调用任何其他 WLAN P2P 方法。 |
---|---|
connect() | 启动与具有指定配置的设备的对等连接。 |
cancelConnect() | 取消任何正在进行的对等群组协商。 |
requestConnectInfo() | 请求设备连接信息。 |
createGroup() | 以群组所有者的身份,使用当前设备创建对等群组。 |
removeGroup() | 移除当前对等群组。 |
requestGroupInfo() | 请求对等群组信息。 |
discoverPeers() | 启动对等设备发现 |
requestPeers() | 请求已发现对等设备的当前列表。 |
WifiP2pManager 方法使您可以在侦听器中进行传递,以便 WLAN P2P 框架可以向您的 Activity 通知通话状态。下表介绍可用的侦听器接口和使用侦听器的相应 WifiP2pManager 方法调用:
侦听器接口 | 相关操作 |
---|---|
WifiP2pManager.ActionListener | connect()、cancelConnect()、createGroup()、removeGroup() 和 discoverPeers() |
WifiP2pManager.ChannelListener | initialize() |
WifiP2pManager.ConnectionInfoListener | requestConnectInfo() |
WifiP2pManager.GroupInfoListener | requestGroupInfo() |
WifiP2pManager.PeerListListener | requestPeers() |
WLAN P2P API 定义当发生特定 WLAN P2P 事件时会广播的 Intent,例如发现新的对等设备时,或设备的 WLAN 状态更改时。您可以通过创建处理这些 Intent 的广播接收器,在应用中注册接收这些 Intent:
Intent | 说明 |
---|---|
WIFI_P2P_CONNECTION_CHANGED_ACTION | 当设备的 WLAN 连接状态更改时广播。 |
WIFI_P2P_PEERS_CHANGED_ACTION | 当您调用 discoverPeers() 时广播。如果您在应用中处理此 Intent,则通常需要调用 requestPeers() 以获取对等设备的更新列表。 |
WIFI_P2P_STATE_CHANGED_ACTION | 当 WLAN P2P 在设备上启用或停用时广播。 |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION | 当设备的详细信息(例如设备名称)更改时广播。 |
创建 WLAN P2P 应用
创建 WLAN P2P 应用涉及为应用创建并注册广播接收器、发现对等设备,连接到对等设备,以及将数据传输到对等设备。以下部分将介绍如何完成此操作。
初始设置
在使用 WLAN P2P API 之前,您必须确保您的应用可以访问硬件,并且设备支持 WLAN P2P API 协议。如果设备支持 WLAN P2P,您可以获得 WifiP2pManager 的实例,创建并注册广播接收器,然后开始使用 WLAN P2P API。
请求在设备上使用 WLAN 硬件的权限,同时声明您的应用在 Android 清单中具有正确的最低 SDK 版本:
检查 WLAN P2P 是否开启并受支持。您可以在广播接收器收到 WIFI_P2P_STATE_CHANGED_ACTION Intent 时,在接收器中检查此项。向您的 Activity 通知 WLAN P2P 的状态,并作出相应回应:
@Override 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) { // Wifi P2P is enabled } else { // Wi-Fi P2P is not enabled } } ... }
获取和初始化P2P资源
获取 WifiP2pManager 的实例,并通过调用 initialize(),在 WLAN P2P 框架中注册您的应用。此方法会返回 WifiP2pManager.Channel,用于将您的应用连接到 WLAN P2P 框架。此外,您还应该通过 WifiP2pManager 和 WifiP2pManager.Channel 对象以及对 Activity 的引用,创建广播接收器实例。这样广播接收器便可通知 Activity 感兴趣的事件并进行相应更新。此外,您还可以操纵设备的 WLAN 状态(如有必要):
WifiP2pManager manager; Channel channel; BroadcastReceiver receiver; ... @Override protected void onCreate(Bundle savedInstanceState){ ... manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); channel = manager.initialize(this, getMainLooper(), null); receiver = new WiFiDirectBroadcastReceiver(manager, mChannel, this); ... }
创建 Intent 过滤器,然后添加与广播接收器检查内容相同的 Intent:
IntentFilter intentFilter; ... @Override protected void onCreate(Bundle savedInstanceState){ ... intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
在 Activity 的 onResume() 方法中注册广播接收器,然后在 Activity 的onPause() 方法中取消注册该接收器:
/* register the broadcast receiver with the intent values to be matched */ @Override protected void onResume() { super.onResume(); registerReceiver(mReceiver, intentFilter); } /* unregister the broadcast receiver */ @Override protected void onPause() { super.onPause(); unregisterReceiver(mReceiver); }
发现对等设备
如要发现可连接的对等设备,请调用 discoverPeers(),以检测范围内的可用对等设备。对此功能的调用为异步操作,如果您已创建 WifiP2pManager.ActionListener,则系统会通过 onSuccess() 和 onFailure() 告知应用成功与否。onSuccess() 方法仅会通知您发现进程已成功,但不会提供有关其发现的实际对等设备(如有)的任何信息:
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { ... } @Override public void onFailure(int reasonCode) { ... } });
如果发现进程成功并检测到对等设备,则系统会广播 WIFI_P2P_PEERS_CHANGED_ACTION Intent,您可以在广播接收器中侦听该 Intent,以获取对等设备列表。当应用接收到 WIFI_P2P_PEERS_CHANGED_ACTION Intent 时,您可以通过 requestPeers() 请求已发现对等设备的列表。以下代码展示如何完成此项设置:
PeerListListener myPeerListListener; ... if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() if (manager != null) { manager.requestPeers(channel, myPeerListListener); } }
requestPeers() 方法也为异步操作,并可在对等设备列表可用时通过 onPeersAvailable()(定义见 WifiP2pManager.PeerListListener 接口)通知您的 Activity。onPeersAvailable() 方法为您提供 WifiP2pDeviceList,您可对其进行迭代以查找希望连接的对等设备。
连接到对等设备
获取可能对等设备的列表,且已确定您要连接的设备后,调用connect() 方法即可连接到相应设备。调用此方法需要使用 WifiP2pConfig 对象,其中包含要连接的设备的信息。您可以通过 WifiP2pManager.ActionListener 获知连接是否成功。以下代码展示如何创建与所需设备的连接:
//obtain a peer from the WifiP2pDeviceList WifiP2pDevice device; WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { //success logic } @Override public void onFailure(int reason) { //failure logic } });
建立网络连接
当P2P连接完成后,IP地址是由DHCPServer分配的,一般是Group Owner所在的设备开启DHCPServer给自己和对端(GC端)分贝IP地址。
如果当前设置是GC(Group Client),那么可通过WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION广播直接拿到GO端的IP地址
if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { NetworkInfo networkInfo = (NetworkInfo) intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); WifiP2pInfo p2pInfo = (WifiP2pInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); WifiP2pGroup p2pGroup = (WifiP2pGroup) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); if (networkInfo.isConnected()) { device_addr[0] = p2pInfo.groupOwnerAddress.getHostAddress(); mController.setGroupModule(false); WifiP2pDevice device = p2pGroup.getOwner(); if (device != null && device.getWfdInfo() != null) { mPort = String.valueOf(device.getWfdInfo().getControlPort()); mIp = p2pInfo.groupOwnerAddress.getHostAddress(); } else { Log.d(TAG, "device or device wfdInfo is null"); } } }
如果当前设备是GO(Group Owner)那么就需要用其他方法获取到对端IP地址了,Android 原生未提供直接获取GC IP的方法
如果可以修改AOSP源码的话,可以通过DHCPServer给GC分配IP地址时,把地址给到当前的程序:
Android 源码:packages/modules/NetworkStack/src/android/net/dhcp/DhcpServer.java
transmitAck方法中添加以下code
private boolean transmitAck(@NonNull DhcpPacket packet, @NonNull DhcpLease lease, @NonNull MacAddress clientMac)
Intent notifyMiracast = new Intent(WIFI_P2P_IP_ADDR_CHANGED_ACTION); notifyMiracast.putExtra(WIFI_P2P_PEER_IP_EXTRA,lease.getNetAddr().toString().substring(1)); notifyMiracast.putExtra(WIFI_P2P_PEER_MAC_EXTRA,lease.getHwAddr().toString()); Log.d(TAG,"sendBroadcast IP " + notifyMiracast.getStringExtra(WIFI_P2P_PEER_IP_EXTRA) + "MAC " + notifyMiracast.getStringExtra(WIFI_P2P_PEER_MAC_EXTRA)); mContext.sendBroadcast(notifyMiracast);
也可以读取ARP方法来获取IP
private String macAddressFromRoute(String macAddress){ String ipAddress = null; BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("/proc/net/arp")); // Skip over the line bearing column titles String line = reader.readLine(); while ((line = reader.readLine()) != null) { String[] tokens = line.split("[ ]+"); if (tokens.length