Android Wi-Fi Peer-to-Peer(Android的Wi-Fi P2P對等網絡)

Wi-Fi peer-to-peer(P2P,對等網絡),它允許具備相應硬件的Android 4.0(API level 14)或者更高版本的設備可以直接通過wifi而不需要其它中間中轉節點就能直接通信(Android的Wi-Fi P2P框架符合Wi-Fi聯盟的Wi-Fi Direct?直連認證標志)。使用這些API,你可以搜索並連接其它同樣支持Wi-Fi P2P的設備,然後再通過一個高速的連接進行互相通信,並且這個連接的有效距離要比藍牙連接的有效距離要長的多。這對於需要在用戶之間共享數據的應用程序非常有用,例如多玩傢遊戲或者照片分享之類應用。

Android的Wi-Fi P2P API主要包含以下幾個部分:

* 使用WifiP2pManager類定義的方法搜索、請求並連接到其它對等設備。

* 監聽WiFiP2pManager的方法調用是否成功或者失敗的Listener(監聽器)。當調用WiFiP2pManager的方法時,每一個方法都可以接收一個指定的監聽器作為參數。

* 由Wi-Fi P2P框架識別並發送的指定事件類型的Intent,例如撤銷一個連接或者新發現瞭一個對等設備。

通常你需要一起使用這三個主要部分的API。例如,你可以在調用discoverPeers()方法的時候提供WifiP2pManager.ActionListener監聽器對象,這樣你就能得到ActionListener.onSuccess()和ActionListener.onFailure()方法的通知。如果discoverPeers()方法發現對等設備列表有更新,同樣也會發送WIFI_P2P_PEERS_CHANGE_ACTION類型的Intent廣播。

1、API概覽

WifiP2pManager類提供瞭與設備上的Wi-Fi硬件進行交互的方法,例如搜索和連接對等設備。以下是可用的操作:

表一:Wi-Fi P2P方法

方法 描述
initialize() 在Wi-Fi框架上註冊應用程序。這個方法必須在調用其它Wi-Fi P2P方法之前調用。
connect() 使用指定配置與一臺設備開始一個P2P對等連接。
cancelConnect() 取消正在進行中的P2P對等網絡組的交互。
requestConnectInfo() 請求一臺設備連接信息。
createGroup() 創建一個對等網絡組,並將當前設備作為群組的擁有者。
removeGroup() 移除當前的P2P對等網絡組。
requestGroupInof() 請求P2P對等網絡組信息。
discoverPeers() 開始搜索對等網絡(設備)。
requestPeers() 請求當前已發現的對等網絡(設備)。

WifiP2pManager的方法允許你傳遞一個listener監聽器,這樣Wi-Fi P2P框架才能通知你的activity所調用方法的狀態。可使用的listener監聽器接口和相應調用這些監聽器的WifiP2pManager方法如下表:

表二:Wi-Fi P2P監聽器

監聽器接口 可被使用的操作
WifiP2pManager.ActionListener connect(),cancelConnect(),createGroup(),removeGroup(),discoverPeers()
WifiP2pManager.ChannelListener initialize()
WifiP2pManager.ConnectionInfoListener requestConnectInfo()
WifiP2pManager.GroupInfoListener requestGroupInfo()
WifiP2pManager.PeerListListener requestPeers()

Wi-Fi P2P的API定義某些Wi-Fi P2P事件發生時要廣播的Intent,例如發現瞭一個新的對等網絡(設備)或者一臺設備的Wi-Fi狀態改變的事件。你可以在你的應用程序中創建並註冊一個廣播接受者來處理下面這些Intent:


表三:Wi-Fi P2P Intents

Intent 描述
WIFI_P2P_CONNECTION_CHANGED_ACTION 當設備的Wi-Fi連接狀態改變時會發送這個廣播。
WIFI_P2P_PEERS_CHANGED_ACTION 當你調用discoverPeers()方法就會收到這個廣播。如果你在你的應用程序中處理這個intent,你同樣需要調用requestPeers()來獲取一個最新的對等網絡(設備)列表。
WIFI_P2P_STATE_CHANGED_ACTION 當設備的Wi-Fi P2P啟用或者禁用時會發送這個廣播。當設備的Wi-Fi P2P啟用或者禁用時會發送這個廣播。
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 當設備的細節被修改時會發送這個廣播,例如設備的名字。

2、為Wi-Fi P2P的Intent創建一個廣播接收器

廣播接收器(broadcase receiver)允許你接收Android系統廣播出來的Intent,這樣你的應用程序才能響應你所感興趣的時間。創建一個廣播接收器來處理Wi-Fi P2P intent的基本步驟如下:

