跪拜 Guibai
← Back to the summary

How a Robot Vacuum App Fixed Map Jank, Cache Staleness, and P2P Lag

Androidiot Robot Vacuum Device Detail Page Map Loading Slow and Lag Optimization Plan

1. Background

1.1 The current TanGe device detail page has several experience issues:

  1. Map loads slowly when entering the device detail page.
  2. When entering the detail page for the first time, the operation guide and loading display order conflict.
  3. After adding map caching, the map does not refresh in some scenarios.
  4. After map management switches, deletes, or saves a map, the detail page may continue to read the old cache.
  5. After quick mapping succeeds, there is a delay in cloud map generation, and the detail page map does not refresh in time.
  6. During cleaning, map cache refresh may cause the map to jump.
  7. After killing the App and re-entering, the map displays first, then the forbidden zones appear later, visually out of sync.
  8. After path data is cached, frequently killing the App / re-entering causes trajectory or robot point jumps.
  9. P2P connection is slow, causing real-time data like maps, paths, and forbidden zones to arrive late.
  10. Rapidly switching between the device list and the detail page can cause path and map data loss.

1.2 This modification revolves around the following goals:


2. What Problems This Modification Solves

2.1 First Entry Operation Guide and Loading Order Problem

Original Problem

When entering the device detail page for the first time:

Modified Logic

Target flow:

First time entering detail page

-> Show operation guide

-> Return to detail page

-> Show gif loading once

Second and subsequent entries to detail page

-> Do not show gif loading

Key Points

Resolution Result


2.2 Map Cache Causing No Refresh After Map Management Changes

Original Problem

After adding map cache, the detail page reads the local cache first:

Enter detail page

-> Read local map cache

-> Quickly display map

But the following scenarios cause the cache to become stale:

If the cache is not cleared, the next entry to the detail page will show the old map.

Modified Solution

Encapsulate a unified entry in AbstractMapDataFetcher:

open fun invalidateCurrentUsedMapCache() {}

open fun reloadCurrentUsedMapCache(delayMillis: Long = 0L) {

getMultiMapsFromCloud()

}

Implement the actual logic in TanGeMapImpl:

override fun reloadCurrentUsedMapCache(delayMillis: Long) {

clearCurrentUsedMapCache()

tanGeDevice.scope.launch {

if (delayMillis > 0) {

delay(delayMillis)

}

getMultiMapsFromCloud()

}

}

All map change scenarios uniformly call:

device.mapProxy.reloadCurrentUsedMapCache(...)

Covered Scenarios

Map Switch Successful

useMap success

-> Clear map cache

-> Pull cloud map list

-> Find currently used map

-> Update currentMap and cache

Map Deletion Successful

deleteMap success

-> Clear map cache

-> Pull cloud map list

-> Write cache if current map exists

-> Clear currentMap and cache if no current map

Delete All History Maps Successful

deleteSweeperHistoryData success

-> Clear cache

-> Pull cloud maps

-> Clear currentMap and cache if no map in cloud

Map Save Toggle Changes

Map save toggle change success

-> Clear cache

-> Pull cloud maps

Map Save / Replace Successful

replaceMap success

-> Clear cache

-> Pull cloud maps

Quick Mapping Successful

Quick mapping is special; the success callback usually only means "command sent successfully", not "cloud map has been generated".

So a delayed refresh is used:

Quick mapping success

-> Clear cache

-> Delay 5 seconds

-> Pull cloud maps

Resolution Result


2.3 Automatically Sync Cache After Cloud Map Returns

Original Problem

Cache was only used when the detail page reads it, but there was no stable cache update rule after the cloud map returns.

Modified Logic

TanGeMapImpl.getMultiMapsFromCloud() uniformly handles:

Cloud has currently used map

-> Update currentMap

-> Write cache

Cloud does not have currently used map

-> currentMap = null

-> Clear cache

Resolution Result


2.4 Map Jumping Problem During Cleaning

