The Android Developer Challenge is back! Submit your idea before December 2.

通过 RTT 确定 WLAN 位置信息

您可以利用 WLAN RTT(往返时间)API 提供的 WLAN 定位功能,测量距附近支持 RTT 的 WLAN 接入点和 WLAN 感知对等设备的距离。

如果您测量与三个或更多接入点的距离,则可以使用多点定位算法来预估与这些测量值最相符的设备位置。结果通常可以精确到 1 至 2 米。

凭借这种精准度,您可以开发基于精确位置的服务,例如室内导航、无歧义语音控制(例如,“打开这盏灯”)以及基于位置的信息(例如,“此产品是否有特别优惠?”)。

请求发出设备无需连接到接入点即可通过 WLAN RTT 测量距离。为维护隐私,只有发出请求的设备能够确定距接入点的距离,接入点没有此信息。前台应用执行 WLAN RTT 操作不受限制,但后台应用执行此类操作时会受限。

WLAN RTT 和相关精确时间测量 (FTM) 功能由 IEEE 802.11mc 标准规定。WLAN RTT 需要 FTM 提供的精确时间测量,因为前者通过测量数据包在设备之间往返所需的时间,并将该时间乘以光速来计算两个设备之间的距离。

实现差异因 Android 版本而异

WLAN RTT 在 Android 9(API 级别 28)中引入。使用此协议在运行 Android 9 的设备中使用多点定位来确定设备的位置时,您需要有权访问应用中预先确定的接入点 (AP) 位置数据。存储和检索此类数据的方式由您决定。

在运行 Android 10 (API level 29) and higher 的设备上,AP 位置数据可以表示为 ResponderLocation 对象,其中包括纬度、经度和海拔高度。对于支持位置配置信息/位置城市报告(LCI/LCR 数据)的 WLAN RTT AP,协议会在测距过程中返回 ResponderLocation 对象。

此功能支持应用查询 AP,以直接询问 AP 的位置,而无需提前存储此信息。因此,即使之前不知道 AP(例如用户进入新建筑物时),您的应用也可以找到 AP 并确定其位置。

要求

  • 测距请求发出设备的硬件必须实现 802.11mc FTM 标准。
  • 测距请求发出设备必须运行 Android 9(API 级别 28)或更高版本的操作系统。
  • 测距请求发出设备必须启用位置服务并打开 WLAN 扫描(Settings > Location)。
  • 测距请求发出设备必须拥有 ACCESS_FINE_LOCATION 权限。
  • 接入点必须实现 IEEE 802.11mc FTM 标准。

设置

如要将应用设置为使用 WLAN RTT,请执行以下步骤。

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.ACCESS_FINE_LOCATION" />

ACCESS_FINE_LOCATION 权限属于危险权限,因此每次用户要执行 RTT 扫描操作时,您都需要在运行时请求该权限。如果尚未获得授权,则应用需要向用户请求该权限。如需详细了解运行时权限,请参阅请求应用权限

2. 检查设备是否支持 WLAN RTT

如要检查设备是否支持 WLAN RTT,请使用 PackageManager API:

Kotlin

context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)

Java

context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);

3. 检查 WLAN RTT 是否可用

设备上可能存在 WLAN RTT,但由于用户已禁用 WLAN,该功能目前或不可用。如果 SoftAP 或网络共享处于使用状态,则某些设备可能不支持 WLAN RTT,具体视设备的硬件和固件功能而定。如要检查 WLAN RTT 当前是否可用,请调用 isAvailable()

WLAN RTT 的可用性随时可能发生变化。您的应用应注册一个 BroadcastReceiver,以接收 ACTION_WIFI_RTT_STATE_CHANGED(系统会在可用性发生变化时发送该内容)。当应用收到广播 Intent 时,其应检查可用性的当前状态,并对其行为进行相应调整。

例如:

Kotlin

val filter = IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED)
val myReceiver = object: BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (wifiRttManager.isAvailable) {
            …
        } else {
            …
        }
    }
}
context.registerReceiver(myReceiver, filter)

Java