2.1 創建一個繼承BroadcaseReceiver的類。在這個類的構造方法中,你最好將WifiP2pManager、WifiP2pManager.Channel,還有註冊瞭這個廣播接收器的activity作為參數傳遞進來。這樣才能讓廣播接收器向activity發送更新的同時,還能在需要的時候訪問Wi-Fi硬件和通信的信道。

2.2 在廣播接收器中,檢查onReceive()方法中你所感興趣的intent。基於接收到的intent完成一些必要的操作。例如,如果廣播接收器接收到一個WIFI_P2P_PEERS_CHANGE_ACTION類型的Intent,你可以調用requestPeers()方法來獲取當前已發現的對等網絡(設備)。

下面的代碼展示如何創建一個典型的廣播接收器。這個廣播接收器攜帶一個WifiP2pManager對象和一個activity最為參數,並在廣播接收器接到一個intent的時候,使用這兩個對象來適當完成所需要的操作:

/**
 * 一個接收Wi-Fi P2P重要事件的廣播接收器
 */
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)) {
            // 檢查Wi-fi是否已經啟用,並通知適當的activity
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // 調用WifiP2pManager.requestPeers()來獲取當前的對等網絡(設備)列表
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
            // 響應新的連接或者斷開連接
        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            // 響應本臺設備wifi狀態的改變
        }
    }
}

3、創建一個Wi-Fi P2P應用程序

創建一個Wi-Fi P2P應用程序涉及到為應用程序創建並註冊一個廣播接收器,搜索對等網絡(設備),連接一臺對等設備,還有向一臺對等設備傳輸數據。下面的章節描述如何完成這些操作。

3.1 初始設置

在使用Wi-Fi P2P的API之前,你必須確認你的應用程序能夠訪問對應的硬件並且設備支持Wi-Fi P2P協議。如果支持Wi-Fi P2P,你就可以獲取一個WifiP2pManager實例,創建並註冊你的廣播接收器,然後開始使用Wi-Fi P2P的API。

3.1.1 在Android的manifest文件中請求使用設備上Wi-Fi硬件的權限,並為你的應用程序聲明正確的最小SDK版本號:





3.1.2 檢查Wi-Fi P2P能夠支持並且已經啟用。進行這項檢查的一個較好的地方是在你的廣播接收器中,當廣播接收器接收到WIFI_P2P_STATE_CHANGED_ACTION的時候。從而通知你的activity關於Wi-Fi P2P的狀態並做出相應的應對。

@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已經啟用
        } else {
            // Wi-Fi P2P沒有啟用
        }
    }
    ...
}

3.1.3 在你的activity的onCreate()方法中獲取一個WifiP2pManager實例,並通過調用initialize()方法將你的應用程序註冊到Wi-Fi P2P框架中。這個方法會返回一個WifiP2pManager.Channel對象,這個對象被用於連接你的應用程序和Wi-Fi P2P框架。你同樣需要使用這個WifiP2pManager和WifiP2pManager.Channel對象,還有你的activity引用創建一個你的廣播接收器實例。這樣才能允許你的廣播接收器通知你的activity所感興趣的事件並進行更新。它同樣能讓你在需要的時候操作設備的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);
    ...
}

3.1.4 創建一個intent過濾器,並在你的廣播接收器中添加相同的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);
    ...
}

3.1.5 在activity的onResume()方法中註冊你的廣播接收器,並在onPause()方法中註銷:

/* 使用需要匹配的intent值註冊廣播接收器 */
@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mReceiver, mIntentFilter);
}
/* 註銷廣播接收器 */
@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mReceiver);
}

當你已經獲取到WifiP2pManager.Channel對象並且設置好廣播接收器,你的應用程序就可以調用Wi-Fi P2P的方法和接收Wi-Fi P2P的intent。

你現在可以實現你的應用程序並且通過調用WifiP2pManager中的方法來使用Wi-Fi P2P的功能。下面的章節將闡述如何執行常見的操作,比如搜索和連接對等設備。

3.2 搜索對等網絡(設備)

搜索允許被連接的對等設備,可以調用discoverPeers()方法來檢測附近范圍中允許被發現的對等網絡(設備)。這個方法的執行過程是異步的,如果你有創建並傳遞一個WifiP2pManager.ActionListener對象作為參數,你可以在這個對象中的onSuccess()和onFailure()方法中接收到操作執行的結果。其中onSuccess()方法隻是通知你搜索程序已經成功執行,但是不會提供任何實際搜索到的對等設備信息;

mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
    @Override
    public void onSuccess() {
        ...
    }

    @Override
    public void onFailure(int reasonCode) {
        ...
    }
});