Original Problem

During cleaning, the real-time map is updating. If getMultiMapsFromCloud() is triggered at this time, it returns the static map from map management.

The original logic might directly:

currentMap.postValue(currentUsedMap)

Leading to:

Real-time cleaning map

-> Overwritten by map management static map

-> UI jump

Additionally, if the real-time map during cleaning is written to the "currently used map cache", the next entry might also read this temporary map.

Modified Principles

During cleaning / paused / relocating:

Non-cleaning state:

Modified Logic

Add state check in TanGeMapImpl.getMultiMapsFromCloud():

val shouldUpdateCurrentMap = shouldApplyMapManagerMapToCurrentMap()

if (currentUsedMap != null) {

if (shouldUpdateCurrentMap) {

currentMap.postValue(currentUsedMap)

}

cacheCurrentUsedMap(currentUsedMap)

}

In the real-time map cache hook:

override fun onCurrentMapUpdatedByRealtimeData(map: MapEditInfoBean) {

if (!isCleaningOrRelocating()) {

cacheCurrentUsedMap(map)

}

}

Covered States

Resolution Result


2.5 Forbidden Zone Data Loading Slower Than Map

Original Problem

After killing the App and re-entering the detail page:

Map cache displays first

Forbidden zone data displays only after device reports it

Leading to user seeing:

Map first

Forbidden zones appear later

Modified Solution

Forbidden zones are relatively stable configuration data, suitable for caching.

Add forbidden zone cache in TanGeFunctionality:

Receive forbidden zone data

-> Parse forbidden zones

-> Update forbiddenZone

-> Write to Hawk cache

During initialization:

restoreCachedMapAccessoryData()

-> restoreCachedForbiddenZone()

And forbidden zone restoration uses synchronous assignment:

tanGeDevice.forbiddenZone.value = array?.toList().orEmpty()

Instead of asynchronous:

postValue()

Resolution Result


2.6 Path Cache Causing Map Jump on Frequent Re-entry

Original Problem

Path data was initially also cached:

Receive path data

-> Write to Hawk

Kill App and re-enter

-> Restore old path

-> Wait for new path report

-> Jump from old path to new path

Path during cleaning is strongly real-time data, not suitable for persistence.

Modified Solution

Path is no longer persistently cached.

Still retained within the current process:

currentPathData = extendedData.data

But not written to Hawk:

private fun cachePathData(pathData: ByteArray) {

clearCachedPathData()

}

Also clear old path cache during initialization:

restoreCachedMapAccessoryData()

-> restoreCachedForbiddenZone()

-> clearCachedPathData()

Resolution Result


3. Problems Encountered During This Modification

3.1 Timing Conflict Between Operation Guide and Loading

Initially tried to show map loading first, then pop up the operation guide. Later the requirement was adjusted to:

First entry: Operation guide -> Detail page loading

Therefore, the loading display needed to be removed from onCreate() and controlled by onChildResume() after the operation guide returns.


3.2 Map Cache Refresh Entry Too Scattered

Initially, map cache was only written in TanGeMapImpl.getMultiMapsFromCloud(). But there are many entry points for map changes:

If each place handles cache separately, it's easy to miss one.

Finally changed to a unified call:

reloadCurrentUsedMapCache()


3.3 Quick Mapping Success Does Not Equal Map Generation Complete

The quick mapping success callback only means the command was sent successfully; the cloud map might not be generated yet. If you pull the map immediately, it's often still old data.

Finally adopted:

Quick mapping success

-> Delay 5 seconds

-> Then pull cloud map

This solution handles most cases, but if the cloud is slower, polling enhancement might still be needed.


3.4 Conflict Between Map Management Data and Real-time Map During Cleaning

Map management map is a static currently used map. The map during cleaning is a dynamically changing map.

Both write to currentMap, overwriting each other and causing jumps.

The final solution is:

During cleaning, only allow real-time map to refresh currentMap

Map management data only refreshes the list and cache, does not overwrite currentMap


