移动 App 网络请求最佳实践:超时、重连与弱网环境优化

📅 2026/7/31 7:55:58
移动 App 网络请求最佳实践:超时、重连与弱网环境优化
移动 App 网络请求最佳实践超时、重连与弱网环境优化在移动端开发中网络请求的稳定性直接影响用户体验。相比服务端移动网络具有高延迟、易中断、带宽波动大等特点。本文从超时策略、自动重连机制和弱网环境适配三个维度梳理一套可落地的优化方案。一、超时策略分级设置避免一刀切1. 区分场景设定超时时间不要对所有接口使用同一个超时值。推荐按业务重要性分级场景连接超时读取超时说明关键数据登录、支付10s15s需容忍短暂网络抖动常规列表/详情5s8s快速失败避免卡顿上传/下载大文件30s60s根据文件大小动态调整后台预加载3s5s非关键任务快速放弃2. 实现示例OkHttp Kotlinval client OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .build() // 对特定请求单独设置 val request Request.Builder() .url(url) .header(X-Timeout, fast) .build() val call client.newCall(request).apply { // 通过拦截器动态调整超时 }3. 渐进式超时策略对于重要接口可采用“先短后长”的策略第1次尝试连接5s读取8s → 失败 第2次尝试连接8s读取12s → 失败 第3次尝试连接12s读取20s → 成功降级体验注意每次重试间隔应递增如 500ms → 2s → 5s避免雪崩效应。二、自动重连机制有状态、有限度1. 幂等性检查只有幂等请求才能自动重试GET / HEAD / OPTIONS → 安全PUT / DELETE → 需确认后端是否幂等POST → 默认不重试除非业务层保证2. 指数退避算法fun retryDelay(attempt: Int): Long { val baseDelay 1000L // 1秒 val maxDelay 30000L // 30秒上限 return minOf(baseDelay * (1 shl attempt), maxDelay) } // 使用示例 repeat(maxRetries) { attempt - try { val response executeRequest() if (response.isSuccessful) break } catch (e: IOException) { if (attempt maxRetries - 1) throw e delay(retryDelay(attempt)) } }3. 智能触发条件不是所有错误都值得重试错误类型是否重试原因SocketTimeoutException✅可能瞬间网络波动UnknownHostException❌DNS 问题重试无效HTTP 503 Service Unavailable✅服务暂时过载HTTP 401 Unauthorized❌需要重新认证HTTP 429 Too Many Requests⏸️ 等待后重试限流需等待 Retry-After4. 连接池复用保持 TCP 连接存活减少三次握手开销val connectionPool ConnectionPool( maxIdleConnections 5, keepAliveDuration 5, TimeUnit.MINUTES )三、弱网环境优化让 App 更“耐抗”1. 网络状态感知实时监听网络变化动态调整请求行为class NetworkMonitor(private val context: Context) { fun getCurrentQuality(): NetworkQuality { val cm context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network cm.activeNetwork ?: return NetworkQuality.NONE val caps cm.getNetworkCapabilities(network) ?: return NetworkQuality.UNKNOWN return when { caps.linkDownstreamBandwidthKbps 150 - NetworkQuality.POOR caps.linkDownstreamBandwidthKbps 2000 - NetworkQuality.MODERATE else - NetworkQuality.GOOD } } } enum class NetworkQuality { NONE, POOR, MODERATE, GOOD, UNKNOWN }2. 差异化请求策略根据网络质量调整参数fun buildRequest(url: String, quality: NetworkQuality): Request { val timeoutMultiplier when (quality) { NetworkQuality.POOR - 3.0 // 弱网下超时延长3倍 NetworkQuality.MODERATE - 1.5 else - 1.0 } // 同时可以调整压缩、图片质量等 return Request.Builder() .url(url) .header(Accept-Encoding, if (quality NetworkQuality.POOR) gzip else identity) .build() }3. 离线优先架构核心思路先展示缓存再发起网络请求。用户操作 → 读取本地缓存 → 显示UI → 异步刷新 → 更新缓存对于列表页推荐使用 Room Paging 3 实现Dao interface ArticleDao { Query(SELECT * FROM articles ORDER BY updated_at DESC) fun getArticles(): PagingSourceInt, Article Insert(onConflict OnConflictStrategy.REPLACE) suspend fun insertAll(articles: ListArticle) } // Repository 层 class ArticleRepository(private val api: Api, private val dao: ArticleDao) { fun getArticles(): FlowPagingDataArticle { return Pager(PagingConfig(pageSize 20)) { ArticlePagingSource(api, dao) }.flow } }4. 请求合并与去抖高频场景下如搜索输入、滚动加载使用 debounce 合并请求searchQueryFlow .debounce(300) // 300ms内无新输入才发送 .distinctUntilChanged() .flatMapLatest { query - api.search(query) } .catch { /* 处理错误 */ } .collect { results - updateUI(results) }5. 图片加载优化图片往往是弱网下的最大瓶颈// Glide 配置示例 Glide.with(context) .load(url) .override(400, 400) // 限制分辨率 .format(DecodeFormat.PREFER_RGB_565) // 减少内存占用 .diskCacheStrategy(DiskCacheStrategy.DATA) // 缓存原始数据 .onlyRetrieveFromCache(true) // 弱网下只读缓存 .into(imageView) // 结合网络质量动态调整 if (networkQuality NetworkQuality.POOR) { imageUrl ?quality30width200 }6. 请求优先级队列为不同请求分配优先级确保关键请求优先执行enum class Priority { HIGH, MEDIUM, LOW } class PriorityQueueDispatcher { private val queues mapOf( Priority.HIGH to PriorityBlockingQueueRequest(), Priority.MEDIUM to PriorityBlockingQueueRequest(), Priority.LOW to PriorityBlockingQueueRequest() ) fun enqueue(request: Request, priority: Priority) { queues[priority]?.put(request) processNext() } private fun processNext() { for (p in Priority.values()) { queues[p]?.poll()?.let { execute(it); return } } } }四、监控与告警让问题可见1. 埋点关键指标DNS 解析耗时TCP 连接耗时TLS 握手耗时首字节时间TTFB总传输时间重试次数失败原因分布2. 日志上报策略{ event: network_request, properties: { url: /api/v1/articles, method: GET, status_code: 200, duration_ms: 2340, retry_count: 2, network_type: WIFI, signal_strength: -65, error_code: null } }3. 自动化测试使用 Charles / Wireshark 模拟弱网# macOS 上使用 Network Link Conditioner # 配置丢包率 5%带宽 100KB/s延迟 500msCI/CD 中加入弱网测试用例Test fun testRequestUnderPoorNetwork() { // 模拟弱网环境 val mockInterceptor MockInterceptor().apply { setDelay(2000) // 2秒延迟 setFailureRate(0.3) // 30%失败率 } val client OkHttpClient.Builder() .addInterceptor(mockInterceptor) .build() // 验证重试逻辑 val response client.newCall(request).execute() assertTrue(response.isSuccessful || retryCount 3) }总结移动端网络优化的核心原则分层处理超时、重试、缓存各司其职互不干扰感知环境根据网络质量动态调整策略而非固定配置容错设计假设网络会失败优雅降级而非崩溃数据驱动用真实监控数据指导优化方向最后记住永远不要在 UI 线程做网络请求这是所有优化的基础前提。