Android网络通信(8):WiFi Direct

摘要:
用于Android网络通信的WiFi Direct使用Wi-Fi Direct技术,使具有硬件支持的设备无需中间接入点即可直接连接。Android 4.0和更高版本的系统为Wi-FiDirect提供API支持。通过使用这些API,开发者可以实现支持Wi-FiDirect的设备之间的相互检测和连接,从而在比蓝牙更远的距离实现高速数据通信效果。}在活动的onCreate()方法中获取WifiP2pManager对象的实例,并通过对象的initialize()方法向Wi-FiDirect系统注册当前应用程序。成功注册后,将返回WifiP2pManager.Channel,应用程序可以通过该通道与Wi-FiDirect系统交互。

Android网络通信之WiFi Direct

使用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意向。

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

创建Wi-Fi Direct应用:
完整的Wi-Fi Direct应用包含创建并注册广播接收器、检测对等设备、连接对等设备以及在对等设备间传输数据几个方面的功能。下面将详细介绍如何实现。
准备工作:在使用Wi-Fi Direct API之前,首先要确保应用程序能够访问硬件,并且设备支持Wi-Fi Direct协议。如果这些条件都满足,就可以获取一个WifiP2pManager实例,创建并注册广播接收器,最后就是使用Wi-Fi Direct API了。
在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(mManager, mChannel, 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 
            } 
        } 
    } 

官方参考链接:http://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager.html

参考链接:http://blog.csdn.net/yichigo/article/details/8472570

代码下载链接:http://download.csdn.net/detail/klcf0220/5894381
http://www.apkbus.com/android-137269-1-1.html

免责声明:文章转载自《Android网络通信(8):WiFi Direct》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇eslint 禁用命令NodeManager介绍下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

C++实现ftp客户端

#ifndef CLIENT_H_ #define CLIENT_H_ #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include &...

jmeter之如何减负-实现稳定超高并发测试(性能调优)之正确添加监听器

jmeter之如何减负-实现稳定超高并发测试(性能调优)在测试过程中,初学者使用工具不当,添加众多监控组件,非常想看到实时报告,跑不了一会,jmeter就卡死,只得重启 下面来总结下如何正确使用jmeter,有效利用执行资源,小型机器也可以实现高并发负载。 减负一: 优化监听(GUI模式)“查看结果树”,需要勾选“仅日志错误”,这样只会保存错误日志到内存,...

SpringBoot启动过程中涉及到了扩展接口

SpringApplicationRunListener接口 1、ApplicationListener接口 是ApplicationContext的事件监听器 2、EnvironmentPostProcessor接口 上下文环境后置处理器,事件中调用 3、PropertySourceLoader接口 自定义配置文件加载器,自己解析配置文件属性...

TCP与虚连接

http://bbs.csdn.net/topics/390262738 在TCP通信时,会建立一个从源端到目的端的虚拟连接。感觉这种连接类似电路交换,只是这种连接是虚拟存在的。发送的报文都应该是沿着这条虚拟链路进行传播,不存在分组会独立寻址的问题。但是IP层向下传输时,每个IP报文段都包含了目的端口的信息,即这样的话,每个分组会独立寻址啊。 引用2楼的...

【转】高性能网络编程4--TCP连接的关闭

TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们分别做了些什么。 为方便阅读,我们可以带着以下5个问题来阅读本文: 1、当socket被多进程或者多线程共享时,关闭连接时有何区别? 2、关连接时,若连接上有来自对端的还未处理的消息,会怎么处理? 3、关连接时,若连接上有本进程待发送却未来得及发送出的消息,又会怎么处理?...

TCP建立连接时socket的epoll态及一个可能的状态不一致问题

零、原因 其实本来是在看TCP三次握手时客户端和服务器端socket对于epoll状态何时返回何种状态,不过后来引出了一个另有意思的问题:就是客户端和服务器双方对于三次握手的状态出现了不一致。我们知道,在三次握手中,客户端在发送最后一个ack之后进入ESTABLISHED状态,并没有要求服务器对于这个ACK再次ACK(当然也没有办法要求ACK,否则这样就是...