Wi-Fi 感知概览

Wi-Fi 感知功能使搭载 Android 8.0(API 级别 26)和 用户彼此之间不需要任何其他类型的 相互连接。WLAN 感知也称为“邻近感知网络”(NAN)。

Wi-Fi 感知网络的工作原理是与邻近设备形成集群,或者 创建新的集群,前提是设备是某个区域中的第一个。此集群行为适用于整个设备,由 WLAN 感知系统服务管理;应用无法控制集群行为。应用使用情况 Wi-Fi Aware API 与 Wi-Fi Aware 系统服务通信,后者负责管理 设备上的 Wi-Fi 感知硬件。

应用可通过 WLAN 感知 API 执行以下操作:

  • 发现其他设备:该 API 具有发现其他设备 附近的设备。此过程会在一台设备发布一项或多项可发现服务时启动。之后,当设备订阅一个或多个 服务并进入发布商的 Wi-Fi 范围后,订阅者会收到一个 系统会通知您,系统已发现匹配的发布商。在订阅者发现发布者后,订阅者可以发送短消息或与发现的设备建立网络连接。设备可以既是发布者又是订阅者。

  • 创建网络连接:在两台设备发现每台设备后创建网络连接 也可以创建一个 无需接入点即可建立双向 Wi-Fi 感知网络连接。

蓝牙连接相比,WLAN 感知网络连接支持的覆盖范围更广,支持的吞吐率更高。这些类型的连接对于共享大型 用户之间的数据量,例如照片分享应用。

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 感知功能:

  • 在搭载 Android 12(API 级别 31)或更高版本的设备上,您可以使用 onServiceLost() 回调,以便在您的应用因 或服务中断。
  • 简化了 Wi-Fi 感知数据路径的设置。较低的版本使用 L2 消息功能来提供发起方的 MAC 地址,由此导致了延迟。在搭载 Android 12 及更高版本的设备上,响应程序 (服务器)可以配置为接受任何对等方,也就是说, 以便预先知道发起者的 MAC 地址。这可加快数据路径启动,并只需一个网络请求即可实现多个点对点链接。
  • 在 Android 12 或更高版本上运行的应用可以使用 WifiAwareManager.getAvailableAwareResources() 方法获取当前可用数据路径的数量、发布会话、 以及订阅会话这有助于应用确定是否存在 有足够的可用资源来执行所需功能。

初始设置

如需将应用设置为使用 Wi-Fi 感知发现和网络,请执行以下步骤:

  1. 在应用的清单中请求以下权限:

    <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" />
  2. 检查设备是否支持 Wi-Fi 感知 PackageManager API 进行集成,如下所示:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. 检查 WLAN 感知功能当前是否可用。WLAN 感知功能可能存在于以下设备上: 但当前可能不可用,因为用户已停用 Wi-Fi 或位置信息。如果正在使用 WLAN 直连、SoftAP 或网络共享,某些设备可能不支持 WLAN 感知功能,具体取决于其硬件和固件功能。要检查 WLAN 感知功能当前是否可用,请调用 isAvailable()

    WLAN 感知功能的可用性随时都可能发生变化。您的应用应注册 BroadcastReceiver 才能收到每当可用性发生变化时发送的 ACTION_WIFI_AWARE_STATE_CHANGED。应用收到该广播 intent 后,应舍弃所有现有会话(假设 WLAN 感知服务已中断),然后检查当前的可用性状态并相应地调整其行为。例如:

    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() 回调方法。

发布后,当运行匹配订阅者应用的设备进入发布设备的 WLAN 范围时,订阅者会发现该服务。时间 订阅者发现发布者,但发布者不会收到 通知;但是,如果订阅者向发布者发送消息 发布商会收到通知发生这种情况时,系统会调用 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() 发现会话

此时,您的订阅会等待匹配的发布商进入 WLAN 范围。出现这种情况时,系统会执行 onServiceDiscovered() 回调方法。您可以使用 PeerHandle 参数来发送消息,或者 创建与该发布商的关联

要停止订阅某项服务,请调用 DiscoverySession.close()。 发现会话与其父级相关联 WifiAwareSession。如果父级会话关闭,其关联的发现会话也会随之关闭。舍弃时 对象也会关闭,系统无法保证何时超出范围 因此,我们建议您明确调用 close() 方法。

发送消息

要向另一台设备发送消息,您需要以下对象:

如需发送消息,请调用 sendMessage()。通过 之后可能会发生以下回调:

虽然需要 PeerHandle 才能与对等方通信,但您不应 必须将其用作对等设备的永久标识符应用可以使用更高级别的标识符,这些标识符嵌入在发现服务本身或后续消息中。您可以使用以下代码在发现服务中嵌入标识符: 该 setMatchFilter()setServiceSpecificInfo() PublishConfig 方法或 SubscribeConfig。通过 setMatchFilter() 方法会影响发现,而 setServiceSpecificInfo() 方法不会影响发现。

在消息中嵌入标识符意味着修改消息字节数组, 包含标识符(例如,作为前几个字节)。

创建连接

WLAN 感知功能支持两个 WLAN 感知设备之间的客户端-服务器网络连接。

要设置客户端-服务器连接,请执行以下操作:

  1. 使用 WLAN 感知发现功能发布服务(在 并订阅服务(在 客户端)。

  2. 订阅者发现该发布者后 从订阅者向发布者发送消息

  3. 在发布商设备上启动 ServerSocket,并设置或获取其端口:

    Kotlin

    val ss = ServerSocket(0)
    val port = ss.localPort

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. 使用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);
  5. 发布商请求广告联盟后 向订阅者发送消息

  6. 订阅者收到发布者发来的消息后,使用与发布者相同的方法在订阅者设备上请求 WLAN 感知网络。正确做法 未指定端口 NetworkSpecifier。通过 并在连接到网络时调用相应的回调方法, 可用、已更改或丢失。

  7. 对订阅者调用 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);
  8. 完成网络连接后,请调用 unregisterNetworkCallback()

对等设备测距和位置感知发现

具有 Wi-Fi RTT 位置信息的设备 功能可以直接测量与同伴的距离,并使用这些信息 限制 Wi-Fi 感知服务发现。

Wi-Fi RTT API 允许使用其 MAC 地址或其 PeerHandle

WLAN 感知发现功能可以被限制为仅发现特定地理围栏内的服务。例如,您可以设置一个地理围栏, 如果设备发布 "Aware_File_Share_Service_Name" 服务,但该服务没有 3 米(以 3,000 毫米)为单位,10 米以内 (指定为 10,000 毫米)。

要启用地理围栏,发布者和订阅者都必须采取操作:

  • 发布者必须使用 setRangingEnabled(true) 对发布的服务启用测距。

    如果发布者未启用测距,则会忽略订阅者指定的任何地理围栏限制,并执行正常发现而忽略距离。

  • 订阅者必须使用 setMinDistanceMmsetMaxDistanceMm 的某种组合来指定地理围栏。

    对于任一值,未指定的距离表示没有限制。仅指定最大距离意味着最小距离为 0。仅指定 最小距离表示没有最大值。

在地理围栏内发现对等服务时, onServiceDiscoveredWithinRange 回调,从而提供到对等设备的测量距离。通过 然后,可根据需要调用直接 Wi-Fi RTT API,以测量距离 。