15、WiFi RTT(室内定位):WifiRttManager介绍、发起RTT测距请求、获取测距结果(Android 9+)

说到室内定位,大家第一反应可能是蓝牙 Beacon 或者 UWB。但 Android 从 9.0 开始,其实内置了一套基于 WiFi 的精准测距方案——WiFi RTT(Round Trip Time)。说白了,就是让手机和 WiFi 接入点(AP)之间通过来回飞行时间算距离,精度能到 1-2 米。

我最早接触这个功能是在一个商场导航项目里。当时客户要求室内定位精度在 3 米以内,还不能额外部署硬件。我翻了一圈文档,发现 WiFi RTT 正好满足需求——只要 AP 支持 802.11mc 协议就行。嗯,今天我们就来聊聊这个。

核心要点:WiFi RTT 是 Android 9+ 原生支持的室内定位方案,通过测量信号往返时间计算距离,精度可达 1-2 米。需要 AP 支持 IEEE 802.11mc 协议。

15.1 WifiRttManager 是什么?

WifiRttManager 是 Android 框架里专门负责 RTT 测距的系统服务。你想想看,它就像是一个“测距调度中心”——你告诉它要测哪些 AP,它帮你发起请求、收集结果、回调给你。

获取方式很简单:

val rttManager = context.getSystemService(Context.WIFI_RTT_RANGING_SERVICE) as? WifiRttManager

不过要注意,这个服务不是所有设备都有。我记得有一次在测试机上跑 demo,死活拿不到实例,后来才发现那台手机硬件不支持 RTT。所以使用前一定要做检查:

if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)) {
    // 设备不支持 RTT,走降级方案
    return
}

注意:即使 Android 版本 >= 9,也不代表设备一定支持 RTT。必须同时满足:硬件支持 + 系统服务可用 + 应用有 ACCESS_FINE_LOCATION 权限。

15.2 发起 RTT 测距请求

发起请求前,你需要先拿到目标 AP 的信息。这里分两种情况:

  • 扫描到的 AP:通过 WifiManager.startScan() 获取 ScanResult,里面包含 AP 的 MAC 地址和信道信息。
  • 已知的 AP:手动构造 RangingRequest,指定 MAC 地址。

我个人习惯用扫描的方式,因为可以顺便拿到信号强度做辅助判断。来看代码:

// 1. 构造测距请求
val request = RangingRequest.Builder().run {
    // 添加要测距的 AP(可以添加多个)
    scanResults.forEach { result ->
        if (result.is80211mcResponder) {  // 检查是否支持 RTT
            addAccessPoint(result)
        }
    }
    build()
}

// 2. 发起测距
val executor = ContextCompat.getMainExecutor(context)
rttManager?.startRanging(request, executor, object : RangingResultCallback() {
    override fun onRangingResults(results: List<RangingResult>) {
        // 测距成功,处理结果
    }

    override fun onRangingFailure(code: Int) {
        // 测距失败,处理错误
    }
})

这里有个坑——is80211mcResponder 这个字段。我曾经在调试时发现,有些 AP 明明支持 802.11mc,但扫描结果里这个字段却是 false。后来查了文档才知道,这个字段需要 AP 在 Beacon 帧里主动声明支持,有些厂商没开这个广播。所以我的建议是:不要完全依赖这个字段,可以尝试对所有 AP 发起测距,看哪些能返回结果。

小技巧:如果测距请求一直失败,可以试试先调用 WifiManager.startScan() 刷新扫描结果,再发起 RTT。有时候缓存的信息会导致信道不匹配。

15.3 获取测距结果

测距结果通过 RangingResult 对象返回。每个对象对应一个 AP 的测距数据,主要字段如下:

字段 类型 说明
getDistanceMm() int 距离,单位毫米(mm)
getDistanceStdDevMm() int 距离标准差,单位毫米
getRssi() int 信号强度,单位 dBm
getMacAddress() MacAddress AP 的 MAC 地址
getStatus() int 测距状态(成功/失败)

拿到距离后,怎么用?如果你有多个 AP 的测距结果,可以用三边定位算法算出手机位置。不过这里我要提醒一句:

避坑指南:我曾经在商场里测试,发现 RTT 测距结果偶尔会跳变——比如前一秒距离 3 米,下一秒变成 8 米。这是因为多径效应和信号遮挡导致的。我的处理方式是:对连续 3-5 次结果做滑动平均滤波,能有效减少抖动。

另外,getDistanceStdDevMm() 这个值很有用。标准差越小,说明这次测距越可靠。我一般会设置一个阈值,比如标准差超过 1 米的结果直接丢弃:

results.filter { it.distanceStdDevMm < 1000 }  // 只保留标准差小于1米的结果

15.4 整体流程与架构

说了这么多,我们来画一张图,把整个 RTT 测距的流程串起来:

WiFi RTT 测距完整流程 1. 权限检查 ACCESS_FINE_LOCATION 2. 获取Manager WifiRttManager 3. 扫描AP WifiManager.startScan() 过滤 802.11mc 5. 构造请求 RangingRequest.Builder 6. 发起测距 startRanging() 7. 回调结果 RangingResultCallback 8. 处理测距结果 距离 / 标准差 / RSSI 9. 三边定位 / 滤波输出

这张图把整个流程分成了 9 步。从权限检查开始,到最终的定位输出。我个人觉得最关键的是第 4 步(过滤 AP)和第 8 步(结果处理)——前者决定了你能测到多少 AP,后者决定了定位精度。

15.5 实战中的注意事项

最后,再分享几个我在项目中踩过的坑:

  • 权限问题:Android 10 以后,ACCESS_FINE_LOCATION 必须动态申请,而且用户拒绝后无法后台定位。我建议在发起 RTT 前先检查权限状态。
  • 测距频率:不要频繁调用 startRanging()。我一般控制在 1-2 秒一次,太频繁会导致 AP 响应不过来,反而丢包。
  • 多 AP 融合:如果周围有 3 个以上支持 RTT 的 AP,定位效果会好很多。少于 3 个时,建议结合 RSSI 做辅助定位。
  • 硬件兼容性:不同厂商的 WiFi 芯片对 RTT 的支持程度不一样。高通和博通的芯片表现较好,联发科有些老芯片会有延迟偏高的问题。

我的经验:如果你在开发室内定位功能,建议先拿几台主流机型(Pixel、三星、小米)做兼容性测试。RTT 在 Android 原生支持上没问题,但各家厂商的定制 ROM 可能会有差异。

好了,关于 WiFi RTT 的基础用法就聊到这里。记住核心三点:检查支持、构造请求、处理结果。剩下的就是根据你的业务场景做算法优化了。


公众号:蓝海资料掘金营,微信 deep3321