Android Wi-Fi Direct 开发指南

(本文为Android官方开发文档译文) 

使用Wi-Fi Direct技术可以让具备硬件支持的设备在没有中间接入点的情况下进行直接互联。Android 4.0(API版本14)及以后的系统都提供了对Wi-Fi Direct的API支持。通过对这些API的使用,开发者可以实现支持Wi-Fi Direct的设备间进行相互探测和连接,从而获得较之蓝牙更远距离的高速数据通信效果。对于诸如多人游戏、图片共享等需要在用户之间传输数据的应用而言,这一技术无疑是十分有价值的。

关于Wi-Fi Direct的API函数的使用需要注意一下几个要点:

·用于探测(discover)对等设备(peers)、向对等设备发起请求(request)以及建立连接(connect)的方法定义在类WifiP2pManager中。

·通过设置监听器(Listener)可以获知WifiP2pManager中方法调用的成功与否。监听器以参数的形式传递给被调用的方法。

·当发现新对等设备或链接丢失的时候,Wi-Fi Direct系统(framework)以意向(Intent)的方式根据检测到的不同事件做出相应的通知。

开发中,以上三点的配合使用相当普遍。简单举个例子,定义一个监听器WifiP2pManager.ActionListener并调用函数discoverPeers(),当相应事件发生的时候就会在ActionListener.onSuccess()和ActionListener.onFailure()两个方法中得到通知。当discoverPeers()方法检测到了对等设备列表变化的时候,可以收到由系统广播(broadcast)发出一个WIFI_P2P_PEERS_CHANGED_ACTION意向。

API概述(API Overview)




WifiP2pManager类所提供的方法可用于操作当前设备中的Wi-Fi硬件,实现诸如探测对、连接对等设备等功能。目前所支持的功能如下:

表 1.Wi-Fi Direct Methods

Method

Description

initialize()

Registers the application with the Wi-Fi framework. This must be called before calling any other Wi-Fi Direct method.

connect()

Starts a peer-to-peer connection with a device with the specified configuration.

cancelConnect()

Cancels any ongoing peer-to-peer group negotiation.

requestConnectInfo()

Requests a device's connection information.

createGroup()

Creates a peer-to-peer group with the current device as the group owner.

removeGroup()

Removes the current peer-to-peer group.

requestGroupInfo()

Requests peer-to-peer group information.

discoverPeers()

Initiates peer discovery

requestPeers()

Requests the current list of discovered peers.

WifiP2pManager中所提供的方法允许特定的监听器作为参数传入,以便Wi-Fi Direct机制能够汇报函数调用的结果。下表中列出了目前支持的监听器接口以及WifiP2pManager中用到相应监听器的方法。

表 2. Wi-Fi Direct Listeners

Listener interface

Associated actions

WifiP2pManager.ActionListener

connect(),cancelConnect(),createGroup(),removeGroup(), and discoverPeers()

WifiP2pManager.ChannelListener

initialize()

WifiP2pManager.ConnectionInfoListener

requestConnectInfo()

WifiP2pManager.GroupInfoListener

requestGroupInfo()

WifiP2pManager.PeerListListener

requestPeers()

每当有Wi-Fi Direct事件发生的时候(例如,发现新的对等设备、设备的Wi-Fi状态改变等),Wi-Fi Direct API会以广播的形式发出一个意向。而在应用程序中需要做的事情就是创建广播接收器(creating a broadcast receiver)来处理这些意向:

表 3. Wi-Fi Direct Intents

Intent

Description

WIFI_P2P_CONNECTION_CHANGED_ACTION

Broadcast when the state of the device's Wi-Fi connection changes.

WIFI_P2P_PEERS_CHANGED_ACTION

Broadcast when you calldiscoverPeers(). You usually want to call requestPeers() to get an updated list of peers if you handle this intent in your application.

WIFI_P2P_STATE_CHANGED_ACTION

Broadcast when Wi-Fi Direct is enabled or disabled on the device.

WIFI_P2P_THIS_DEVICE_CHANGED_ACTION

Broadcast when a device's details have changed, such as the device's name.

创建广播接收器以处理Wi-Fi Direct意向(Creating a Broadcast Receiver for Wi-Fi Direct Intents)