IntentFilter filter =
    new IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
BroadcastReceiver myReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (wifiRttManager.isAvailable()) {
            …
        } else {
            …
        }
    }
};
context.registerReceiver(myReceiver, filter);

如需了解详细信息,请参阅广播

创建测距请求

通过指定请求范围的 AP 或 WLAN 感知对等设备的列表,即可创建测距请求 (RangingRequest)。您可以在单个测距请求中指定多个接入点或 WLAN 感知对等设备,然后测量并返回与所有设备的距离。

例如,请求可以使用 addAccessPoint() 方法指定要测量距离的接入点:

Kotlin

val req: RangingRequest = RangingRequest.Builder().run {
    addAccessPoint(ap1ScanResult)
    addAccessPoint(ap2ScanResult)
    build()
}

Java

RangingRequest.Builder builder = new RangingRequest.Builder();
builder.addAccessPoint(ap1ScanResult);
builder.addAccessPoint(ap2ScanResult);

RangingRequest req = builder.build();

接入点由其 ScanResult 对象标识,该对象可通过调用 WifiManager.getScanResults() 获得。您可以使用 addAccessPoints(List) 批量添加多个接入点。

与之类似,测距请求可以通过以下两种途径添加 WLAN 感知对等设备:使用 addWifiAwarePeer(MacAddress 对等点) 方法利用请求的 MAC 地址,或者使用 addWifiAwarePeer(PeerHandle 对等点) 方法利用请求的 PeerHandle。如需了解发现 WLAN 感知对等设备的更多信息,请参阅 WLAN 感知文档

请求测距

应用使用 WifiRttManager.startRanging() 方法发出测距请求,并提供以下内容:用于指定操作的 RangingRequest、用于指定回调上下文的 Executor,以及用于接收结果的 RangingResultCallback

例如:

Kotlin

val mgr = context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as WifiRttManager
val request: RangingRequest = myRequest
mgr.startRanging(request, executor, object : RangingResultCallback() {

    override fun onRangingResults(results: List<RangingResult>) { … }

    override fun onRangingFailure(code: Int) { … }
})

Java

WifiRttManager mgr =
      (WifiRttManager) Context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE);

RangingRequest request ...;
mgr.startRanging(request, executor, new RangingResultCallback() {

  @Override
  public void onRangingFailure(int code) { … }

  @Override
  public void onRangingResults(List<RangingResult> results) { … }
});

测距操作以异步方式执行;测距结果在 RangingResultCallback 的某个回调中返回:

  • 如果整个测距操作失败,则会触发 onRangingFailure 回调,并返回 RangingResultCallback 中描述的状态代码。如果该服务当时出于某些原因(例如 WLAN 遭到禁用、应用请求的测距操作过多并受到限制,或者存在权限问题)无法执行测距操作,则可能会发生此类失败。
  • 测距操作完成后,会触发 onRangingResults 回调,并返回与请求列表匹配的结果列表(每个请求匹配一个结果)。结果的顺序不一定与请求的顺序一致。请注意,测距操作可能已经完成,但每个结果仍有可能提示该特定测量失败,

解释测距结果

onRangingResults 回调返回的每个结果均由 RangingResult 对象指定。请对每个请求执行以下操作。

1. 识别请求

根据创建 RangingRequest 时提供的信息来识别请求。该信息通常是在 ScanResult 中提供的 MAC 地址,用于识别接入点。您可以使用 getMacAddress() 方法从测距结果中获得 MAC 地址。

测距结果列表的顺序可能与测距请求中指定的对等设备(接入点)的顺序不同,因此您应使用 MAC 地址而非结果的顺序来识别对等设备。

2. 确定每个测量是否成功

如要确定测量是否成功,请使用 getStatus() 方法。STATUS_SUCCESS 以外的任何值都表示失败。失败意味着此结果的所有其他字段(上述请求标识除外)均为无效字段,相应的 get* 方法也将失败,并显示 IllegalStateException 异常。

3. 获取每个成功测量的结果

对于每个成功的测量,您可以使用相应的 get 方法检索结果值: