使用 WiFi 建立 P2P 连接

编写:naizhengtan - 原文:<http://developer.android.com/training/connect-devices-wirelessly/wifi-direct.html>

Wi-Fi 点对点(P2P)API 允许应用程序在无需连接到网络和热点的情况下连接到附近的设备。(Android Wi-Fi P2P 使用 Wi-Fi Direct™ 验证程序进行编译)。Wi-Fi P2P 技术使得应用程序可以快速发现附近的设备并与之交互。相比于蓝牙技术,Wi-Fi P2P 的优势是具有较大的连接范围。

本节主要内容是使用 Wi-Fi P2P 技术发现并连接到附近的设备。

配置应用权限

使用 Wi-Fi P2P 技术,需要添加 CHANGE_WIFI_STATE,ACCESS_WIFI_STATE 以及 INTERNET 三种权限到应用的 manifest 文件。Wi-Fi P2P 技术虽然不需要访问互联网,但是它会使用标准的 Java socket(需要 INTERNET 权限)。下面是使用 Wi-Fi P2P 技术需要申请的权限。

1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2. package="com.example.android.nsdchat"
3. ...

5. <uses-permission
6. android:required="true"
7. android:name="android.permission.ACCESS_WIFI_STATE"/>
8. <uses-permission
9. android:required="true"
10. android:name="android.permission.CHANGE_WIFI_STATE"/>
11. <uses-permission
12. android:required="true"
13. android:name="android.permission.INTERNET"/>
14. ...

设置广播接收器(BroadCast Receiver)和 P2P 管理器

使用 Wi-Fi P2P 的时候,需要侦听当某个事件出现时发出的broadcast intent。在应用中,实例化一个 IntentFilter,并将其设置为侦听下列事件:

WIFI_P2P_STATE_CHANGED_ACTION

指示 Wi-Fi P2P 是否开启

WIFI_P2P_PEERS_CHANGED_ACTION

代表对等节点(peer)列表发生了变化

WIFI_P2P_CONNECTION_CHANGED_ACTION

表明Wi-Fi P2P的连接状态发生了改变

WIFI_P2P_THIS_DEVICE_CHANGED_ACTION

指示设备的详细配置发生了变化

1. private final IntentFilter intentFilter = new IntentFilter();
2. ...
3. @Override
4. public void onCreate(Bundle savedInstanceState) {
5. super.onCreate(savedInstanceState);
6. setContentView(R.layout.main);

8. //  Indicates a change in the Wi-Fi P2P status.
9. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);

11. // Indicates a change in the list of available peers.
12. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

14. // Indicates the state of Wi-Fi P2P connectivity has changed.
15. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

17. // Indicates this device's details have changed.
18. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

20. ...
21. }

在 onCreate() 方法的最后,需要获得 WifiPpManager 的实例,并调用它的 initialize() 方法。该方法将返回 WifiP2pManager.Channel 对象。 我们的应用将在后面使用该对象连接 Wi-Fi P2P 框架。

1. @Override

3. Channel mChannel;

5. public void onCreate(Bundle savedInstanceState) {
6. ....
7. mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
8. mChannel = mManager.initialize(this, getMainLooper(), null);
9. }

接下来,创建一个新的 BroadcastReceiver 类侦听系统中 Wi-Fi P2P 状态的变化。在 onReceive() 方法中,加入对上述四种不同 P2P 状态变化的处理。

1. @Override
2. public void onReceive(Context context, Intent intent) {
3. String action = intent.getAction();
4. if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
5. // Determine if Wifi P2P mode is enabled or not, alert
6. // the Activity.
7. int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
8. if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
9. activity.setIsWifiP2pEnabled(true);
10. } else {
11. activity.setIsWifiP2pEnabled(false);
12. }
13. } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

15. // The peer list has changed!  We should probably do something about
16. // that.

18. } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

20. // Connection state changed!  We should probably do something about
21. // that.

23. } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
24. DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()
25. .findFragmentById(R.id.frag_list);
26. fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(
27. WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));

29. }
30. }

最后,在主 activity 开启时,加入注册 intent filter 和 broadcast receiver 的代码,并在 activity 暂停或关闭时,注销它们。上述做法最好放在 onResume() 和 onPause() 方法中。

1. /** register the BroadcastReceiver with the intent values to be matched */
2. @Override
3. public void onResume() {
4. super.onResume();
5. receiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
6. registerReceiver(receiver, intentFilter);
7. }

9. @Override
10. public void onPause() {
11. super.onPause();
12. unregisterReceiver(receiver);
13. }

初始化对等节点发现(Peer Discovery)

调用 discoverPeers() 开始搜寻附近带有 Wi-Fi P2P 的设备。该方法需要以下参数:

1. mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {

3. @Override
4. public void onSuccess() {
5. // Code for when the discovery initiation is successful goes here.
6. // No services have actually been discovered yet, so this method
7. // can often be left blank.  Code for peer discovery goes in the
8. // onReceive method, detailed below.
9. }

11. @Override
12. public void onFailure(int reasonCode) {
13. // Code for when the discovery initiation fails goes here.
14. // Alert the user that something went wrong.
15. }
16. });