广播接收器可以让应用程序接收到Android系统所发出的广播意向。这样,应用程序就能对感兴趣的事件做出响应。创建广播接收器的基本步骤如下:

  1. 创建一个继承BroadcastReceiver类的新类。构造函数的参数分别传递WifiP2pManager,WifiP2pManager.Channel,以及在这个广播接收器中需要注册的活动(activity)。这是一种最常见的参数设置模式,它让广播接收器能够引起活动作出更新,同时又能在必要时使用Wi-Fi硬件和通信信道。
  2. 在广播接收器的onReceive()函数中,针对感兴趣的特定意向可以执行相应的动作(actions)。例如,当广播接收器收到了意向WIFI_P2P_PEERS_CHANGED_ACTION,就可以调用requestPeers()方法来列举出当前探测到的对等设备。

下面的代码将展示了如何创建一个特定的广播接收器。例子中的广播接收器以WifiP2pManager对象和一个活动(activity)作为参数,并使用它们针对收到的意向(intent)做出相应的动作(action):

/**
 * A BroadcastReceiver that notifies of important Wi-Fi p2p events.
 */
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

    private WifiP2pManager manager;
    private Channel channel;
    private MyWiFiActivity activity;

    public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel,
            MyWifiActivity activity) {
        super();
        this.manager = manager;
        this.channel = channel;
        this.activity = activity;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
            // Check to see if Wi-Fi is enabled and notify appropriate activity
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // Call WifiP2pManager.requestPeers() to get a list of current peers
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
            // Respond to new connection or disconnections
        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            // Respond to this device's wifi state changing
        }
    }
}

 

创建Wi-Fi Direct应用(Creating a Wi-Fi Direct Application)


完整的Wi-Fi Direct应用包含创建并注册广播接收器、检测对等设备、连接对等设备以及在对等设备间传输数据几个方面的功能。下面将详细介绍如何实现。

准备工作(Initial setup)

在使用Wi-Fi Direct API之前,首先要确保应用程序能够访问硬件,并且设备支持Wi-Fi Direct协议。如果这些条件都满足,就可以获取一个WifiP2pManager实例,创建并注册广播接收器,最后就是使用Wi-Fi Direct API了。

  1. 在Android manifest文件中加入以下内容,允许使用Wi-Fi设备上的硬件并声明应用程序正确支持了调用API所需的最低SDK版本

<uses-sdk android:minSdkVersion="14" />

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

检查Wi-Fi Direct支持并已开启。推荐在广播接收器收到WIFI_P2P_STATE_CHANGED_ACTION意向的时候进行检测。检测结果需要通告相应的活动并做出处理:

@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 Direct is enabled

        } else {

            // Wi-Fi Direct is not enabled

        }

    }

    ...

}

在活动的onCreate()方法中获取WifiP2pManager对象的一个实例,通过该对象的initialize()方法向Wi-Fi Direct系统注册当前的应用程序。注册成功后,会返回一个WifiP2pManager.Channel,通过它,应用程序就能和Wi-Fi Direct系统交互。WifiP2pManager和WifiP2pManager.Channel对象以及一个活动的引用最后都被作为参数传递给自定义的广播接收器。这样,该活动就能够响应广播接收器的通知并作出相应的更新。当然,这样做也使程序具备了操纵设备Wi-Fi状态的能力:

WifiP2pManager mManager;

Channel mChannel;

BroadcastReceiver mReceiver;

...

@Override

protected void onCreate(Bundle savedInstanceState){

    ...

    mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);

    mChannel = mManager.initialize(this, getMainLooper(), null);

    mReceiver = new WiFiDirectBroadcastReceiver(manager, channel, this);

    ...

}

创建一个意向过滤器(intent filter),其中添加的意向种类和广播接收器中的保持一致:

IntentFilter mIntentFilter;

...

@Override

protected void onCreate(Bundle savedInstanceState){

    ...

    mIntentFilter = new IntentFilter();

    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);

    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

    ...

}

在活动的onResume()方法中注册广播接收器,并在活动的onPause()方法中注销它:

/* register the broadcast receiver with the intent values to be matched */
@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mReceiver, mIntentFilter);
}
/* unregister the broadcast receiver */
@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mReceiver);
}

一旦成功获取WifiP2pManager.Channel并创建了广播接收器,应用程序就已经具备了使用Wi-Fi Direct相关函数和接收Wi-Fi Direct意向的能力。尽管放手使用WifiP2pManager为你提供的方法,让程序也拥有Wi-Fi Direct的特殊能力吧!

下一节将讲述如何实现一些常见的动作,例如探测并连接到对等设备。

 

探测对等设备(Discovering peers)

