Wi-Fi 感知功能使搭载 Android 8.0(API 级别 26)和 用户彼此之间不需要任何其他类型的 相互连接。Wi-Fi 感知也称为邻居感知 网络 (NAN)。
Wi-Fi 感知网络的工作原理是与邻近设备形成集群,或者 创建新的集群,前提是设备是某个区域中的第一个。这个 集群行为适用于整个设备,并由 Wi-Fi 网络进行管理 感知系统服务;应用无法控制聚类行为。应用使用情况 Wi-Fi Aware API 与 Wi-Fi Aware 系统服务通信,后者负责管理 设备上的 Wi-Fi 感知硬件。
应用可通过 WLAN 感知 API 执行以下操作:
发现其他设备:该 API 具有发现其他设备 附近的设备。当一台设备发布一部设备时,整个流程就开始了 或更多可发现的服务然后,当设备订阅一个或多个 服务并进入发布商的 Wi-Fi 范围后,订阅者会收到一个 系统会通知您,系统已发现匹配的发布商。在 订阅者发现发布商,则他们可以发送一个简短的 消息或与被发现的设备建立网络连接。 设备可以既是发布者又是订阅者。
创建网络连接:在两台设备发现每台设备后创建网络连接 也可以创建一个 无需接入点即可建立双向 Wi-Fi 感知网络连接。
Wi-Fi 感知网络连接支持更长的吞吐量, 相较于蓝牙的距离 连接。这些类型的连接对于共享大型 用户之间的数据量,例如照片分享应用。
Android 13(API 级别 33)增强功能
在搭载 Android 13(API 级别 33)及更高版本且支持免安装体验的设备上
通信模式,那么应用可以使用
PublishConfig.Builder.setInstantCommunicationModeEnabled()
和
SubscribeConfig.Builder.setInstantCommunicationModeEnabled()
方法
为发布者或订阅者启用或停用即时通信模式
探索会话。即时通信模式可加快信息交流,
服务发现,以及作为发布者或订阅者的一部分设置的任何数据路径
探索会话。确定设备是否支持即时通信
请使用 isInstantCommunicationModeSupported()
方法。
Android 12(API 级别 31)增强功能
Android 12(API 级别 31)针对 WLAN 感知功能添加了一些增强功能:
- 在搭载 Android 12(API 级别 31)或更高版本的设备上,您可以使用
onServiceLost()
回调,以便在您的应用因 或服务中断。 - Wi-Fi 感知数据路径的设置已简化。早期版本 使用 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" />
检查设备是否支持 Wi-Fi 感知
PackageManager
API 进行集成,如下所示:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
检查 WLAN 感知功能当前是否可用。以下设备可能支持 WLAN 感知功能: 但当前可能不可用,因为用户已停用 Wi-Fi 或位置信息服务。根据设备的硬件和固件功能, 如果已启用 Wi-Fi 直连、SoftAP 或网络共享,则可能不支持 Wi-Fi 感知 。要检查 WLAN 感知功能当前是否可用,请调用
isAvailable()
。WLAN 感知功能的可用性随时都可能发生变化。您的应用应 注册
BroadcastReceiver
以接收ACTION_WIFI_AWARE_STATE_CHANGED
, 每当库存状况发生变化时发送。当您的应用收到 广播 intent,则应舍弃所有现有会话(假设 Wi-Fi 感知服务中断),请查看 可用性的当前状态,并相应地调整其行为。 例如: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 感知,您的应用必须获得
通过调用 WifiAwareSession
attach()
。此方法
执行以下操作:
- 开启 WLAN 感知硬件。
- 加入或组建 WLAN 感知集群。
- 创建具有唯一命名空间的 Wi-Fi 感知会话,该命名空间充当 容器内创建的所有发现会话。
如果应用成功附加,系统将执行
onAttached()
回调。
此回调提供了一个 WifiAwareSession
对象
您的应用应该用于所有后续会话操作的值。应用可以使用
以发布服务,或者
订阅某项服务。
您的应用应调用
attach()
仅 1 次。如果
您的应用调用 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
回调。 - 您的应用订阅某项服务。然后,当它发现匹配的
则您的应用会获取
PeerHandle
(从onServiceDiscovered()
回调。
- 您的应用发布服务并接收来自订阅者的消息。
您的应用将获得订阅者的
要发送消息,请致电
sendMessage()
。通过
之后可能会发生以下回调:
- 当对等端成功收到消息后,系统会调用
onMessageSendSucceeded()
回调。 - 当对等端收到消息时,系统会调用
onMessageReceived()
receiving 应用中的回调。
虽然需要 PeerHandle
才能与对等方通信,但您不应
必须将其用作对等设备的永久标识符更高级别的标识符
(内嵌在发现服务本身或
后续消息。您可以使用以下代码在发现服务中嵌入标识符:
该
setMatchFilter()
或
setServiceSpecificInfo()
PublishConfig
方法或
SubscribeConfig
。通过
setMatchFilter()
方法会影响发现,而
setServiceSpecificInfo()
方法不会影响发现。
在消息中嵌入标识符意味着修改消息字节数组, 包含标识符(例如,作为前几个字节)。
创建连接
WLAN 感知功能支持两个 WLAN 感知设备之间的客户端-服务器网络连接。
要设置客户端-服务器连接,请执行以下操作:
订阅者发现该发布者后 从订阅者向发布者发送消息。
针对发布商启动
ServerSocket
并设置或获取其端口:Kotlin
val ss = ServerSocket(0) val port = ss.localPort
Java
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();
使用
ConnectivityManager
执行以下操作: 使用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()
方法后,Network
对象适用于 您可以打开Socket
与ServerSocket
相关联,但您需要知道ServerSocket
的 IPv6 地址和端口。您可以从NetworkCapabilities
对象 在onCapabilitiesChanged()
回调中提供: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 感知服务发现。
Wi-Fi RTT API 允许使用其 MAC 地址或其 PeerHandle。
Wi-Fi 感知发现功能可以限制为仅发现
特定地理围栏例如,您可以设置一个地理围栏,
如果设备发布 "Aware_File_Share_Service_Name"
服务,但该服务没有
3 米(以 3,000 毫米)为单位,10 米以内
(指定为 10,000 毫米)。
要启用地理围栏,发布者和订阅者都必须采取操作:
发布者必须使用 setRangingEnabled(true)。
如果发布商未启用测距,则任何地理围栏限制 将忽略并执行正常发现, 从而忽略距离
订户必须使用 setMinDistanceMm 和 setMaxDistanceMm。
对于任一值,未指定的距离表示没有限制。仅指定 最大距离意味着最小距离为 0。仅指定 最小距离表示没有最大值。
在地理围栏内发现对等服务时, onServiceDiscoveredWithinRange 回调,从而提供到对等设备的测量距离。通过 然后,可根据需要调用直接 Wi-Fi RTT API,以测量距离 。