概述:
Wi-Fi peer-to-peer(P2P)讓Android4.0及之後版本的設備可以使用合適的硬件來通過WiFi直接連接對方而不用中間訪問點. 使用這些API, 我們可以發現和連接其它支持WiFi P2P的設備, 並可以跨越比藍牙遠的多的距離高速通信. 這對那些需要在用戶間共享數據的APP很有用, 比如一個多用戶遊戲或者照片分享APP. WiFi P2P包含這些主要的部分:
l 可以讓我們發現, 請求和連接的定義在WifiP2pManager類中的方法.
l 可以提醒我們WifiP2pManager方法調用成功或者失敗的監聽器. 當調用WifiP2pManager中的方法, 每個方法都傳遞收到一個指定的listener作爲參數.
l 當WiFi P2Pframework檢測到指定事件的時候通過intent提醒我們. 比如丟失連接或者發現了新的目標.
我們會經常一起使用這三個組件. 慄如, 可以提供一個WifiP2pManager.ActionListener給discoverPeers(), 這樣就可以得到ActionListener.onSuccess()和ActionListener.onFailure()的提醒. 當discoverPeers()發現peer列表發生變化的時候會發出一個WIFI_P2P_PEERS_CHANGED_ACTION的intent廣播.
API總覽:
WifiP2pManager類提供了很多方法允許我們使用WiFi硬件來做比如發現和連接到其它設備的操作. 我們可以做這些事兒:
方法 |
描述 |
initialize() |
註冊APP到WiFi framework. 必須先於任何WiFi方法執行. |
connect() |
啓動一個與指定配置的設備的P2P連接. |
cancelConnect() |
取消任何正在進行的P2P連接. |
requestConnectInfo() |
請求設備的連接信息. |
createGroup() |
通過當前設備作爲一個組的擁有者創建一個P2P組. |
removeGroup() |
移除當前的P2P組. |
requestGroupInfo() |
請求P2P組信息. |
discoverPeers() |
初始化peer發現. |
requestPeers() |
請求當前發現的peer的列表. |
WifiP2pManager中的方法可以接收監聽器, 這樣WiFi P2Pframework可以提醒我們的activity狀態變化. 可用的監聽器接口和相應的WifiP2pManager方法可以參考下表:
監聽器接口 |
關聯方法 |
WifiP2pManager.ActionListener |
Connect(), cancelConnect(), createGroup(), removeGroup(), discoverPeers() |
WifiP2pManager.ChannelListener |
Initialize() |
WifiP2pManager.ConnectionInfoListener |
requestConnectInfo() |
WifiP2pManager.GroupInfoListener |
requestGroupInfo() |
WifiP2pManager.PeerListListener |
requestPeers() |
WiFi P2P API還定義了一些廣播的intent, 在WiFi P2P事件發生的時候會發送廣播,比如當一個新的peer被發現或者設備的WiFi狀態發生變化的時候. 我們可以註冊接收這些intent來處理這些事件:
Intent |
Description |
WIFI_P2P_CONNECTION_CHANGED_ACTION |
當設備WiFi連接狀態改變的時候廣播. |
WIFI_P2P_PEERS_CHANGED_ACTION |
當我們調用discoverPeers()時候廣播. 如果我們處理了這個intent, 那麼可能會經常調用requestPeers()來獲得peer的列表更新peer的list. |
WIFI_P2P_STATE_CHANGED_ACTION |
WiFi P2P啓動或者關閉的時候廣播. |
WIFI_P2P_ THIS_DEVICE_CHANGED_ACTION |
當一個設備的詳細信息發生變化的時候會廣播, 比如設備的名字. |
爲WiFi P2P Intent創建一個Broadcast Receiver:
Broadcast receiver讓我們可以接收Androidframework發出的廣播, 這樣我們的APP就可以響應我們感興趣的事件了. 創建它的基礎步驟是這樣的:
1. 創建一個類, 繼承自BroadcastReceiver類. 我們最常爲其構造方法指定的參數是WifiP2pManager和WifiP2pManager.Channel. 這讓broadcast receiver可以給activity發送更新, 就好像訪問了WiFi硬件和通信通道一樣.
2. 在broadcast receiver的onReceive()方法中檢查感興趣的intent. 比如如果broadcast receiver收到了一個WIFI_P2P_PEERS_CHANGED_ACTION intent, 我們就可以調用requestPeers()方法來得到當前發現的peer的列表.
下面的代碼展示瞭如何創建一個典型的broadcast receiver. 它接收一個WifiP2pManager對象和一個activity作爲參數, 並使用這倆類來處理感興趣的intent:
/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
public class
WiFiDirectBroadcastReceiver
extends BroadcastReceiver
{
private WifiP2pManager mManager;
private Channel mChannel;
private MyWiFiActivity mActivity;
public WiFiDirectBroadcastReceiver(WifiP2pManager manager,
Channel channel,
MyWifiActivity activity)
{
super();
this.mManager
= manager;
this.mChannel
= channel;
this.mActivity
= 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 andnotify appropriate activity
} else
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action))
{
// Call WifiP2pManager.requestPeers() toget a list of current peers
} else
if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action))
{
// Respond to new connection ordisconnections
} else
if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action))
{
// Respond to this device's wifi statechanging
}
}
}
創建一個WiFi P2P的APP:
創建這樣一個APP涉及創建和註冊一個broadcast receiver, 發現peer, 連接到一個peer, 還有交換數據. 下面我們挨個討論它們.
初始化設置:
在使用WiFi P2P的API之前, 我們必須確保APP可以訪問硬件並且設備支持WiFi P2P協議. 如果WiFi P2P可以支持, 我們可以獲取一個WifiP2pManager的實例, 創建和註冊我們的broadcast receiver, 並開始使用WiFi P2P API.
1. 請求使用WiFi硬件的使用權限, 聲明APP的最小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" />
2. 檢查WiFi P2P是否支持並開啓. 一個檢查該功能的好地方是在broadcast receiver收到WIFI_P2P_STATE_CHANGED_ACTION intent的時候. 提醒activityWiFiP2P的狀態並作出相應的反應:
@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 P2P is enabled
} else {
// Wi-Fi P2P is not enabled
}
}
...
}
3. 在activity的onCreate()方法中, 獲取一個WifiP2pManager的實例並通過initialize()方法註冊WiFi P2P framework. 該方法返回一個WifiP2pManager.Channel, 它用來連接我們的APP與WiFi P2Pframework. 我們還應該用WifiP2pManager和WifiP2pManager.Channel創建一個broadcast receiver的實例. 這樣就可以收到感興趣的事件了, 還可以操作WiFi的狀態(如果需要的話):
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);
...
}
4. 創建一個intent filter並增加感興趣的intent:
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);
...
}
5. 在onResume()方法中註冊broadcast receiver, 並在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並設置了broadcast receiver, APP就可以調用WiFi P2P的方法和接收WiFiP2P的intent了. 現在就可以實現APP並使用WiFi P2P的功能了.
發現peer:
想要發現可以連接的peer, 調用discoverPeers()方法來檢查範圍內可用的peer. 該方法是異步的, 如果我們創建了WifiP2pManager.ActionListener的話它的結果將會通過onSuccess()和onFailure()方法返回. onSuccess()方法只會提醒我們發現操作處理成功, 而不提供任何關於發現的設備的信息(如果有的話):
mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
...
}
@Override
public void onFailure(int reasonCode) {
...
}
});
如果發現操作成功了並檢查到了peer, 系統將會廣播一個WIFI_P2P_PEERS_CHANGED_ACTION, 我們可以通過監聽它來得到一個peer的列表. 當APP收到這個intent的時候, 我們可以用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 (mManager != null) {
mManager.requestPeers(mChannel, myPeerListListener);
}
}
RequestPeers()方法也是異步的, 如果它發現了列表可用, 將會在onPeersAvailable()方法中提醒我們, 想要實現這個功能需要實現WifiP2pManager.PeerListListener接口. onPeersAvailable()方法提供給我們一個WifiP2pDeviceList列表, 通過這個列表我們可以找到想要連接的peer.
連接Peer:
當我們決定了想要連接的設備之後, 調用connect()方法來連接該設備. 這個方法的調用需要一個WifiP2pConfig對象作爲參數, 它包含了要連接的設備的信息. 我們可以通過WifiP2pManager.ActionListener來接收連接的結果是成功還是失敗. 栗子:
//obtain a peer from the WifiP2pDeviceList
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
mManager.connect(mChannel, config, new ActionListener() {
@Override
public void onSuccess() {
//success logic
}
@Override
public void onFailure(int reason) {
//failure logic
}
});
數據交換:
一旦連接建立了, 我們就可以在設備間用socket進行數據交換了. 這些是交換數據的基本步驟:
1. 創建一個ServerSocket. 該socket會在指定端口等待一個客戶端的連接並在連接之前阻塞着, 所以要在後臺線程中實現這個功能.
2. 創建一個客戶端Socket. 客戶端使用服務端的IP地址和端口來連接到服務端設備.
3. 從客戶端發送數據給服務端. 當客戶端socket成功的連接到服務端socket, 我們就可以通過數據流從客戶端發送數據給服務端了.
4. 服務端socket等待客戶端連接(通過accept()方法). 該方法會在客戶端連接之前阻塞, 所以應該在一個新的線程中調用該方法. 當有客戶端接入的時候, 服務端設備可以從客戶端收到設備. 然後就可以保存數據或者展示給用戶了.
下面的代碼從WiFi P2P Demo中修改得來, 展示瞭如何創建一個這樣的CS socket連接並通過一個服務從客戶端發送JPEG圖片數據給服務端:
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);
}
}
}
在客戶端中, 連接到服務端socket並交換數據. 栗子:
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
}
}
}
}
參考: https://developer.android.com/guide/topics/connectivity/wifip2p.html