调用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意向。程序接收到该意向后,通过调用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()方法同样是一个异步函数,当它准备好一份对等设备列表的时候,就会通知监听器WifiP2pManager.PeerListListener中定义的onPeersAvailable()方法。而onPeersAvailable()方法中所能获取到的对等设备列表以WifiP2pDeviceList形式存储,通过遍历这个列表可以选择出希望连接的设备。

连接对等设备(Connecting to peers)

确定了要连接的设备,还需调用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
    }
});

传输数据(Transferring data)

连接一旦建立成功,数据传输也就是顺理成章的事情。以下是通过socket发送数据的基本步骤:

  1. 创建ServerSocket。它将被用于监听特定端口,等待客户端发起的连接请求。该操作需要在后台线程中实现。
  2. 创建客户端Socket。客户端通过ServerSocket对应的IP和端口连接到服务设备。
  3. 客户端向服务器发生数据。客户socket成功连接到服务socket后,就能以字节流的形式向服务器发生数据了。
  4. 服务器socket通过accept()方法等待客户端数据连接的到来。该方法在收到客户端数据之前一直处于阻塞状态。因此,需要在单独的线程中调用它。数据连接一旦建立,服务设备就能接收到客户端的数据。这时要做的就是施以相应的动作,例如将数据保存到文件,或者是直接显示到用户界面上,等等。

以下代码修改自SDK自带的示例Wi-Fi Direct Demo。它演示了如何建立一对客户端-服务器连接,并由客户端向服务器发送JPEG图片。若需完整的演示工程,只需编译并运行SDK示例Wi-Fi Direct Demo即可。

public static class FileServerAsyncTask extends AsyncTask {

 

    private Context context;

    private TextView statusText;

 

    public FileServerAsyncTask(Context context, View statusText) {

        this.context = context;

        this.statusText = (TextView) statusText;

    }

 

    @Override

    protected String doInBackground(Void... params) {

        try {

 

            /**

             * Create a server socket and wait for client connections. This

             * call blocks until a connection is accepted from a client

             */

            ServerSocket serverSocket = new ServerSocket(8888);

            Socket client = serverSocket.accept();

 

            /**

             * If this code is reached, a client has connected and transferred data

             * Save the input stream from the client as a JPEG file

             */

            final File f = new File(Environment.getExternalStorageDirectory() + "/"

                    + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis()

                    + ".jpg");

 

            File dirs = new File(f.getParent());

            if (!dirs.exists())

                dirs.mkdirs();

            f.createNewFile();

            InputStream inputstream = client.getInputStream();

            copyFile(inputstream, new FileOutputStream(f));

            serverSocket.close();

            return f.getAbsolutePath();

        } catch (IOException e) {

            Log.e(WiFiDirectActivity.TAG, e.getMessage());

            return null;

        }

    }

 

    /**

     * Start activity that can handle the JPEG image

     */

    @Override

    protected void onPostExecute(String result) {

        if (result != null) {

            statusText.setText("File copied - " + result);

            Intent intent = new Intent();

            intent.setAction(android.content.Intent.ACTION_VIEW);

            intent.setDataAndType(Uri.parse("file://" + result), "image/*");

            context.startActivity(intent);

        }

    }

}

On the client, connect to the server socket with a client socket and transfer data. This example transfers a JPEG file on the client device's file system.

Context context = this.getApplicationContext();

String host;

int port;

int len;

Socket socket = new Socket();

byte buf[]  = new byte[1024];

...

try {

    /**

     * Create a client socket with the host,

     * port, and timeout information.

     */

    socket.bind(null);

    socket.connect((new InetSocketAddress(host, port)), 500);

 

    /**

     * Create a byte stream from a JPEG file and pipe it to the output stream

     * of the socket. This data will be retrieved by the server device.

     */

    OutputStream outputStream = socket.getOutputStream();

    ContentResolver cr = context.getContentResolver();

    InputStream inputStream = null;

    inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"));

    while ((len = inputStream.read(buf)) != -1) {

        outputStream.write(buf, 0, len);

    }

    outputStream.close();

    inputStream.close();

} catch (FileNotFoundException e) {

    //catch logic

} catch (IOException e) {

    //catch logic

}

 

/**

 * Clean up any open sockets when done

 * transferring or if an exception occurred.

 */

finally {

    if (socket != null) {

        if (socket.isConnected()) {

            try {

                socket.close();

            } catch (IOException e) {

                //catch logic

            }

        }

    }

}

 

示例代码下载:

以上示例代码来源于android sdk中自带的例子程序