3.5 Forbidden Zones and Paths Cannot Be Cached the Same Way

Initially, paths were also attempted to be cached, but paths are real-time data, causing jumps on re-entry. Finally differentiated:

Forbidden zones: Configuration data, can be cached

Paths: Real-time data, not persistently cached


4. New Problems Caused by This Modification and Remaining Issues

4.1 New Problems Discovered and Resolved

Problem 1: Map Jumping During Cleaning

Cause:

getMultiMapsFromCloud() overwrites currentMap after returning

Solution:

Do not allow map management map to overwrite currentMap during cleaning


Problem 2: Path Cache Causing Jump on Re-entry

Cause:

Old path cache is restored, then new path overwrites it, UI jumps

Solution:

Path is no longer persistently cached


Problem 3: Forbidden Zone Restoration Later Than Map

Cause:

Map synchronous setValue

Forbidden zone asynchronous postValue

Solution:

Forbidden zone cache restoration changed to synchronous value


4.2 High-Risk Problems Currently Avoided

The following problems are currently avoided:


4.3 Remaining Legacy Issues

Remaining Issue 1: Quick Mapping Only Refreshes Once with Delay

Current quick mapping logic is:

Success callback

-> Delay 5 seconds

-> Pull cloud map once

If the device or cloud takes more than 5 seconds to generate the map, the latest map might still not be pulled.

A more stable solution is polling:

Quick mapping success

-> Pull every 5 seconds

-> Up to 3 to 5 times

-> Stop when map count changes or current map changes

Resolved.

Now it is:

Pull every 5 seconds

Up to 3 times

Stop when map count changes or current mapId changes


Remaining Issue 2: Forbidden Zone Cache May Briefly Show Old Configuration

Forbidden zones are configuration data, suitable for caching. But if the user modifies forbidden zones on another client, the current App, after being killed and re-entered, will briefly show the old forbidden zones first, then update after the device reports.

This is a common problem with cache-based solutions.

Acceptable reasons:

More stable solution:

Add version number / timestamp / map ID binding to forbidden zone cache

Clear forbidden zone cache when map ID changes


Now:

Forbidden zone cache bound to deviceId + mapId

Clear forbidden zone cache when map changes

Clear forbidden zone cache when cloud has no current map

There is still only one theoretical extreme case:

Forbidden zones for the same mapId are modified on another client

Current App re-enters offline

Because the protocol has no forbidden zone version number/update time, the client cannot determine if this cache is stale. This situation can only wait for device/P2P report to overwrite.

This is not a problem that can be completely eliminated by this code change, unless the backend or device protocol provides a forbidden zone version number.

Remaining Issue 3: Map Cache and Forbidden Zone Cache Not Strongly Bound to Map ID

Current cache keys are device-dimension:

tange_map_cache_deviceId

tange_forbidden_zone_cache_deviceId

If future devices support multiple maps and different maps have different forbidden zones, it's best to upgrade to:

tange_forbidden_zone_cache_deviceId_mapId

Currently, if forbidden zones themselves are device-global data, no change is needed.

Resolved for the forbidden zone part.

Now the forbidden zone cache key is:

tange_forbidden_zone_cache_deviceId_mapId

Map cache is still:

tange_map_cache_deviceId

This is reasonable, because the currently used map cache itself is a snapshot of the "currently used map". When switching maps, the cache is cleared and re-pulled; there is no need to persist a map cache for every single map.


5. Current Final Cache Strategy

5.1 Data That Can Be Cached

Data Cached Reason
Currently used map Yes Speed up first screen
Forbidden zones Yes Configuration data, stable
Cloud map list Indirect refresh Obtained by getMultiMapsFromCloud()
Map management currently used map Yes Map loading
Map save status Not cached separately Device status report is sufficient

5.2 Data Not Recommended for Persistent Caching

