Android WiFi P2P

概述:

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

 

發佈了81 篇原創文章 · 獲贊 4 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章