Wi-Fi Direct™ 的API可以让app应用连接到其附近的设备,而无需借助连接到网络或者热点。app应用可以快速发现附近设备并与之交互,其距离范围超过了蓝牙。

本节课讲解用Wi-Fi Direct如何发现附近设备并与之连接。

设置Application Permissions


CHANGE_WIFI_STATEACCESS_WIFI_STATE 和INTERNET 权限添加到应用的manifest。Wi-Fi Direct不需要连接到互联网,但是其用到了标准的java socket,这需要 INTERNET

<manifest xmlns:android="http:///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并设置其监听如下:


WIFI_P2P_STATE_CHANGED_ACTION

表明Wi-Fi Peer-To-Peer (P2P) 是否可用 WIFI_P2P_PEERS_CHANGED_ACTION

表明可用peer列表已经发生改变 WIFI_P2P_CONNECTION_CHANGED_ACTION

表明Wi-Fi P2P 连接状态已经发生改变 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION

表明设备的配置细节已经发生改变

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

@Override

Channel mChannel;

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

现在创建一个新的BroadcastReceiver

@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(.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列表


现在编写获取和处理Peer列表的代码。首先实现WifiP2pManager.PeerListListener

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() 方法,以实现在收到带有 action WIFI_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

连接一个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()

@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);
            }
            ...