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 测距的流程串起来:
这张图把整个流程分成了 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