Data Cached Reason
Path data No Strongly real-time, caching causes jumps
Robot current position No Strongly real-time, caching causes jumps
Fixed-point cleaning points No Temporary task status
Zone cleaning area No Current task status
Area selection cleaning No Current task status
Cleaning area/time Not as map cache Real-time status report is sufficient
Battery Not as map cache Status report is sufficient

6. Recommended Final Data Flow

6.1 Entering Detail Page

init device

-> restoreCachedMapAccessoryData()

-> Restore forbidden zone cache

-> Clear old path cache

-> showCachedMapFirstIfAvailable()

-> Read currently used map cache

-> Set currentMap

-> mapDataReady = true

-> getMultiMapsFromCloud()

-> Refresh cloud map

-> Update currentMap in non-cleaning state

-> Update/clear map cache

-> P2P real-time data arrives

-> updateMapData()

-> Refresh real-time map

-> Write map cache only in non-cleaning state

-> Forbidden zone report

-> parseForbidZone()

-> Update forbiddenZone

-> Write forbidden zone cache

-> Path report

-> currentPathData = data

-> parseTanGePath()

-> Do not write persistent cache


6.2 Map Management Operations

Switch / Delete / Save toggle change

-> reloadCurrentUsedMapCache()

-> Clear cache

-> Pull cloud map

-> Update currentMap/cache


6.3 Quick Mapping

Quick mapping success

-> reloadCurrentUsedMapCache(5000L)

-> Clear cache

-> Delay 5 seconds

-> Pull cloud map


7. Industry Common Solutions for Slow P2P Connection

P2P slowness is usually not a single-point problem, but a combination of the following factors:

Common industry optimization solutions are as follows.


7.1 Pre-connect

Idea

Start connecting to the device before the user enters the detail page.

For example:

Homepage device list display

-> Pre-connect P2P for visible devices

-> Connection already established when user clicks detail page

Applicable

Note


7.2 Short-Term Keep-Alive

Idea

After the App exits the detail page, switches to background, or locks screen, do not immediately disconnect P2P, but keep it alive for a period.

Your current code already has a similar idea:

private static final long FOREGROUND_RECONNECT_GRACE_PERIOD_MS = 60_000L;

That is, if returning to the foreground within 5 seconds, do not reconnect.

Benefits

Note


7.3 Layered Loading: Cache First, P2P Supplement Later

Idea

P2P slowness cannot be completely avoided, so the UI should not completely wait for P2P.

Recommendation:

Layer 1: Local cache map/forbidden zones

Layer 2: Cloud map management data

Layer 3: P2P real-time map/path/status

Corresponding experience:

Immediately display cached map

-> Forbidden zone configuration synchronously restored

-> Cloud corrects current map

-> P2P real-time path/status supplemented

This is also the direction adopted by this modification.


7.4 Connection State Machine Management

Do not directly call in multiple places:

connect()

reconnect()

disconnect()

Recommend unified encapsulation of a state machine:

Idle

Connecting

Connected

Failed

Retrying

Disconnected

And handle:


7.5 Timeout and Degradation

Common Strategy

P2P connection successful within 3 seconds: Display real-time data

P2P connection exceeds 3 seconds: Continue displaying cache + loading

P2P connection exceeds 8 seconds: Prompt connection failure or weak network

P2P connection succeeds later: Automatically supplement real-time data

You currently have:

private static final long P2P_RECONNECT_TIMEOUT_MS = 8_000L;

This is reasonable.


7.6 LAN Priority, Cloud Relay Fallback

Common P2P connection paths in the industry:

LAN Direct

-> NAT P2P

-> TURN/Relay Cloud Relay

Optimization direction:


7.7 Connection Result Cache and Weak Network Diagnosis

Record:

Used for subsequent strategies:

Recent connection failures are frequent

-> Reduce pre-connection frequency

-> Prompt user to check network

Recent connections are fast

-> Allow short-term keep-alive


7.8 Decoupling Data Channel and UI

Do not make the detail page UI completely dependent on the first P2P packet.

