跳到主要内容

移动端弱网、离线和重试策略

📶 移动网络不可靠:切换、丢包、延迟时刻发生。居安思危的设计是找用户体验的预先。

弱网条件下的策略

请求超时与重试

// OkHttp 配置
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(RetryInterceptor(maxRetries = 3))
.build()

// 指数退灯重试
class RetryInterceptor(val maxRetries: Int) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
while (true) {
try {
return chain.proceed(chain.request())
} catch (e: IOException) {
if (++attempt > maxRetries) throw e
Thread.sleep((2.0.pow(attempt) * 1000).toLong()) // 指数退灯
}
}
}
}

离线缓存策略

数据分层缓存

// Room 数据库作为本地缓存
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
fun getUsers(): Flow<List<User>> = flow {
// 1. 先发射缓存数据
val cached = dao.getAll()
if (cached.isNotEmpty()) emit(cached)

// 2. 拉取网络数据
try {
val fresh = api.getUsers()
dao.insertAll(fresh) // 更新缓存
emit(fresh)
} catch (e: IOException) {
if (cached.isEmpty()) throw e // 缓存也没有才投错
}
}
}

缓存有效期设计

@Entity
data class CachedData(
val id: String,
val data: String,
val updatedAt: Long = System.currentTimeMillis()
)

fun CachedData.isExpired(ttlMs: Long = 5 * 60 * 1000): Boolean {
return System.currentTimeMillis() - updatedAt > ttlMs
}

请求队列与离线演播

// 将遇到网络错误的请求存入一个队列并当网络恢复后重试
class RequestQueue(private val db: AppDatabase) {

suspend fun enqueue(request: PendingRequest) {
db.requestDao().insert(request)
}

suspend fun processQueue() {
val pending = db.requestDao().getPending()
pending.forEach { req ->
try {
executeRequest(req)
db.requestDao().markComplete(req.id)
} catch (e: IOException) {
db.requestDao().incrementRetryCount(req.id)
}
}
}
}

网络状态监听

// 监听网络变化
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
// 网络已恢复,处理队列
lifecycleScope.launch { requestQueue.processQueue() }
}

override fun onLost(network: Network) {
// 网络断开,切换离线模式
showOfflineMode()
}
}

connectivityManager.registerDefaultNetworkCallback(networkCallback)

常见误区

  • 所有接口都用同一个重试次数,忽略了隐影更新操作不能重试
  • 缓存没有设置过期时间,导致展示过时数据
  • 离线队列没有限制尽小重试次数,网络恢复后大量请求市发