需要注意的是,这仅仅表示对Peer发现(Peer Discovery)完成初始化。discoverPeers() 方法开启了发现过程并且立即返回。系统会通过调用 WifiP2pManager.ActionListener 中的方法通知应用对等节点发现过程初始化是否正确。同时,对等节点发现过程本身仍然继续运行,直到一条连接或者一个 P2P 小组建立。

获取对等节点列表

在完成对等节点发现过程的初始化后,我们需要进一步获取附近的对等节点列表。第一步是实现 WifiP2pManager.PeerListListener 接口。该接口提供了 Wi-Fi P2P 框架发现的对等节点信息。下列代码实现了相应功能:

1. private List peers = new ArrayList();
2. ...

4. private PeerListListener peerListListener = new PeerListListener() {
5. @Override
6. public void onPeersAvailable(WifiP2pDeviceList peerList) {

8. // Out with the old, in with the new.
9. peers.clear();
10. peers.addAll(peerList.getDeviceList());

12. // If an AdapterView is backed by this data, notify it
13. // of the change.  For instance, if you have a ListView of available
14. // peers, trigger an update.
15. ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();
16. if (peers.size() == 0) {
17. Log.d(WiFiDirectActivity.TAG, "No devices found");
18. return;
19. }
20. }
21. }

接下来,完善 Broadcast Receiver 的 onReceiver() 方法。 当收到 WIFI_P2P_PEERS_CHANGED_ACTION 事件时, 调用 requestPeer() 方法获取对等节点列表。我们需要将 WifiP2pManager.PeerListListener 传递给 receiver。一种方法是在 broadcast receiver 的构造函数中,将对象作为参数传入。

1. public void onReceive(Context context, Intent intent) {
2. ...
3. else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

5. // Request available peers from the wifi p2p manager. This is an
6. // asynchronous call and the calling activity is notified with a
7. // callback on PeerListListener.onPeersAvailable()
8. if (mManager != null) {
9. mManager.requestPeers(mChannel, peerListListener);
10. }
11. Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
12. }...
13. }

现在,一个带有 WIFI_P2P_PEERS_CHANGED_ACTION action 的 intent 将触发应用对 Peer 列表的更新。

连接一个对等节点

为了连接到一个对等节点,我们需要创建一个新的 WifiP2pConfig 对象,并将要连接的设备信息从表示我们想要连接设备的 WifiP2pDevice 拷贝到其中。然后调用 connect() 方法。

1. @Override
2. public void connect() {
3. // Picking the first device found on the network.
4. WifiP2pDevice device = peers.get(0);

6. WifiP2pConfig config = new WifiP2pConfig();
7. config.deviceAddress = device.deviceAddress;
8. config.wps.setup = WpsInfo.PBC;

10. mManager.connect(mChannel, config, new ActionListener() {

12. @Override
13. public void onSuccess() {
14. // WiFiDirectBroadcastReceiver will notify us. Ignore for now.
15. }

17. @Override
18. public void onFailure(int reason) {
19. Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
20. Toast.LENGTH_SHORT).show();
21. }
22. });
23. }

在本段代码中的 WifiP2pManager.ActionListener 实现仅能通知我们初始化的成功或失败。想要监听连接状态的变化,需要实现 WifiP2pManager.ConnectionInfoListener 接口。接口中的 onConnectionInfoAvailable() 回调函数会在连接状态发生改变时通知应用程序。当有多个设备同时试图连接到一台设备时(例如多人游戏或者聊天群),这一台设备将被指定为“群主”(group owner)。

1. @Override
2. public void onConnectionInfoAvailable(final WifiP2pInfo info) {

4. // InetAddress from WifiP2pInfo struct.
5. InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress());

7. // After the group negotiation, we can determine the group owner.
8. if (info.groupFormed && info.isGroupOwner) {
9. // Do whatever tasks are specific to the group owner.
10. // One common case is creating a server thread and accepting
11. // incoming connections.
12. } else if (info.groupFormed) {
13. // The other device acts as the client. In this case,
14. // you'll want to create a client thread that connects to the group
15. // owner.
16. }
17. }

此时,回头继续完善 broadcast receiver 的 onReceive() 方法,并修改对 WIFI_P2P_CONNECTION_CHANGED_ACTION intent 的监听部分的代码。当接收到该 intent 时,调用 requestConnectionInfo() 方法。此方法为异步,所以结果将会被我们提供的 WifiP2pManager.ConnectionInfoListener 所获取。

1. ...
2. } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

4. if (mManager == null) {
5. return;
6. }

8. NetworkInfo networkInfo = (NetworkInfo) intent
9. .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

11. if (networkInfo.isConnected()) {

13. // We are connected with the other device, request connection
14. // info to find group owner IP

16. mManager.requestConnectionInfo(mChannel, connectionListener);
17. }
18. ...