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 }
參考文章

![clip_image002[6] clip_image002[6]](/image/aHR0cHM6Ly9pbWFnZXMwLmNuYmxvZ3MuY29tL2Jsb2cvNTc2MTk1LzIwMTUwNy8xMzA5NDM0NTU0ODcxNzcuZ2lm.png)