搭載 Android 8.0 (API 級別 26) 以上版本的裝置,可讓搭載 Android 8.0 (API 級別 26) 以上版本的裝置,能偵測到彼此並直接連線,不必另外建立任何其他類型的連線。Wi-Fi Aware 又稱為鄰近感知網路 (NAN)。
Wi-Fi Aware 網路的運作方式,是使用鄰近裝置形成叢集。如果裝置是區域內的第一部叢集,也可以建立新叢集。這個叢集行為適用於整部裝置,並由 Wi-Fi 感知系統服務管理;應用程式無法控管叢集行為。應用程式會透過 Wi-Fi Aware API 連線至 Wi-Fi Aware 系統服務,由該服務管理裝置上的 Wi-Fi Aware 硬體。
Wi-Fi Aware API 可讓應用程式執行以下作業:
探索其他裝置:這個 API 具有尋找附近裝置的機制。當某部裝置發布一或多個可搜尋的服務時,程序就會開始。之後,當裝置訂閱一或多項服務,並輸入發布者的 Wi-Fi 範圍時,訂閱者就會收到已找到相符發布者的通知。訂閱者發現發布者後,訂閱者就可以傳送簡短訊息,或與發現的裝置建立網路連線。裝置可同時設為發布者和訂閱者。
建立網路連線:兩部裝置找到彼此後,即可在沒有存取點的情況下建立雙向 Wi-Fi Aware 網路連線。
Wi-Fi Aware 網路連線支援比藍牙連線更長的總處理量速率。如果應用程式會與使用者分享大量資料,例如相片分享應用程式,這類連線就非常實用。
Android 12 (API 級別 31) 改善項目
Android 12 (API 級別 31) 為 Wi-Fi Aware 新增了一些強化項目:
- 在搭載 Android 12 (API 級別 31) 以上版本的裝置上,您可以使用
onServiceLost()
回呼,在應用程式因服務停止運作或移出範圍,而遺失服務時收到快訊。 - Wi-Fi Aware 資料路徑的設定方式已大幅縮短。先前版本是使用 L2 訊息傳遞來提供啟動者的 MAC 位址,從而產生延遲。在搭載 Android 12 以上版本的裝置上,可將回應器 (伺服器) 設為接受任何對等互連,也就是不需要事先知道初始化器的 MAC 位址。這可以加快資料路徑的傳輸速度,並在只有一個網路要求的情況下啟用多個點對點連結。
- 在 Android 12 以上版本中執行的應用程式可以使用
WifiAwareManager.getAvailableAwareResources()
方法,取得目前可用的資料路徑數量、發布工作階段和訂閱工作階段數。這有助於應用程式判斷是否有足夠的資源來執行所需功能。
初始設定
如要設定應用程式使用 Wi-Fi 感知探索功能和網路功能,請執行下列步驟:
請在應用程式資訊清單中要求下列權限:
<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" /> <!-- If your app targets Android 13 (API level 33) or higher, you must declare the NEARBY_WIFI_DEVICES permission. --> <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" <!-- If your app derives location information from Wi-Fi APIs, don't include the "usesPermissionFlags" attribute. --> android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" <!-- If any feature in your app relies on precise location information, don't include the "maxSdkVersion" attribute. --> android:maxSdkVersion="32" />
使用
PackageManager
API 檢查裝置是否支援 Wi-Fi Aware,如下所示:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
檢查目前是否可使用 Wi-Fi Aware。裝置上可能會具備 Wi-Fi Aware,但由於使用者停用 Wi-Fi 或定位功能,因此目前無法使用。如果裝置正在使用 Wi-Fi Direct、SoftAP 或網路共用功能,則某些裝置可能不支援 Wi-Fi Aware,視其硬體和韌體功能而定。如要確認 Wi-Fi Aware 目前是否可用,請呼叫
isAvailable()
。Wi-Fi Aware 的可用性隨時可能變更。應用程式應註冊一個
BroadcastReceiver
,以便接收ACTION_WIFI_AWARE_STATE_CHANGED
,每當可用性變更時,就會傳送該訊息。應用程式收到廣播意圖時,應捨棄所有現有工作階段 (假設 Wi-Fi Aware 服務中斷),然後檢查可用性目前的狀態,並據此調整行為。例如:Kotlin
val wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager? val filter = IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED) val myReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { // discard current sessions if (wifiAwareManager?.isAvailable) { ... } else { ... } } } context.registerReceiver(myReceiver, filter)
Java
WifiAwareManager wifiAwareManager = (WifiAwareManager)context.getSystemService(Context.WIFI_AWARE_SERVICE) IntentFilter filter = new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED); BroadcastReceiver myReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // discard current sessions if (wifiAwareManager.isAvailable()) { ... } else { ... } } }; context.registerReceiver(myReceiver, filter);
詳情請參閱「廣播」。
取得工作階段
如要開始使用 Wi-Fi Aware,應用程式必須透過呼叫 attach()
取得 WifiAwareSession
。這個方法會執行以下作業:
- 開啟 Wi-Fi Aware 硬體。
- 彙整或形成 Wi-Fi Aware 叢集。
- 建立具有專屬命名空間的 Wi-Fi Aware 工作階段,該命名空間做為容器中所建立的所有探索工作階段的容器。
如果應用程式成功附加,系統就會執行 onAttached()
回呼。這個回呼提供 WifiAwareSession
物件,應用程式應用於所有後續工作階段作業。應用程式可以使用這個工作階段來發布服務或訂閱服務。
應用程式只能呼叫 attach()
一次。如果應用程式多次呼叫 attach()
,應用程式就會在每次呼叫收到不同的工作階段,且每個工作階段都有各自的命名空間。這種做法在複雜情況下可能很有用,但一般應避免使用。
發布服務
如要讓服務可供探索,請呼叫 publish()
方法,該方法會用到下列參數:
PublishConfig
會指定服務名稱和其他設定屬性,例如比對篩選器。DiscoverySessionCallback
會指定事件發生時要執行的動作,例如訂閱者收到訊息時要執行的動作。
範例如下:
Kotlin
val config: PublishConfig = PublishConfig.Builder() .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME) .build() awareSession.publish(config, object : DiscoverySessionCallback() { override fun onPublishStarted(session: PublishDiscoverySession) { ... } override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) { ... } })
Java
PublishConfig config = new PublishConfig.Builder() .setServiceName(“Aware_File_Share_Service_Name”) .build(); awareSession.publish(config, new DiscoverySessionCallback() { @Override public void onPublishStarted(PublishDiscoverySession session) { ... } @Override public void onMessageReceived(PeerHandle peerHandle, byte[] message) { ... } }, null);
如果發布成功,系統會呼叫 onPublishStarted()
回呼方法。
發布之後,如果執行相符的訂閱者應用程式,裝置會進入發布裝置的 Wi-Fi 範圍,訂閱者會探索該服務。訂閱者找到發布者時,發布者不會收到通知;不過,如果訂閱者傳送訊息給發布者,則發布者會收到通知。發生這種情況時,系統會呼叫 onMessageReceived()
回呼方法。您可以使用這個方法的 PeerHandle
引數來傳送訊息給訂閱者或建立連線。
如要停止發布服務,請呼叫 DiscoverySession.close()
。探索工作階段與父項 WifiAwareSession
相關聯。如果父項工作階段關閉,相關聯的探索工作階段也會一併關閉。雖然捨棄的物件也會關閉,但系統無法保證在範圍外的工作階段關閉時一定會關閉,因此建議您明確呼叫 close()
方法。
訂閱服務
如要訂閱服務,請呼叫 subscribe()
方法,該方法會採用下列參數:
-
SubscribeConfig
會指定要訂閱的服務名稱以及其他設定屬性,例如比對篩選條件。 DiscoverySessionCallback
會指定事件發生時要執行的動作,例如找到發布者時。
範例如下:
Kotlin
val config: SubscribeConfig = SubscribeConfig.Builder() .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME) .build() awareSession.subscribe(config, object : DiscoverySessionCallback() { override fun onSubscribeStarted(session: SubscribeDiscoverySession) { ... } override fun onServiceDiscovered( peerHandle: PeerHandle, serviceSpecificInfo: ByteArray, matchFilter: List<ByteArray> ) { ... } }, null)
Java
SubscribeConfig config = new SubscribeConfig.Builder() .setServiceName("Aware_File_Share_Service_Name") .build(); awareSession.subscribe(config, new DiscoverySessionCallback() { @Override public void onSubscribeStarted(SubscribeDiscoverySession session) { ... } @Override public void onServiceDiscovered(PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter) { ... } }, null);
如果訂閱作業成功,系統會在應用程式中呼叫 onSubscribeStarted()
回呼。由於您可以在應用程式發現該引數後,使用回呼中的 SubscribeDiscoverySession
引數與發布者通訊,因此建議您儲存這項參照。您隨時可以在探索工作階段呼叫 updateSubscribe()
來更新訂閱工作階段。
此時,您的訂閱項目會等待相符的發布商進入 Wi-Fi 範圍。發生這種情況時,系統會執行 onServiceDiscovered()
回呼方法。您可以使用這個回呼中的 PeerHandle
引數來傳送訊息或建立連線給該發布者。
如要停止訂閱服務,請呼叫 DiscoverySession.close()
。探索工作階段與父項 WifiAwareSession
相關聯。如果父項工作階段關閉,相關聯的探索工作階段也會一併關閉。雖然捨棄的物件也會關閉,但系統無法保證在範圍外的工作階段關閉時一定會關閉,因此建議您明確呼叫 close()
方法。
傳送訊息
如要傳送訊息至其他裝置,您必須準備下列物件:
DiscoverySession
。這個物件可讓您呼叫sendMessage()
。發布服務或訂閱服務,應用程式可獲得DiscoverySession
。另一個裝置的
PeerHandle
可轉送訊息。應用程式可透過以下其中一種方式取得其他裝置的PeerHandle
:- 您的應用程式會發布服務並接收訂閱者傳送的訊息。應用程式會從
onMessageReceived()
回呼取得訂閱者的PeerHandle
。 - 應用程式訂閱服務。然後,當它找到相符的發布商時,應用程式就會從
onServiceDiscovered()
回呼取得發布者的PeerHandle
。
- 您的應用程式會發布服務並接收訂閱者傳送的訊息。應用程式會從
如要傳送訊息,請呼叫 sendMessage()
。此時可能會發生以下回呼:
- 當對等點成功收到訊息時,系統會在「傳送」應用程式中呼叫
onMessageSendSucceeded()
回呼。 - 對等點收到訊息時,系統會在「接收」應用程式中呼叫
onMessageReceived()
回呼。
雖然 PeerHandle
需要與對等連線通訊,但我們不建議您使用此永久 ID。應用程式可以使用較高層級的 ID,嵌入在探索服務本身或後續訊息中。您可以透過 PublishConfig
或 SubscribeConfig
的 setMatchFilter()
或 setServiceSpecificInfo()
方法,在探索服務中嵌入 ID。setMatchFilter()
方法會影響探索作業,setServiceSpecificInfo()
方法則不會影響探索。
在訊息中嵌入 ID 意味著將訊息位元組陣列修改為包含 ID (例如,前幾個位元組)。
建立連線
Wi-Fi Aware 支援兩部 Wi-Fi Aware 裝置之間的用戶端伺服器網路。
如何設定用戶端與伺服器連線:
訂閱者發現發布者後,請從訂閱者向發布者傳送訊息。
在發布者裝置上啟動
ServerSocket
,並設定或取得通訊埠:Kotlin
val ss = ServerSocket(0) val port = ss.localPort
Java
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
使用
ConnectivityManager
向發布者要求 Wi-Fi Aware 網路,並使用WifiAwareNetworkSpecifier
指定探索工作階段和訂閱者的PeerHandle
(系統會從訂閱者傳輸的訊息取得這項資訊):Kotlin
val networkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle) .setPskPassphrase("somePassword") .setPort(port) .build() val myNetworkRequest = NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE) .setNetworkSpecifier(networkSpecifier) .build() val callback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { ... } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { ... } override fun onLost(network: Network) { ... } } connMgr.requestNetwork(myNetworkRequest, callback);
Java
NetworkSpecifier networkSpecifier = new WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle) .setPskPassphrase("somePassword") .setPort(port) .build(); NetworkRequest myNetworkRequest = new NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE) .setNetworkSpecifier(networkSpecifier) .build(); ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { ... } @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { ... } @Override public void onLost(Network network) { ... } }; ConnectivityManager connMgr.requestNetwork(myNetworkRequest, callback);
發布者要求聯播網後,應傳送訊息給訂閱者。
訂閱者收到發布者傳送訊息後,請使用與發布者相同的方法,向訂閱者要求 Wi-Fi 感知網路。建立
NetworkSpecifier
時,請勿指定通訊埠。有可用、變更或遺失的網路連線時,系統會呼叫適當的回呼方法。在訂閱者上呼叫
onAvailable()
方法後,就可以開啟Socket
,與發布者的ServerSocket
通訊,但您需要知道ServerSocket
的 IPv6 位址和通訊埠。Network
您可以從onCapabilitiesChanged()
回呼中提供的NetworkCapabilities
物件取得這些參數:Kotlin
val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo val peerIpv6 = peerAwareInfo.peerIpv6Addr val peerPort = peerAwareInfo.port ... val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)
Java
WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo(); Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr(); int peerPort = peerAwareInfo.getPort(); ... Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
網路連線完畢後,請呼叫
unregisterNetworkCallback()
。
選擇同業和位置辨識探索
具有 Wi-Fi RTT 位置功能的裝置可以直接測量與同類應用程式的距離,並使用這項資訊限制 Wi-Fi Aware 服務探索。
Wi-Fi RTT API 允許使用其 MAC 位址或 PeerHandle 直接範圍到 Wi-Fi 感知對等互連。
Wi-Fi Aware 探索可限制為僅探索特定地理圍欄內的服務。舉例來說,您可以設定地理圍欄,允許探索裝置所發布 "Aware_File_Share_Service_Name"
服務的裝置範圍不超過 3 公尺 (指定為 3,000 公釐),且範圍不得超過 10 公尺 (指定為 10,000 公釐)。
如要啟用地理圍欄,發布者和訂閱者都必須採取下列行動:
發布者必須使用 setRangingEnabled(true) 在已發布的服務上啟用範圍。
如果發布者未啟用範圍範圍,則系統會忽略訂閱者指定的所有地理圍欄限制,並執行常態探索,而忽略距離。
訂閱者必須使用 setMinDistanceMm 和 setMaxDistanceMm 的組合來指定地理圍欄。
如果有任一值,未指定距離則代表沒有限制。只指定距離上限表示最小距離為 0。只指定距離下限表示沒有上限。
如果在地理圍欄內發現對等互連服務,就會觸發 onServiceDiscoveredWithinRange 回呼,藉此提供與對等點之間的測量距離。之後可視需要呼叫直接 Wi-Fi RTT API,以便稍後測量距離。