Android WifiDirect学习(一)
WiFi Direct基本介绍
Wi-Fi Direct标准允许无线网络中的设备无需通过无线路由器即可相互连接。与蓝牙技术类似,这种标准允许无线设备以点对点形式互连,不过在传输速度与传输距离方面则比蓝牙有大幅提升。
Wi-Fi Direct可以支持一对一直连,也可以实现多台设备同时连接
WiFiDirect 一对一 搜索/连接/传输基本流程
第一步:初始化WifiDirect模块,一般情况下,只要打开Wifi,WifiDirect就会处于激活状态
第二步:WifiDirect进行搜索状态,只有处于Wifidirect搜索状态的手机才能被其他手机搜索到。
第三步:连接搜索到的手机,建立连接。连接一旦建立,就会停止搜索。
第四步:在建立好连接的网络基础上进行网络传输(socket通信)
第五步:传输完毕后,可以根据需要继续进行传输或者断开连接
Android WiFi Direct
Android 4.0(API level 14)之后版本支持
Android WiFi Direct 基本使用方法
Android WiFi Direct API包含以下主要部分:
- 允许用户发现,请求然后连接对等设备的各种方法,定义在WifiP2pManager类中。
- 允许用户定义收到调用WifiP2pManager类中方法成功或失败的通知的监听器(ActionListener)。当用户调用WifiP2pManager类中的方法时,每一个方法都可以收到一个以参数形式传过来的特定监听。
- 通知用户被Wi-Fi直连技术框架检测到的特定事件的Intent,比如一个已丢掉的连接或者一个新的Peer的发现等。
Android WiFi Direct API 概述
WifiP2pManager类提供了很多方法允许用户通过设备的Wi-Fi模块来进行交互,比如做一些如发现,连接其他对等设备的事情。下列的方法都是可以使用的:
表格1.Wi-Fi直连技术方法
方法名 |
详细描述 |
initialize() |
通过Wi-Fi框架对应用来进行注册。这个方法必须在任何其他Wi-Fi直连方法使用之前调用。 |
connect()] |
开始一个拥有特定设置的设备的点对点连接。 |
cancelConnect() |
取消任何一个正在进行的点对点组的连接。 |
requestConnectInfo() |
获取一个设备的连接信息。 |
createGroup() |
以当前设备为组拥有者来创建一个点对点连接组。 |
removeGroup() |
移除当前的点对点连接组。 |
requestGroupInfo() |
获取点对点连接组的信息。 |
discoverPeers() |
初始化对等设备的发现。 |
requestPeers() |
获取当前发现的对等设备列表。 |
WifiP2pManager的方法可以让你在一个监听器(ActionListener)里传递参数,这样Wi-fi直连框架就可以通知给你的窗体这个方法调用的状态。
可以被使用的监听器接口和使用监听器的相应的WifiP2pManager的方法的调用都将在下面这张表中有所描述:
表格2. Wi-Fi直连监听器方法
监听器接口 |
相关联的方法 |
WifiP2pManager.ActionListener |
connect(), cancelConnect(), createGroup(), removeGroup(), and discoverPeers() |
WifiP2pManager.ChannelListener |
initialize() |
WifiP2pManager.ConnectionInfoListener |
requestConnectInfo() |
WifiP2pManager.GroupInfoListener |
requestGroupInfo() |
WifiP2pManager.PeerListListener |
requestPeers() |
Wi-Fi直连技术的API定义了一些当特定的Wi-Fi直连事件发生时作为广播的Intent,比如说当一个新的Peer被发现,或者一个Peer的Wi-Fi状态的改变。你可以在你的应用里通过创建一个处理这些Intent的BroadcastReceiver来注册去接收这些Intent。
Table 3. Wi-Fi 直连意图
意图名称 |
详细描述 |
WIFI_P2P_CONNECTION_CHANGED_ACTION |
当设备的Wi-Fi连接信息状态改变时候进行广播。 |
WIFI_P2P_PEERS_CHANGED_ACTION |
当调用discoverPeers()方法的时候进行广播。在你的应用里处理此意图时,你通常会调用requestPeers()去获得对等设备列表的更新。 |
WIFI_P2P_STATE_CHANGED_ACTION |
当设备的Wi-Fi 直连功能打开或关闭时进行广播。 |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION |
当设备的详细信息改变的时候进行广播,比如设备的名称 |
创建一个Wi-Fi直连的应用
创建一个Wi-Fi直连的应用包括创建和注册一个BroadcastReceiver,发现其他设备,连接其他设备,然后传输数据等步骤。接下来的几个部分描述了怎么去做这些工作。
初始化设置
在使用Wi-Fi直连的API之前,你必须确保你的应用可以访问设备的硬件并且你的设备要支持Wi-Fi直连的通讯协议。如果Wi-Fi直连技术是支持的,你可以获得一个WifiP2pManager的实例对象,然后创建并注册你的BroadcastReceiver,然后开始使用WiFiDirect的API方法。
1.为设备的Wi-Fi硬件获取权限并在Android的清单文件中声明你的应用正确使用的最低SDK版本:
1 <uses-sdk android:minSdkVersion="14" /> 2 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> 3 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> 4 <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> 5 <uses-permission android:name="android.permission.INTERNET" /> 6 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.检查设备是否支持Wi-Fi直连技术。一种好的解决办法是当你的BrocastReceiver接收到一个WIFI_P2P_STATE_CHANGED_ACTION Intent。通知你的Activity Wi-Fi直连的状态和相应的反应。
1 @Override 2 public void onReceive(Context context, Intent intent) { 3 ... 4 String action = intent.getAction(); 5 if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { 6 int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); 7 if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { 8 // Wifi Direct is enabled 9 } else { 10 // Wi-Fi Direct is not enabled 11 } 12 } 13 ... 14 }
3.在你的窗体的onCreate()方法里,获得一个WifiP2pManager的实例并调用initialize()方法通过WiFiDirect框架去注册你的应用。这个方法返回一个WifiP2pManager.Channel对象,是被用来连接你的应用和WiFiDirect框架的。你应该再创建一个以WifiP2pManager和WifiP2pManager.Channel为参数且关联你的Activity的BroadcastRecevier的实例。这样你的BroadcastRecevier就可以接收到你感兴趣的事件去通知你的Activity并更新它。它还可以让你在需要的时候操纵设备的Wi-Fi状态。
1 WifiP2pManager mManager; 2 Channel mChannel; 3 BroadcastReceiver mReceiver; 4 ... 5 @Override 6 protected void onCreate(Bundle savedInstanceState){ 7 ... 8 mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); 9 mChannel = mManager.initialize(this, getMainLooper(), null); 10 mReceiver = new WiFiDirectBroadcastReceiver(manager, channel, this); 11 ... 12 }
4.创建一个IntentFilter并把它添加在你的BroadcastReceiver需要处理的Intent上。
1 IntentFilter mIntentFilter; 2 ... 3 @Override 4 protected void onCreate(Bundle savedInstanceState){ 5 ... 6 mIntentFilter = new IntentFilter(); 7 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 8 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 9 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 10 mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); 11 ... 12 }
5.注册你的BroadcastReceiver在Acitivity的onResume()方法,解除注册在onPause()方法中。(BroadcastReceiver的register和unregister根据需要可放在不同的位置)
1 /* register the broadcast receiver with the intent values to be matched */ 2 @Override 3 protected void onResume() { 4 super.onResume(); 5 registerReceiver(mReceiver, mIntentFilter); 6 } 7 /* unregister the broadcast receiver */ 8 @Override 9 protected void onPause() { 10 super.onPause(); 11 unregisterReceiver(mReceiver); 12 }
当你获取到一个WifiP2pManager.Channel对象并且设置好你的BroadcastReceiver时,你的应用就可以调用Wi-Fi直连的方法并且可以接收Wi-Fi直连的Intent。
你可以现在就通过调用WifiP2pManager中的方法取实现你的应用体验Wi-Fi直连技术的特性了。
下面的章节描述了怎样去实现一些常用的操作,比如说发现其他设备(搜索)和连接它们。
发现对等设备
要发现可以使用并连接的对等设备,调用discoverPeers()方法去检测在范围内的可使用设备。这个方法的调用是异步的同时如果你创建了一个WifiP2pManager.ActionListener监听器的话你会通过onSuccess()或者onFailure()方法收到发现成功或失败的消息。onSuccess()方法只能通知你发现的过程是否成功而不能提供任何关于发现设备的信息:
1 manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { 2 @Override 3 public void onSuccess() { 4 ... 5 } 6 7 @Override 8 public void onFailure(int reasonCode) { 9 ... 10 } 11 });
如果发现过程成功且检测到了对等设备,系统将会广播出一个WIFI_P2P_PEERS_CHANGED_ACTION Intent,这样你就可以利用BroadcastReceiver监听并获得发现设备的列表。当你的应用接收到WIFI_P2P_PEERS_CHANGED_ACTION Intent时,你就可以调用requestPeers()方法来获取发现设备的列表,代码如下:
1 PeerListListener myPeerListListener; 2 ... 3 if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { 4 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 (manager != null) { 9 manager.requestPeers(channel, myPeerListListener); 10 } 11 }
连接到设备
当你已经找到你要连接的设备在获得发现设备列表之后,调用connect()方法去连接指定设备。这个方法的调用需要一个包含待连接设备信息的WifiP2pConfig对象。你可以通过WifiP2pManager.ActionListener接收到连接是否成功的通知。
下面的代码展示了怎样去连接一个想得到的连接:
1 //obtain a peer from the WifiP2pDeviceList 2 WifiP2pDevice device; 3 WifiP2pConfig config = new WifiP2pConfig(); 4 config.deviceAddress = device.deviceAddress; 5 manager.connect(channel, config, new ActionListener() { 6 7 @Override 8 public void onSuccess() { 9 //success logic 10 } 11 12 @Override 13 public void onFailure(int reason) { 14 //failure logic 15 } 16 });
数据传输
WiFiDirect连接成功后,可以收到系统广播WIFI_P2P_CONNECTION_CHANGED_ACTION,此时调用requestConnectionInfo,会异步回调方法onConnectionInfoAvailable(final WifiP2pInfo info)。在WifiP2pInfo中,我们可以得知isGroupOwner(是不是GO),groupOwnerAddress(GO的IP地址是多少)。
此处注意:GC可以知道GO的地址,而GO是不知道GC的地址的。因此,一般的Socket编程思路是,GO做Server端,GC做Client端。
一旦连接已经建立,你可以通过Socket来进行数据的传输。基本的数据传输步骤如下(google官方例子,也可以根据需要使用适用Socket通讯的其他方法协议,如HTTP,FTP协议):
1.创建一个ServerSocket对象。这个服务端Socket对象等待一个来自指定地址和端口的客户端的连接且阻塞线程直到连接发生,所以把它建立在一个后台线程里。
2.创建一个客户端Socket.这个客户端Socket对象使用指定ip地址和端口去连接服务端设备。(IP地址从上面的groupOwnerAddress获得,端口号由server和client两端通讯之前确定)
3.从客户端给服务端发送数据。当客户端成功连接服务端设备后,你可以通过字节流从客户端给服务端发送数据。
4.服务端等待客户端的连接(使用accept()方法)。这个调用阻塞服务端线程直到客户端连接上,所以叫这个过程一个新的线程。当连接建立时,服务端可以接受来自客户端的数据。执行关于数据的任何动作,比如保存数据或者展示给用户。
下来的例子,展示了怎样去创建服务端和客户端的连接和通信,并且使用一个客户端到服务端的服务来传输了一张JPEG图像。如。
1 public static class FileServerAsyncTask extends AsyncTask { 2 3 private Context context; 4 private TextView statusText; 5 6 public FileServerAsyncTask(Context context, View statusText) { 7 this.context = context; 8 this.statusText = (TextView) statusText; 9 } 10 11 @Override 12 protected String doInBackground(Void... params) { 13 try { 14 15 /** 16 * Create a server socket and wait for client connections. This 17 * call blocks until a connection is accepted from a client 18 */ 19 ServerSocket serverSocket = new ServerSocket(8888); 20 Socket client = serverSocket.accept(); 21 22 /** 23 * If this code is reached, a client has connected and transferred data 24 * Save the input stream from the client as a JPEG file 25 */ 26 final File f = new File(Environment.getExternalStorageDirectory() + "/" 27 + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis() 28 + ".jpg"); 29 30 File dirs = new File(f.getParent()); 31 if (!dirs.exists()) 32 dirs.mkdirs(); 33 f.createNewFile(); 34 InputStream inputstream = client.getInputStream(); 35 copyFile(inputstream, new FileOutputStream(f)); 36 serverSocket.close(); 37 return f.getAbsolutePath(); 38 } catch (IOException e) { 39 Log.e(WiFiDirectActivity.TAG, e.getMessage()); 40 return null; 41 } 42 } 43 44 /** 45 * Start activity that can handle the JPEG image 46 */ 47 @Override 48 protected void onPostExecute(String result) { 49 if (result != null) { 50 statusText.setText("File copied - " + result); 51 Intent intent = new Intent(); 52 intent.setAction(android.content.Intent.ACTION_VIEW); 53 intent.setDataAndType(Uri.parse("file://" + result), "image/*"); 54 context.startActivity(intent); 55 } 56 } 57 }
在客户端,使用客户端套接字连接服务端套接字并传输数据。这个例子从客户端的文件系统里传输了一张JPEG的图像到服务端。
1 Context context = this.getApplicationContext(); 2 String host; 3 int port; 4 int len; 5 Socket socket = new Socket(); 6 byte buf[] = new byte[1024]; 7 ... 8 try { 9 /** 10 * Create a client socket with the host, 11 * port, and timeout information. 12 */ 13 socket.bind(null); 14 socket.connect((new InetSocketAddress(host, port)), 500); 15 16 /** 17 * Create a byte stream from a JPEG file and pipe it to the output stream 18 * of the socket. This data will be retrieved by the server device. 19 */ 20 OutputStream outputStream = socket.getOutputStream(); 21 ContentResolver cr = context.getContentResolver(); 22 InputStream inputStream = null; 23 inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg")); 24 while ((len = inputStream.read(buf)) != -1) { 25 outputStream.write(buf, 0, len); 26 } 27 outputStream.close(); 28 inputStream.close(); 29 } catch (FileNotFoundException e) { 30 //catch logic 31 } catch (IOException e) { 32 //catch logic 33 } 34 35 /** 36 * Clean up any open sockets when done 37 * transferring or if an exception occurred. 38 */ 39 finally { 40 if (socket != null) { 41 if (socket.isConnected()) { 42 try { 43 socket.close(); 44 } catch (IOException e) { 45 //catch logic 46 } 47 } 48 } 49 }
参考文章