公司动态
Androidiot扫地机器人详情页地图加载慢卡顿优化方案
Androidiot扫地机器人详情页地图加载慢卡顿优化方案1. 背景1.1 当前探鸽设备详情页存在几个体验问题进入设备详情页时地图加载慢。第一次进入详情页时操作指引和 loading 展示顺序冲突。加入地图缓存后部分场景地图不刷新。地图管理切换、删除、保存地图后详情页可能继续读取旧缓存。快速建图成功后云端地图生成有延迟详情页地图没有及时刷新。清扫中地图缓存刷新可能导致地图跳变。杀死 App 后重新进入地图先显示禁区后显示视觉上不同步。路径数据缓存后频繁杀 App / 重新进入会导致轨迹或机器点跳变。P2P 连接较慢导致地图、路径、禁区等实时数据到达较晚。快速切换设备列表和详情页路径和地图会有丢失情况1.2 本次修改围绕以下目标展开- 第一次进入详情页先显示操作指引再进入详情页显示一次 loading。- 第二次及以后进入详情页不显示 gif loading。- 地图缓存只用于提升首屏地图显示速度。- 地图管理、快速建图、保存/删除/切换地图后缓存必须刷新或失效。- 清扫中不能因为缓存或地图管理数据导致实时地图跳变。- 禁区可以缓存路径不持久化缓存。- 所有地图相关变更统一走缓存刷新入口避免散落逻辑导致漏刷新。-快速切换设备列表和详情页不做5s限制每次都全量请求数据—2. 此次修改解决了哪些问题2.1 首次进入详情页操作指引和 loading 顺序问题原问题第一次进入设备详情页时- 详情页会立即显示 loading。- 同时固件信息回调里又会弹出OperationGuidelineActivity。- 操作指引覆盖了 loading导致用户看不到加载动画。- 后续每次进入也可能继续显示 gif loading。修改后逻辑目标流程text第一次进入详情页- 显示操作指引- 返回详情页- 显示一次 gif loading第二次及以后进入详情页- 不显示 gif loading关键点使用pendingShowFirstGifLoading标记操作指引返回后需要展示 loading。showGifLoading()内部通过HawkUtil.getFirstStartMap()判断是否为第一次。第一次显示后立即HawkUtil.setFirstStartMap(false)。后续进入详情页直接隐藏 loading。解决结果首次进入顺序正确。后续不再重复显示 gif loading。避免操作指引覆盖 loading。避免 loading 每次进入都闪一下。2.2 地图缓存导致地图管理变更后不刷新的问题原问题加了地图缓存后详情页会先读本地缓存进入详情页- 读取本地地图缓存- 快速显示地图但以下场景会导致缓存过期地图切换成功。地图删除成功。删除全部历史地图成功。地图保存开关变化。保存/替换地图成功。快速建图成功。云端地图无当前使用地图。如果不清缓存下次进详情页会看到旧地图。修改后方案在AbstractMapDataFetcher中封装统一入口open fun invalidateCurrentUsedMapCache() {}open fun reloadCurrentUsedMapCache(delayMillis: Long 0L) {getMultiMapsFromCloud()}在TanGeMapImpl中实现实际逻辑override fun reloadCurrentUsedMapCache(delayMillis: Long) {clearCurrentUsedMapCache()tanGeDevice.scope.launch {if (delayMillis 0) {delay(delayMillis)}getMultiMapsFromCloud()}}所有地图变更场景都统一调用device.mapProxy.reloadCurrentUsedMapCache(…)覆盖场景地图切换成功useMap 成功- 清地图缓存- 拉云端地图列表- 找当前使用地图- 更新 currentMap 和缓存地图删除成功deleteMap 成功- 清地图缓存- 拉云端地图列表- 有当前地图则写缓存- 无当前地图则清 currentMap 和缓存删除全部历史地图成功deleteSweeperHistoryData 成功- 清缓存- 拉云端地图- 云端无地图时清 currentMap 和缓存地图保存开关变化地图保存开关变化成功- 清缓存- 拉云端地图地图保存 / 替换成功replaceMap 成功- 清缓存- 拉云端地图快速建图成功快速建图比较特殊成功回调通常只是“命令发送成功”不是“云端地图已经生成”。所以使用延迟刷新快速建图成功- 清缓存- 延迟 5 秒- 拉云端地图解决结果地图管理变更后不会继续读旧缓存。地图切换、删除、保存、快速建图后都有统一刷新入口。页面不再直接操作 Hawk 缓存减少遗漏。2.3 云端地图回来后自动同步缓存原问题缓存只在详情页读取时使用但云端地图回来后没有形成稳定的缓存更新规则。修改后逻辑TanGeMapImpl.getMultiMapsFromCloud()统一负责云端有当前使用地图- 更新 currentMap- 写入缓存云端没有当前使用地图- currentMap null- 清缓存解决结果云端数据成为缓存的权威来源。缓存不会长期停留在旧状态。云端地图为空时旧缓存会被清除。2.4 清扫中地图跳变问题原问题清扫过程中实时地图正在更新。如果此时触发getMultiMapsFromCloud()返回的是地图管理中的静态地图。原逻辑可能直接currentMap.postValue(currentUsedMap)导致实时清扫地图- 被地图管理静态地图覆盖- UI 跳变另外清扫中实时地图如果写入“当前使用地图缓存”下次进入也可能读到临时地图。修改后原则清扫中 / 暂停中 / 重定位中- 不允许地图管理地图覆盖 currentMap- 不把实时清扫地图写入当前使用地图缓存非清扫状态- 正常更新 currentMap- 正常写缓存修改后逻辑在TanGeMapImpl.getMultiMapsFromCloud()中增加状态判断val shouldUpdateCurrentMap shouldApplyMapManagerMapToCurrentMap()if (currentUsedMap ! null) {if (shouldUpdateCurrentMap) {currentMap.postValue(currentUsedMap)}cacheCurrentUsedMap(currentUsedMap)}在实时地图缓存 hook 中override fun onCurrentMapUpdatedByRealtimeData(map: MapEditInfoBean) {if (!isCleaningOrRelocating()) {cacheCurrentUsedMap(map)}}覆盖状态SmartCleaningRelocating其他isWorkingOrPause()包含的工作/暂停状态解决结果清扫中实时地图不会被地图管理静态地图覆盖。清扫中不会把临时实时地图写入持久化缓存。避免清扫中地图跳变。非清扫状态地图管理功能仍然正常。2.5 禁区数据加载比地图慢的问题原问题杀死 App 后重新进入详情页地图缓存先显示禁区数据等设备上报后才显示导致用户看到先有地图过一会儿禁区才出现修改后方案禁区属于相对稳定配置数据适合缓存。在TanGeFunctionality中增加禁区缓存收到禁区数据- 解析禁区- 更新 forbiddenZone- 写入 Hawk 缓存初始化时restoreCachedMapAccessoryData()- restoreCachedForbiddenZone()并且禁区恢复使用同步赋值tanGeDevice.forbiddenZone.value array?.toList().orEmpty()而不是异步postValue()解决结果冷启动时可以同步恢复禁区。禁区和地图缓存展示时机更接近。后续设备实时上报禁区后仍会覆盖缓存。2.6 路径缓存导致频繁重进地图跳变的问题原问题路径数据一开始也做了缓存收到路径数据- 写入 Hawk杀死 App 后重进- 恢复旧路径- 等新路径上报- 从旧路径跳到新路径清扫中路径是强实时数据不适合持久化。修改后方案路径不再持久化缓存。当前进程内仍然保留currentPathData extendedData.data但不写 Hawkprivate fun cachePathData(pathData: ByteArray) {clearCachedPathData()}初始化时也清掉旧路径缓存restoreCachedMapAccessoryData()- restoreCachedForbiddenZone()- clearCachedPathData()解决结果杀 App 后不会恢复旧路径。避免旧路径到新路径的跳变。当前运行期间仍能正常实时绘制路径。禁区缓存不受影响。3. 此次所有修改遇到的问题3.1 操作指引和 loading 的时机冲突最初尝试让地图 loading 先显示再弹操作指引。后来需求调整为第一次进入操作指引 - 详情页 loading因此需要把 loading 展示从onCreate()中移除改为操作指引返回后的onChildResume()控制。3.2 地图缓存刷新入口太分散最开始地图缓存只在TanGeMapImpl.getMultiMapsFromCloud()里写入。但地图变化的入口有很多详情页保存地图地图管理切换地图地图管理删除地图删除全部历史地图地图保存开关变化快速建图如果每个地方单独处理缓存容易漏掉。最终改为统一调用reloadCurrentUsedMapCache()3.3 快速建图成功不等于地图生成完成快速建图成功回调只是命令发送成功云端地图可能还没生成。如果立即拉地图经常还是旧数据。最终采用快速建图成功- 延迟 5 秒- 再拉云端地图这个方案能解决大部分情况但如果云端更慢仍可能需要轮询增强。3.4 清扫中地图管理数据和实时地图冲突地图管理地图是静态当前使用地图。清扫中的地图是实时变化地图。两者都写currentMap会互相覆盖造成跳变。最终方案是清扫中只允许实时地图刷新 currentMap地图管理数据只刷新列表和缓存不覆盖 currentMap3.5 禁区和路径不能同样缓存最初路径也尝试缓存但路径属于实时数据会导致重进后跳变。最后区分为禁区配置数据可以缓存路径实时数据不持久化缓存4. 此次修改引起的新问题及遗留问题4.1 已发现并解决的新问题问题一清扫中地图跳变原因getMultiMapsFromCloud() 返回后覆盖 currentMap解决清扫中不允许地图管理地图覆盖 currentMap问题二路径缓存导致重进跳变原因旧路径缓存被恢复随后新路径覆盖UI 跳变解决路径不再持久化缓存问题三禁区恢复晚于地图原因地图同步 setValue禁区异步 postValue解决禁区缓存恢复改为同步 value4.2 当前没有继续保留的高风险问题以下问题当前已规避旧地图缓存长期残留。删除地图后仍显示旧地图。切换地图后详情页继续显示旧缓存。清扫中被静态地图覆盖。路径缓存导致轨迹跳变。后续进入详情页重复显示 gif loading。4.3 仍然存在的遗留问题遗留问题一快速建图只延迟刷新一次当前快速建图逻辑是成功回调- 延迟 5 秒- 拉一次云端地图如果设备或云端生成地图超过 5 秒仍可能拉不到最新地图。更稳的方案是轮询快速建图成功- 每 5 秒拉一次- 最多 3 到 5 次- 拉到地图数量变化或当前地图变化后停止已解决。现在是每 5 秒拉一次最多 3 次地图数量变化或当前 mapId 变化后停止遗留问题二禁区缓存可能短时间展示旧配置禁区是配置数据适合缓存。但如果用户在其他端修改禁区当前 App 杀死后重进短时间内会先显示旧禁区等设备上报后再更新。这是缓存类方案的通用问题。可接受原因禁区变化频率低。实时上报会覆盖。比“地图先出、禁区后出”体验更好。更稳方案禁区缓存增加版本号 / 时间戳 / 地图 ID 绑定地图 ID 变更时清禁区缓存现在禁区缓存绑定 deviceId mapId地图变化时清禁区缓存云端无当前地图时清禁区缓存仍然只存在一种理论极端情况同一张 mapId 的禁区在其他端被修改当前 App 离线重进因为协议里没有禁区版本号/更新时间客户端无法判断这份缓存是否过期这种情况只能等设备/P2P 上报后覆盖。这个不是本次代码能完全消除的问题除非后端或设备协议提供禁区版本号遗留问题三地图缓存和禁区缓存没有强绑定地图 ID当前缓存 key 是设备维度tange_map_cache_deviceIdtange_forbidden_zone_cache_deviceId如果未来设备支持多地图且不同地图有不同禁区最好升级为tange_forbidden_zone_cache_deviceId_mapId当前如果禁区本身是设备全局数据则不用改。已解决禁区部分。现在禁区缓存 key 是tange_forbidden_zone_cache_deviceId_mapId地图缓存仍是tange_map_cache_deviceId这是合理的因为当前使用地图缓存本身就是“当前使用地图”的一份快照地图切换时会清缓存并重拉不需要每张地图都持久化一份地图缓存。5. 当前最终缓存策略5.1 可以缓存的数据数据是否缓存原因当前使用地图是首屏提速禁区是配置型数据稳定云端地图列表间接刷新由getMultiMapsFromCloud()获取地图管理当前使用地图是地图加载地图保存状态不单独缓存设备状态上报即可5.2 不建议持久化缓存的数据数据是否缓存原因路径数据否强实时缓存会跳变机器当前位置否强实时缓存会跳变定点清扫点否临时任务状态划区清扫区域否当前任务状态选区清扫否当前任务状态清扫面积/时间不作为地图缓存实时状态上报即可电量不作为地图缓存状态上报即可6. 推荐的最终数据流6.1 进入详情页init device- restoreCachedMapAccessoryData()- 恢复禁区缓存- 清旧路径缓存- showCachedMapFirstIfAvailable()- 读取当前使用地图缓存- 设置 currentMap- mapDataReady true- getMultiMapsFromCloud()- 刷新云端地图- 非清扫状态更新 currentMap- 更新/清理地图缓存- P2P 实时数据回来- updateMapData()- 刷新实时地图- 非清扫状态才写地图缓存- 禁区上报- parseForbidZone()- 更新 forbiddenZone- 写禁区缓存- 路径上报- currentPathData data- parseTanGePath()- 不写持久化缓存6.2 地图管理操作切换 / 删除 / 保存开关变化- reloadCurrentUsedMapCache()- 清缓存- 拉云端地图- 更新 currentMap/cache6.3 快速建图快速建图成功- reloadCurrentUsedMapCache(5000L)- 清缓存- 延迟 5 秒- 拉云端地图7. 行业内 P2P 连接慢的通用解决方案P2P 慢通常不是单点问题而是由以下因素叠加设备低功耗休眠。NAT 穿透慢。局域网发现慢。云端中转兜底慢。TLS/鉴权握手耗时。App 进入页面才开始连接。设备端同时连接数限制。网络弱、路由器隔离、IPv6/IPv4 切换等问题。行业常见优化方案如下。7.1 预连接 Pre-connect思路在用户进入详情页之前就开始连接设备。例如首页设备列表展示- 对可见设备预连接 P2P- 用户点击详情页时连接已建立适用设备详情页依赖实时视频/地图/P2P 数据。用户大概率会点击某个设备。注意不要对所有设备无限预连接。要限制数量例如只预连接最近使用设备或当前可见设备。避免占用设备连接数。探鸽sdk预建联最多支持3台设备所以此方案不可行如果用户频繁切换首页和详情页多设备同时全量请求也会有问题7.2 短时间保活思路App 从详情页退出、切后台、锁屏后不立刻断开 P2P而是保活一段时间。你当前代码已有类似思路private static final long FOREGROUND_RECONNECT_GRACE_PERIOD_MS 60_000L;即 5秒内回到前台不重新连接。好处避免频繁断开/重连。用户短时间切后台回来地图/路径可继续快速显示。注意保活时间不要太长。设备低功耗产品要考虑功耗。后台策略要符合系统限制。7.3 分层加载缓存先行P2P 后补思路P2P 慢不可完全避免因此 UI 不应该完全等 P2P。推荐第一层本地缓存地图/禁区第二层云端地图管理数据第三层P2P 实时地图/路径/状态对应体验立即显示缓存地图- 禁区配置同步恢复- 云端校正当前地图- P2P 实时路径/状态补齐这也是本次修改采用的方向。7.4 连接状态机管理不要在多个地方直接调用connect()reconnect()disconnect()建议统一封装状态机IdleConnectingConnectedFailedRetryingDisconnected并处理避免重复 connect。避免多个页面同时 reconnect。失败重试加退避。超时兜底。前后台状态统一处理。7.5 超时与降级通用策略P2P 连接 3 秒内成功展示实时数据P2P 连接超过 3 秒继续展示缓存 loadingP2P 连接超过 8 秒提示连接失败或弱网P2P 后续连接成功自动补齐实时数据你当前已有private static final long P2P_RECONNECT_TIMEOUT_MS 8_000L;这是合理的。7.6 局域网优先云端中转兜底行业内常见 P2P 连接路径LAN 直连- NAT P2P- TURN/Relay 云端中转优化方向同 Wi-Fi 下优先 LAN。NAT 穿透失败快速切 Relay。不要长时间卡在单一路径。记录每种路径耗时做策略优化。7.7 连接结果缓存和弱网诊断记录最近一次连接成功时间。最近一次失败错误码。平均连接耗时。是否局域网直连。是否中转。设备是否低功耗休眠。当前网络类型 Wi-Fi / 4G / 5G。用于后续策略最近连接失败频繁- 降低预连接频率- 提示用户检查网络最近连接很快- 允许短时间保活7.8 数据通道和 UI 解耦不要让详情页 UI 完全依赖 P2P 首包。推荐UI 展示缓存态P2P 连接独立进行P2P 数据到达后 patch UI即地图缓存用于首屏。禁区缓存用于首屏。路径不缓存等待实时数据。设备状态、电量、清扫时间可以先展示上次状态或占位。7.9 避免进入页面重复初始化常见慢的原因之一是onCreate- init device- init functionality- query version- get cloud maps- connect p2p- request dp多个请求同时打出去反而拖慢。建议设备对象复用。功能配置只在必要时刷新。地图云端请求和 P2P 请求并行但互不阻塞。页面返回后短时间保留设备连接。7.10 快速失败与重试退避重试不要固定 1 秒无限重试。推荐首次立即重试第二次 1s第三次 2s第四次 4s之后停止或等待用户触发避免电量消耗。设备端连接压力。多页面重复重连。8. 后续建议8.1 快速建图刷新改成轮询当前是延迟 5 秒拉一次。建议升级为快速建图成功- 清缓存- 5 秒后拉地图- 如果地图数量/当前地图没变化- 再等 5 秒- 最多 3 次这样能覆盖云端生成地图更慢的情况。8.2 禁区缓存绑定 mapId如果探鸽设备禁区是每张地图独立的建议把缓存 key 从deviceId升级为deviceId mapId避免多地图切换时短暂显示上一张地图的禁区。行业里做地图/禁区/虚拟墙这类数据一般不会只靠缓存也不会只靠 P2P 上报而是三层数据源第 1 层本地缓存用于首屏秒开但不作为最终权威数据。第 2 层云端 / 地图管理数据用于登录后、换设备后、缓存被清后快速恢复配置数据。第 3 层P2P / 设备实时上报作为最终实时校正数据。也就是说正确链路应该是进入详情页- 先读缓存有就显示- 缓存没有就从地图管理云端数据里的 forbidData 恢复禁区- P2P 禁区上报后再覆盖禁区并写缓存退出登录清缓存后重新登录时虽然没有本地缓存但仍可以从getMultiMapsFromCloud()返回的currentUsedMap.forbidData里恢复禁区。8.3 建立统一 MapRuntimeCacheManager当前缓存逻辑已经统一到mapProxy但后续还可以抽象成MapRuntimeCacheManager管理当前地图缓存禁区缓存缓存版本mapId 绑定清扫状态保护过期策略这样后续扩展更安全。9.快速切换设备列表和详情页路径和地图数据会缺少之前的请求设备数据机制限制由于之前的需求是退到后台5s内断开连接,所以连接还在但是请求设备全量地图数据有限制5s内不请求如果地图路径在退出详情页之前是10帧返回首页后绘制到13-15帧此次如果快速切换在5s内就不会请求地图数据导致刷新丢失5s后又请求了全量数据所以会刷新路径、地图等实时数据都会有跳变不同步问题解决方法去掉5s限制但是可能会有大量数据频繁请求问题多设备同时请求数据量很大10.待确定的修改退出登录是否清除缓存若不清除下次登录可能会出现回充中等状态还是之前的缓存地图若清除这个地图的显示逻辑是按之前的需求还是新需求有待确定(没有地图之前是显示回充中状态有地图是显示地图)11.当前已解决的问题退出登录静态清理没有设备 ID 的问题地图缓存清理禁区缓存清理历史路径缓存清理禁区缓存按mapId绑定快速建图改成轮询刷新清扫中地图不被地图管理数据覆盖路径不持久化避免重进跳变退出登录禁区数据没有及时刷新显示遗留问题修改12.重新登录后缓存数据刷新问题行业内通用方案行业里做地图/禁区/虚拟墙这类数据一般不会只靠缓存也不会只靠 P2P 上报而是三层数据源第 1 层本地缓存用于首屏秒开但不作为最终权威数据。第 2 层云端 / 地图管理数据用于登录后、换设备后、缓存被清后快速恢复配置数据。第 3 层P2P / 设备实时上报作为最终实时校正数据。也就是说正确链路应该是进入详情页- 先读缓存有就显示- 缓存没有就从地图管理云端数据里的 forbidData 恢复禁区- P2P 禁区上报后再覆盖禁区并写缓存退出登录清缓存后重新登录时虽然没有本地缓存但仍可以从getMultiMapsFromCloud()返回的currentUsedMap.forbidData里恢复禁区。提前建立p2p连接方案:进入设备详情页前把需要的地图、路径、禁区等全量数据提取准备好这样全量拉取按数据在多个设备会有线程并发和服务器同时请求压力过大设备响应卡死问题最多支持3个设备超过3个设备只取前3个后面的设备App利用线程池或者协程排队获取要不然还是会有问题.13. 总结此次修改完成了以下核心目标修复首次操作指引和 loading 展示顺序。修复后续重复显示 gif loading。增加当前使用地图缓存提高首屏速度。地图切换、删除、保存、快速建图后统一刷新缓存。云端地图回来后自动写缓存无地图时自动清缓存。清扫中不允许地图管理静态地图覆盖实时地图避免地图跳变。禁区数据支持缓存减少地图先出禁区后出的割裂感。路径数据取消持久化缓存避免重进后轨迹跳变。地图刷新入口统一封装降低后续漏刷风险。保留 P2P 实时数据作为最终权威数据缓存只做首屏体验优化。清扫中快速切换首页和详情页地图路径丢失去掉5s内不请求地图数据限制登录和退出登录缓存问题。禁区数据缓存做3级缓存处理。其他问题和场景需要大量测试根据实际需求修改。遗留问题修改。