Wi-Fi Aware 功能可讓搭載 Android 8.0 (API 級別 26) 以上版本的裝置,在不使用其他連線類型的情況下,直接探索及連線至其他裝置。Wi-Fi Aware 也稱為鄰近感知網路 (NAN)。
Wi-Fi Aware 網路的運作方式是與鄰近裝置形成叢集,如果裝置是區域中的第一個裝置,則會建立新的叢集。這種叢集行為適用於整個裝置,並由 Wi-Fi Aware 系統服務管理;應用程式無法控制叢集行為。應用程式會使用 Wi-Fi Aware API 與 Wi-Fi Aware 系統服務通訊,該服務會管理裝置上的 Wi-Fi Aware 硬體。
Wi-Fi Aware API 可讓應用程式執行下列作業:
探索其他裝置:API 具備尋找附近其他裝置的機制。程序會從一部裝置「發布」一或多項可供偵測的服務開始。接著,當裝置訂閱一或多項服務,並進入發布者的 Wi-Fi 範圍時,訂閱者會收到通知,指出已找到相符的發布者。訂閱者找到發布者後,可以傳送簡短訊息,或與找到的裝置建立網路連線。裝置可以同時是發布者和訂閱者。
建立網路連線:兩部裝置互相偵測到後,即可建立雙向 Wi-Fi Aware 網路連線,不需透過存取點。
相較於藍牙連線,Wi-Fi Aware 網路連線支援更長距離、更高的處理量。這類連線適用於在使用者之間分享大量資料的應用程式,例如相片分享應用程式。
Android 13 (API 級別 33) 強化功能
在搭載 Android 13 (API 級別 33) 以上版本且支援即時通訊模式的裝置上,應用程式可以使用 PublishConfig.Builder.setInstantCommunicationModeEnabled() 和 SubscribeConfig.Builder.setInstantCommunicationModeEnabled() 方法,為發布者或訂閱者探索工作階段啟用或停用即時通訊模式。即時通訊模式可加快訊息交換、服務探索,以及在發布者或訂閱者探索工作階段中設定的任何資料路徑。如要判斷裝置是否支援即時通訊模式,請使用 isInstantCommunicationModeSupported() 方法。
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 Aware 探索和網路功能,請按照下列步驟操作:
在應用程式資訊清單中要求下列權限:
<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" />
使用
PackageManagerAPI 檢查裝置是否支援 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,這些 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,透過WifiAwareNetworkSpecifier要求發布端上的 Wi-Fi Aware 網路,並指定探索工作階段和訂閱端的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 Aware 網路。建立
NetworkSpecifier時,請勿指定通訊埠。當網路連線可用、變更或中斷時,系統會呼叫適當的回呼方法。在訂閱端呼叫
onAvailable()方法後,系統會提供Network物件,您可以使用該物件開啟Socket,與發布端的ServerSocket通訊,但您必須知道ServerSocket的 IPv6 位址和通訊埠。您可以從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 Aware 對等互連裝置的距離。
Wi-Fi Aware 探索功能可限制為僅探索特定地理圍欄內的服務。舉例來說,您可以設定地理圍欄,允許探索發布 "Aware_File_Share_Service_Name" 服務的裝置,距離不小於 3 公尺 (指定為 3,000 公釐),且不超過 10 公尺 (指定為 10,000 公釐)。
如要啟用地理圍欄,發布者和訂閱者都必須採取行動:
發布者必須使用 setRangingEnabled(true),在已發布的服務上啟用測距功能。
如果發布端未啟用測距功能,系統會忽略訂閱端指定的任何地理圍欄限制,並執行一般探索,忽略距離。
訂閱者必須使用 setMinDistanceMm 和 setMaxDistanceMm 的組合指定地理圍欄。
無論是哪一個值,未指定距離都代表沒有限制。如果只指定最大距離,則表示最小距離為 0。如果只指定最小距離,則表示沒有最大距離。
在地理圍欄內偵測到對等互連服務時,系統會觸發 onServiceDiscoveredWithinRange 回呼,提供與對等互連服務的測量距離。接著,您可以視需要呼叫直接 Wi-Fi RTT API,以便日後測量距離。