序言:
Wi-Fi Direct 允許Android 4.0(API level 14)以上,
擁有適當的硬體的裝置來直接和其他裝置連接,
透過Wi-Fi而不需中繼的存取點(access point)。
使用這些APIs,你可以探索並連接其他支援Wi-Fi Direct的裝置,
並透過快速的連線跨越比藍芽連線更長的距離。
這對於要在使用者之前分享資料的app很有用,
比如多玩家連線遊戲或者一個照片分享app。
Wi-Fi Direct APIs 是由下面幾個主要組件構成的:
1. 允許你探索、發出請求(request)及連線到其他點的methods,
定義在
WifiP2pManager
class裡。2. 允許你被提醒的
WifiP2pManager
method calls是否成功的監聽器(Listeners)。當呼叫的
WifiP2pManager
methods時,每個method可以取得一個特定傳入的listener作為參數。
3. 提醒你那些被Wi-Fi Direct framework偵測到的特別的活動(events)的intents,
比如像是放棄(dropped)連線或者一個新發現的peer。
通常我們會一起使用三種APIs。舉例來說,你可以提供一個
WifiP2pManager.ActionListener給一個對discoverPeers()的call,
所以你就可能會被
ActionListener.onSuccess()
或ctionListener.onFailure()
給提醒(notified)。如果說discoverPeers() method發現peers list已經改變的話,
一個WIFI_P2P_PEERS_CHANGED_ACTION的intent也會廣播出來。
API Overview:
WifiP2pManager
class提供了methods來允許你和裝置上的Wi-Fi硬體互動,並作一些像是探索和連接別的peers的事情。
下面是可用的actions:
表一. Wi-Fi Direct Methods
Method | Description |
---|---|
initialize() | 使用Wi-Fi framework來註冊app。 必須在call任何Wi-Fi Direct method前呼叫。 |
connect() | 以指定的設定來和一個裝置開始特定的點對點連線。 |
cancelConnect() | 取消所有進行中的點對點群組溝通。 |
requestConnectInfo() | 取得一個裝置的連線資訊。 |
createGroup() | 以現在的裝置作為群組擁有者來產生一個點對點的群組。 |
removeGroup() | 移除現行的點對點的群組。 |
requestGroupInfo() | 取得點對點的群組資訊。 |
discoverPeers() | 開始對點(peer)的探索。 |
requestPeers() | 取得當前已探索到的點的資訊。 |
WifiP2pManager methods讓你在傳入一個listener,
使得Wi-Fi Direct framework可以提醒你的activity關於一個call的狀態如何。
可用的listener interfaces及對應的
WifiP2pManager
使用其listener的method calls如下:表二. Wi-Fi Direct Listeners
Wi-Fi Direct APIs當特定Wi-Fi Direct events發生時,
會去定義了那些要廣播的intents,
像是新的peer被發現,或者一個裝置的Wi-Fi的狀態改變。
你可以透過作出一個廣播接收器(broadcast receiver),
註冊並在你的app接收處理這些intents:
表三. Wi-Fi Direct Intents
Intent | Description |
---|---|
WIFI_P2P_CONNECTION_CHANGED_ACTION | 當裝置的Wi-Fi連線狀態改變時進行廣播。 |
WIFI_P2P_PEERS_CHANGED_ACTION | 當你呼叫discoverPeers() 進行廣播。通常你會想叫 requestPeers() 來取得更新過的peer list。(如果你有在你的app處理這種intent的話) |
WIFI_P2P_STATE_CHANGED_ACTION | 當裝置上的Wi-Fi Direct被打開或關閉時廣播。 |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION | 當裝置上的細節(details)被改變時廣播, 比如裝置的名稱被改變。 |
Creating a Broadcast Receiver for Wi-Fi Direct Intents:
一個廣播接收器允許你藉由Android系統來接收intents,
以讓你的app可以對你感興趣的events作回應。
基本來製作一個處理Wi-Fi Direct的intents的廣播接受器的步驟如下:
1. 作一個繼承
BroadcastReceiver
class的class。在建構子(constructor)裡,你大概會想要有參數給
WifiP2pManager
, WifiP2pManager.Channel
,以及廣播接收器將註冊的activity。這允許了廣播接收器傳送更新給activity,
以及和Wi-Fi硬體連接,和一個溝通的channel(如果需要的話)。
2. 在廣播接收器裡,在
onReceive()
裡檢查你有興趣的intents。依照所接收到的intent,帶出任何可能的actions。
舉例來說,如果廣播接收器接收一個WIFI_P2P_PEERS_CHANGED_ACTION的intent,
你可以呼叫requestPeers() method,來取得一個現在探索到的peer list。
下面的code展示了如何去作出一個典型的廣播接收器。
廣播接收器拿取
WifiP2pManager
物件以及activity作為參數,並在接收到一個intent時,用這兩個classes來適當地帶出需要的動作。
/** * 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 and notify appropriate activity } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Call WifiP2pManager.requestPeers() to get a list of current peers } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Respond to new connection or disconnections } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { // Respond to this device's wifi state changing } } }
Creating a Wi-Fi Direct Application:
作出一個Wi-Fi Direct的app包含了作出並註冊一個廣播接收器給你的app、
探索peers、連接到一個peer,以及傳輸資料到一個peer。
下面的區塊描述如何去做這件事情。
Initial setup:
在使用Wi-Fi Direct APIs之前,
你必須確保你的app可以存取能支援Wi-Fi Direct protocol的硬體。
如果Wi-Fi Direct被支援的話,你可以取得一個WifiP2pManager實例,
作出並註冊你的廣播接收器並且開始使用Wi-Fi Direct APIs。
1. 要求使用裝置上的Wi-Fi硬體的權限,
並在Android manifest裡聲明你的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. 檢查Wi-Fi Direct是否被打開並被支援。一個好的檢查的地方是在你的廣播接收器裡。
(當它接收到WIFI_P2P_STATE_CHANGED_ACTION intent時)
提醒你的activity注意Wi-Fi Direct狀態並且相對應地去處理:
@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 Direct is enabled } else { // Wi-Fi Direct is not enabled } } ... }
3. 在你的activity裡的
onCreate()
method裡,取得一個WifiP2pManager
的實例,並且用Wi-Fi Direct framework呼叫
initialize()
來註冊你的app。這個method回傳了一個
WifiP2pManager.Channel
,用來連接你的app到Wi-Fi Direct framework。你也應該作一個廣播接收器的實例,
使用
WifiP2pManager
、WifiP2pManager.Channel
物件、以及一個activity的參照。這允許你的廣播接收器來提醒你的activity感興趣的events並從而作更新。
它也讓你可以操作裝置的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); ... }
4. 製作一個intent filter並加上你廣播接收器有檢查的那些intents:
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() method裡註冊廣播接收器,並在onPause() method裡反註冊:
/* 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
並且設定廣播接收器時,你的app可以做出Wi-Fi Direct method的呼叫並接收Wi-Fi Direct intents。
你現在可以透過呼叫WifiP2pManager裡的methods實作app的部分並使用Wi-Fi Direct功能。
接下來的段落描述了如何去做一些常見的動作,
像是探索和連接peers。
Discovering peers:
要探索可以連接的peers,呼叫
discoverPeers()
來偵測範圍內可用的peers。呼叫這個function是不同步的,且成功或失敗會以
onSuccess()
和 onFailure()
你的app溝通。(如果你做了一個
WifiP2pManager.ActionListener
的話)onSuccess()
method只會提醒你探索的process成功,而不會提供任何實際的發現的peer的資訊。
mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { ... } @Override public void onFailure(int reasonCode) { ... } });
如果探索的process成功並偵測到peers的話,
系統會廣播WIFI_P2P_PEERS_CHANGED_ACTION intent,
由此你可以在廣播接收器監聽來接收peer list。
當你的app取得
WIFI_P2P_PEERS_CHANGED_ACTION
intent時,你可以使用requestPeers()來request一組發現的peers的列表。
下面展示設定方法:
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()
method也是不同步的,並且當一個peers的列表可用時,可以用
onPeersAvailable()
提醒你的activity。(被定義在
WifiP2pManager.PeerListListener
interface裡)onPeersAvailable()
method提供你一個WifiP2pDeviceList,你可以透過iteration來尋找你想要連接的peer。
Connecting to peers:
當你已經找到你想連接的裝置(在取得可能的peers的list後),
呼叫connect() method來連線到其他裝置。
這個method call需要一個包含欲連線的裝置資訊的
WifiP2pConfig
物件。你可以透過
WifiP2pManager.ActionListener
來被提醒連線成功或失敗。下面的codes展示如何去對希望的裝置作連線:
//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 } });
Transferring data:
一旦連線被建立,你可以使用sockets在裝置之間傳輸資料。
基本的傳輸資料的步驟如下:
1. 製作一個ServerSocket。這個socket等候從client來的特定port的連線,
且直到連線發生前block,在背景的thread也是這麼做。
2. 製作一個client Socket。client使用IP位址和server socket的port,
來連到server裝置。
3. 從client傳送資料到server。當client socket成功地連接到server socket時,
你可以以byte串流(streams)來將資料從client傳到server。
4. server socket等待一個client連線(以accept() method)。
這個呼叫會block,直到一個client連線,所以呼叫這個也是另一個thread。
當一個連線發生時,server裝置可以從client來接收資料。
用這個資料帶出其他的動作,比如說存到一個檔案裡,
或者將其展示給使用者。
下面的範例是從Wi-Fi Direct Demo sample修改來的,
展示了如何作出client-server socket溝通並用服務(service)從client傳輸JPEG圖檔到server。
如果要看完整的範例就請去找 Wi-Fi Direct Demo的sample來編譯執行吧!
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); } } }
在client端,用client socket連線到server socket,並傳輸資料。
這個範例傳輸一個client裝置上檔案系統的JPEG檔案。
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 } } } }
可以跟你要這個程式的壓縮檔嗎?
回覆刪除我想放到手機上測試。
您好,範例的部分請下載Android developer的API Demos~
刪除您好,請問:如果我寫一個賓果程式,在不架設AP server的情況下想行資料庫數據或檔案互相即時傳輸,透過此API就可以完成,對嗎?
回覆刪除David您好:
刪除的確是這樣子沒有錯,像是對岸的一些音樂撥放程式裡,
就有使用Wi-Fi Direct的方式進行傳輸音樂檔案的功能。
只要能夠讓兩點相配對到就可以進行資料的傳輸。
不過如果是想要有一個主體的Server來管理很多個Client的數據連線的話,
可能還是要弄一個固定的位址來讓其他透過TCP來連結傳輸,會比較恰當。