Recommendation:

UI displays cached state

P2P connection proceeds independently

P2P data patches UI upon arrival

That is:


7.9 Avoid Repeated Initialization on Page Entry

One common reason for slowness is:

onCreate

-> init device

-> init functionality

-> query version

-> get cloud maps

-> connect p2p

-> request dp

Multiple requests sent out simultaneously actually slow things down.

Suggestion:


7.10 Fast Failure and Retry Backoff

Do not retry indefinitely at a fixed 1-second interval.

Recommendation:

First immediate retry

Second 1s

Third 2s

Fourth 4s

Then stop or wait for user trigger

Avoid:


8. Subsequent Suggestions

8.1 Change Quick Mapping Refresh to Polling

Currently, it pulls once after a 5-second delay.

Suggestion to upgrade to:

Quick mapping success

-> Clear cache

-> Pull map after 5 seconds

-> If map count/current map hasn't changed

-> Wait another 5 seconds

-> Up to 3 times

This can cover cases where cloud map generation is slower.


8.2 Bind Forbidden Zone Cache to mapId

If TanGe device forbidden zones are independent per map, it is recommended to upgrade the cache key from:

deviceId

to:

deviceId + mapId

To avoid briefly displaying the previous map's forbidden zones when switching between multiple maps.

In the industry, data like maps/forbidden zones/virtual walls generally doesn't rely solely on cache, nor solely on P2P reporting, but on three layers of data sources:

Layer 1: Local cache

Used for first-screen instant display, but not as the final authoritative data.

Layer 2: Cloud / Map management data

Used for quickly restoring configuration data after login, device change, or cache clearing.

Layer 3: P2P / Device real-time reporting

Serves as the final real-time correction data.

That is, the correct link should be:

Enter detail page

-> Read cache first, display if available

-> If no cache, restore forbidden zones from forbidData in map management cloud data

-> After P2P forbidden zone report, overwrite forbidden zones and write cache

After logging out and clearing cache, when logging back in, although there is no local cache, forbidden zones can still be restored from currentUsedMap.forbidData returned by getMultiMapsFromCloud().

8.3 Establish a Unified MapRuntimeCacheManager

Current cache logic has been unified into mapProxy, but can be further abstracted into:

MapRuntimeCacheManager

Manage:

This makes subsequent expansion safer.


9. Rapid Switching Between Device List and Detail Page:

Path and map data will be missing. The previous request device data mechanism limitation:

10. Modifications to Be Determined:

11. Currently Resolved Problems:

12. Cache Data Refresh Problem After Re-login:

Industry Common Solution:

In the industry, data like maps/forbidden zones/virtual walls generally doesn't rely solely on cache, nor solely on P2P reporting, but on three layers of data sources:

13. Summary

This modification achieved the following core goals:

  1. Fixed the first-time operation guide and loading display order.
  2. Fixed repeated display of gif loading on subsequent entries.
  3. Added currently used map cache to improve first-screen speed.
  4. Unified cache refresh after map switch, deletion, save, and quick mapping.
  5. Automatically write cache after cloud map returns, automatically clear cache when no map exists.
  6. Prevent map management static map from overwriting real-time map during cleaning, avoiding map jumps.
  7. Forbidden zone data supports caching, reducing the disjointed feeling of map appearing before forbidden zones.
  8. Path data cancels persistent caching, avoiding trajectory jumps after re-entry.
  9. Map refresh entry unified encapsulation, reducing the risk of future missed refreshes.
  10. Retain P2P real-time data as the final authoritative data; cache only optimizes first-screen experience.
  11. Fixed map path loss when rapidly switching between homepage and detail page during cleaning, removed the 5s no-request limit for map data.
  12. Login and logout cache issues.
  13. Forbidden zone data cache implemented with 3-level cache handling.
  14. Other issues and scenarios require extensive testing, modified according to actual needs.
  15. Remaining issue modifications.
  16. P2P pre-connection problem and solution implementation verification.