如果搜索程序成功執行並且檢測到對等網絡和設備,系統將會發送WIFI_P2P_PEERS_CHANGED_ACTION類型的intent廣播,你可以在廣播接收器中監聽這個廣播以獲取對等網絡設備列表。當你的應用程序接收到WIFI_P2P_PEERS_CHANGED_ACTION類型的intent,你就可以使用requestPeers()方法來請求已被發現的對等網絡設備列表。下面的代碼展示瞭如何設置這些步驟:

PeerListListener myPeerListListener;
...
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

    // 從wifi p2p manager請求可使用的對等設備。這是一個異步調用的方法,
    // 調用的activity將會在PeerListListener.onPeersAvailable()的回調方法中得到列表結果。
    if (mManager != null) {
        mManager.requestPeers(mChannel, myPeerListListener);
    }
}

這個requestPeers()方法同樣是異步執行的方法,當對等設備列表可用時,它會在WifiP2pManager.PeerListListener接口定義的onPeersAvailable()方法中將結果返回給你的activity。onPeersAvailable()方法會提供一個WifiP2pDeviceList對象,你可以通過這個對象找到你想要連接的對等設備。

3.3 連接對等設備

當你從獲取到的可連接對等設備列表中找出你想要連接的對等設備之後,你就可以調用connect()方法鏈接這個設備。這個方法的調用需要一個WifiP2pConfig對象,這個對象包含瞭想要連接的設備信息。你可以通過WifiP2pManager.ActionListener得到連接成功或者失敗的結果。下面的代碼展示瞭如何連接目標設備:

//從WifiP2pDeviceList中獲取一個對等設備
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
mManager.connect(mChannel, config, new ActionListener() {

    @Override
    public void onSuccess() {
        //連接成功的邏輯處理操作
    }

    @Override
    public void onFailure(int reason) {
        //連接失敗的邏輯處理操作
    }
});

3.4 傳輸數據

一旦連接建立成功,你就可以在設備之間通過socket傳輸數據。傳入數據的基本步驟如下:

3.4.1 創建一個SeverSocket。這個socket會在一個端口上等待客戶端的連接,並且會一直阻塞直到連接請求出現,因此這個方法需要在後臺線程執行。

3.4.2 創建一個客戶端的Socket。這個客戶端使用服務端ServerSocket的IP地址和端口來連接服務端設備。

3.4.3 從客戶端發送數據到服務端。當客戶端的socket成功連接上服務端的socket之後,你就可以通過字節流將數據從客戶端發送到服務端。

3.4.4 服務端的ServerSocket等待著客戶端的連接(通過accept()方法)。這個方法會一直阻塞直到客戶端連接上,因此要在其它線程中調用這個方法。當連接上之後,服務端設備就能夠接收到客戶端發送過來的數據。完成對這些數據的一些操作,例如保存為一個文件或者顯示給用戶。

下面的例子修改於Wi-Fi P2P Demo(可以在Android SDK中找到),它展示瞭如何創建客戶端和服務端之間的socket通信,並從通過一個service從客戶端向服務端發送瞭一個JPEG圖片。完整的代碼,可以直接查看Wi-Fi P2P 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 {

            /**
             * 創建一個ServerSocket並等待客戶端的連接.這個方法會一直阻塞直到接受瞭一個客戶端連接
             */
            ServerSocket serverSocket = new ServerSocket(8888);
            Socket client = serverSocket.accept();

            /**
             * 如果代碼能夠執行到這裡,說明已經連接上一個客戶端並且開始傳輸數據
             * 將來自客戶端的數據流保存為一個JPEG文件
             */
            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;
        }
    }

    /**
     * 啟動能夠處理JPEG圖片的activity
     */
    @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連接服務端的socket並傳輸數據。這個例子傳輸瞭一個客戶端設備文件系統的上的JPEG文件。

Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];
...
try {
    /**
     * 使用服務端IP和端口還有連接超時時間創建一個客戶端socket
     */
    socket.bind(null);
    socket.connect((new InetSocketAddress(host, port)), 500);

    /**
     * 從一個JPEG文件創建一個字節流,並且傳輸到socket的輸出流上。這些數據將會在服務端設備上接收到。
     */
    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
}

/**
 * 當數據傳輸完成或者發生異常時關閉已經打開的socket。
 */
finally {
    if (socket != null) {
        if (socket.isConnected()) {
            try {
                socket.close();
            } catch (IOException e) {
                //catch logic
            }
        }
    }
}

原文地址:https://developer.android.com/guide/topics/connectivity/wifip2p.html

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *