Wi-Fi Direct™ 的API可以让app应用连接到其附近的设备,而无需借助连接到网络或者热点。app应用可以快速发现附近设备并与之交互,其距离范围超过了蓝牙。
本节课讲解用Wi-Fi Direct如何发现附近设备并与之连接。
设置Application Permissions
为了使用Wi-Fi Direct, 把CHANGE_WIFI_STATE
, ACCESS_WIFI_STATE 和
INTERNET
权限添加到应用的manifest。Wi-Fi Direct不需要连接到互联网,但是其用到了标准的java socket,这需要 INTERNET
权限,所以需要添加以下权限.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.nsdchat" ... <uses-permission android:required="true" android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:required="true" android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:required="true" android:name="android.permission.INTERNET"/> ...
建立一个广播接收器Broadcast Receiver和点对点管理器Peer-to-Peer Manager
使用Wi-Fi Direct,需要监听broadcast的intent,其会通知app应用某确定事件的发生。在你的app应用中,实例化一个IntentFilter并设置其监听如下:
- 表明Wi-Fi Peer-To-Peer (P2P) 是否可用
- 表明可用peer列表已经发生改变
- 表明Wi-Fi P2P 连接状态已经发生改变
- 表明设备的配置细节已经发生改变
-
private final IntentFilter intentFilter = new IntentFilter(); ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 表明Wi-Fi Peer-to-Peer状态的改变 intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); // 表明可用peer列表的改变 intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); // 表明Wi-Fi P2P连接状态的改变 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); // 表明设备的配置细节的改变 intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
在onCreate()方法的最后,获取一个
WifiP2pManager的实例,调用其
initialize()
方法。该方法会返回一个WifiP2pManager.Channel
对象,这个对象会在你连接app到Wi-Fi Direct框架时用到。@Override Channel mChannel; public void onCreate(Bundle savedInstanceState) { .... mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); mChannel = mManager.initialize(this, getMainLooper(), null); }
现在创建一个新的
BroadcastReceiver
类用于监听系统的Wi-Fi P2P状态的变化。在onReceive()方法中,添加一个条件来处理上面所列每种P2P状态的变化。@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // 判定Wifi Direct mod是否启用,提示给Activity int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { activity.setIsWifiP2pEnabled(true); } else { activity.setIsWifiP2pEnabled(false); } } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Peer列表改变!我们需要为之做些什么 } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // 连接状态改变!我们需要为之做些什么 } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager() .findFragmentById(R.id.frag_list); fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)); } }
最终,添加代码以注册intent过滤器和广播接收器broadcast receiver,这发生在当你的main activity是活动时,然后在你的main activity暂停活动时移除对他们的注册。做这件事最恰当的位置是在
onResume()
和onPause()方法中。
/** 注册BroadcastReceiver */ @Override public void onResume() { super.onResume(); receiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this); registerReceiver(receiver, intentFilter); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); }
初始化Peer Discovery
调用discoverPeers()方法,以开始以Wi-Fi Direct来搜索附近的设备。该方法接受下面两个参数:
- 当你初始化peer-to-peer mManager时所返回的
WifiP2pManager.Channel
- 一个
WifiP2pManager.ActionListener
的实现,系统会在探索成功亦或不成功时调用其中的方法
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // 探索初始化成功时的处理代码写在这里。 // 但实际上还没有服务被探索到,所以这个方法可以留空。 // peer探索的代码放在onReceive方法,细节在下面讲到 } @Override public void onFailure(int reasonCode) { // 探索初始化失败时的处理代码写在这里。 // 通知用户出错 } });
记住这里只是初始化peer discovery。
discoverPeers()
方法开始探索过程然后会立刻返回。当peer discovery过程被成功初始化时,系统会通过调用所提供的action listener来通知你。 同时,探索过程继续保持活动,直到一个连击被初始化或者一个P2P组被形成。获取Peer列表
现在编写获取和处理Peer列表的代码。首先实现
WifiP2pManager.PeerListListener
接口,此接口提供关于Wi-Fi Direct检测到的Peer们的信息。具体代码如下:private List peers = new ArrayList(); ... private PeerListListener peerListListener = new PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peerList) { // 删除旧peer列表,添加新peer列表 peers.clear(); peers.addAll(peerList.getDeviceList()); // 如果一个ApapterView由该peer列表数据支持,通知其所发生的的改变。例如,如果 // 你有一个显示活动peer列表的ListView,那么触发这个改变。 ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged(); if (peers.size() == 0) { Log.d(WiFiDirectActivity.TAG, "No devices found"); return; } } }
现在修改广播接收器broadcast receiver的
onReceive()
方法,以实现在收到带有 actionWIFI_P2P_PEERS_CHANGED_ACTION的intent时就
调用requestPeers()。
你需要把监听器PeerListListener 传递到广播接收器内。一种方式就是将其设置为广播接收器的构造函数的参数。public void onReceive(Context context, Intent intent) { ... else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // 从wifi p2p manager获取可用peer列表。这是一个异步调用,通过对PeerListListener.onPeersAvailable()的回调来通知主调activity。 if (mManager != null) { mManager.requestPeers(mChannel, peerListListener); } Log.d(WiFiDirectActivity.TAG, "P2P peers changed"); }... }
现在,一个带有action
WIFI_P2P_PEERS_CHANGED_ACTION
的intent将会触发一个更新peer 列表的请求。连接一个Peer
为了连接到一个peer,首先创建一个新的
WifiP2pConfig
对象,然后从WifiPpDevice将数据拷贝到新建的WifiP2pConfig中,WifiPpDevice代表你想要连接到的设备。然后调用connect()
方法。@Override public void connect() { // 拾取网络上发现的第一个设备 WifiP2pDevice device = peers.get(0); WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; config.wps.setup = WpsInfo.PBC; mManager.connect(mChannel, config, new ActionListener() { @Override public void onSuccess() { // WiFiDirectBroadcastReceiver会通知我们。现在忽略。 } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.", Toast.LENGTH_SHORT).show(); } }); }
该代码片段中实现的WifiP2pManager.ActionListener
仅在初始化成功或失败时通知你。为了监听连接状态的变化,需要实现WifiP2pManager.ConnectionInfoListener接口。接口中的
onConnectionInfoAvailable()
回调方法会在连接状态中改变时通知你。为处理多个设备连接到同一设备的情况(像一个游戏中有三个或多个玩家),一个设备会被设计为“group owner”。@Override public void onConnectionInfoAvailable(final WifiP2pInfo info) { // InetAddress 来自WifiP2pInfo结构体 InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress()); // 群组协商后,可确定group owner。 if (info.groupFormed && info.isGroupOwner) { // 做group owner专做的任务。 // 一个常见实例是创建一个服务器线程,并接收连入请求 } else if (info.groupFormed) { // 客户端设备。创建一个客户端线程,连接到group owner。 } }
现在返回到广播接收器的
onReceive()
方法,修改对WIFI_P2P_CONNECTION_CHANGED_ACTION
intent监听的代码部分。当接收到该intent时,调用requestConnectionInfo()
。这是一个异步调用,调用结果会由连接信息监听器接收到。... } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { if (mManager == null) { return; } NetworkInfo networkInfo = (NetworkInfo) intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); if (networkInfo.isConnected()) { // 我们正在连接到其他设备,请求连接信息以发现group owner的IP mManager.requestConnectionInfo(mChannel, connectionListener); } ...
- 当你初始化peer-to-peer mManager时所返回的
WIFI_P2P_STATE_CHANGED_ACTION
WIFI_P2P_PEERS_CHANGED_ACTION
WIFI_P2P_CONNECTION_CHANGED_ACTION
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION