Android史诗级网络优化实践总结

在 Android 开发中,很多网络性能问题并不是接口本身慢,而是请求在真正到达服务端之前,就已经被 DNS 解析、跨网访问、弱网重试、TLS 握手、连接复用不足、CDN 调度不合理 等环节拖慢了。

一个接口从 App 发出到拿到响应,大致会经历下面这些过程:

text 复制代码
App 发起请求
  ↓
DNS 解析域名
  ↓
选择 IP / CDN 节点
  ↓
建立 TCP 连接
  ↓
TLS 握手
  ↓
发送 HTTP 请求
  ↓
服务端或 CDN 响应
  ↓
读取响应数据
  ↓
业务解析和渲染

如果只盯着服务端接口耗时,很容易忽略移动端网络链路中的大头。尤其在国内复杂网络环境下,LocalDNS 劫持、跨运营商访问、CDN 节点调度不准、IPv6/IPv4 选择不佳,都可能让一个原本 100ms 的接口变成 1 秒甚至超时。

本文会从两个层面展开:

  1. 通用方案:如何通过 CDN 和 DNS 优化移动端网络请求。
  2. OkHttp 实践方案:Android 项目中如何落地自定义 DNS、HTTPDNS、连接复用、缓存、失败切换和监控上报。

一、先明确目标:我们到底要优化什么?

移动端网络优化的核心目标不是"把所有请求都变快",而是让用户关键路径上的请求:

  • 更快:减少 DNS、连接、TLS、传输耗时。
  • 更稳:弱网、跨网、DNS 异常时有兜底。
  • 更省:减少重复请求、重复握手、重复下载。
  • 可观测:知道慢在哪里,而不是凭感觉调参。
  • 可灰度:DNS、CDN、域名策略可以动态调整。

所以,一个成熟的移动端网络优化体系通常包括:

text 复制代码
CDN 加速
DNS 优化
连接复用
HTTP 缓存
失败重试
多域名容灾
弱网降级
监控上报
动态配置

二、CDN 优化:让请求离用户更近

CDN 的核心价值是:就近接入、减少跨网、降低源站压力、提升稳定性

对于 Android App 来说,CDN 不只是图片加速,它还可以承载很多非强实时的资源请求。

1. 哪些资源适合走 CDN?

适合走 CDN 的资源包括:

类型 是否推荐 CDN 说明
图片 推荐 头像、封面、缩略图、详情图等
视频/音频 推荐 通常需要专用 CDN 和分片能力
H5 静态资源 推荐 JS、CSS、字体、HTML 模板
APK / 补丁包 推荐 大文件下载,建议单独下载域名
配置文件 推荐 适合短缓存 + 本地兜底
公共 GET 接口 视情况 不含用户态、可缓存时适合
登录/支付/下单接口 不推荐缓存 可走加速转发,但不要缓存
上传接口 单独处理 通常走对象存储或上传加速域名

核心原则:

静态、公共、可缓存、可版本化的资源,优先走 CDN。强实时、强鉴权、强一致性的业务接口,不要盲目缓存。


2. CDN 缓存策略:URL 版本化是关键

很多 CDN 缓存问题不是 CDN 本身的问题,而是 URL 设计不合理。

推荐做法是:资源 URL 版本化

例如:

text 复制代码
https://cdn.example.com/static/main.8f3a2c.js
https://cdn.example.com/app/config/v23/config.json
https://cdn.example.com/image/avatar/123_200x200.webp

这样可以放心给静态资源设置长缓存:

http 复制代码
Cache-Control: public, max-age=31536000, immutable
ETag: "xxx"
Last-Modified: Wed, 03 Jun 2026 10:00:00 GMT

不同资源可以采用不同缓存策略:

资源类型 推荐缓存策略
带 hash 的 JS/CSS/图片 长缓存,半年到一年
App 配置文件 短缓存,30 秒到 5 分钟
运营配置 短缓存 + 本地缓存兜底
用户态接口 通常不缓存
大文件 长缓存 + 断点续传

需要注意的是:

  • 不要让同一个固定 URL 的内容频繁变化。
  • 资源更新应该通过 URL 变化,而不是强制刷新 CDN。
  • 配置类资源要有本地缓存兜底,避免 CDN 短时异常导致 App 不可用。

3. 图片和大文件优化

Android App 中图片和大文件通常是流量大户。

图片优化建议:

  • 使用 WebP/AVIF 等更高压缩率格式。
  • 根据设备尺寸请求合适分辨率,不要下载原图再本地缩放。
  • 缩略图、预览图、原图使用不同 URL。
  • 图片 URL 参数规范化,避免 CDN 缓存碎片。

例如:

text 复制代码
https://cdn.example.com/image/123?w=200&h=200&format=webp
https://cdn.example.com/image/123?w=1080&format=webp

大文件优化建议:

  • 使用独立下载域名,避免占用 API 域名连接。
  • 支持 Range 请求和断点续传。
  • 下载任务支持暂停、恢复、失败重试。
  • 对大文件做 MD5/SHA256 校验。
  • 使用 CDN 分片和就近调度。

4. 多 CDN 和多域名容灾

对于核心业务,可以准备多个 CDN 或多个加速域名:

text 复制代码
cdn-a.example.com
cdn-b.example.com
cdn-c.example.com

容灾策略可以是:

text 复制代码
主 CDN 请求失败
  ↓
判断是否为安全 GET 请求
  ↓
切换备用 CDN 域名
  ↓
再次请求
  ↓
记录失败域名并短时间降权

但要注意:

  • 不要为了容灾把域名拆得太碎。
  • 域名越多,连接复用越差。
  • HTTP/2 对同 host 的连接复用收益很高。
  • 多域名切换更适合静态资源,不适合支付、登录、下单等强业务接口。

推荐做法:

text 复制代码
api.example.com       核心 API
cdn.example.com       静态资源
upload.example.com    上传
download.example.com  大文件下载

按照业务类型拆域名,而不是每个模块都随意建一个域名。


三、DNS 优化:别让请求输在起跑线上

一次网络请求的第一步通常是 DNS 解析。DNS 解析慢、解析错、解析到跨网 IP,都会让后续请求变慢。

1. 移动端常见 DNS 问题

Android App 经常遇到这些 DNS 问题:

  • LocalDNS 被运营商劫持。
  • DNS 缓存污染。
  • 解析结果跨运营商或跨地域。
  • DNS TTL 不可控。
  • 弱网下 DNS 解析超时。
  • IPv6/IPv4 选择不合理。
  • 网络切换后仍使用旧 IP。
  • CDN 节点调度不准确。

这些问题的后果通常是:

text 复制代码
DNS 慢 → 首包慢
IP 不优 → 连接慢
跨网访问 → 传输慢
DNS 异常 → 请求失败
IP 失效 → 重试慢

2. HTTPDNS 是移动端常用方案

HTTPDNS 的基本思路是:App 不完全依赖系统 LocalDNS,而是通过 HTTP/HTTPS 请求 DNS 服务,拿到更准确的 IP 列表。

text 复制代码
App
  ↓
HTTPDNS 服务
  ↓
返回 api.example.com 对应的 IP 列表
  ↓
App 按质量排序
  ↓
OkHttp 使用这些 IP 建连

HTTPDNS 的优势:

  • 绕过 LocalDNS 劫持。
  • 可以根据用户出口 IP、地域、运营商返回更优 IP。
  • 可以做 IP 级别容灾。
  • TTL 更可控。
  • 可以和业务监控联动,动态调整调度策略。

但 HTTPDNS 也不是银弹:

  • HTTPDNS 服务自身需要高可用。
  • HTTPDNS 失败时必须回退系统 DNS。
  • 不能无限缓存 IP。
  • HTTPS 场景不能粗暴把域名替换成 IP。

3. DNS 缓存设计

推荐维护一份本地 DNS 缓存:

kotlin 复制代码
data class DnsRecord(
    val host: String,
    val ips: List<String>,
    val expireAt: Long,
    val source: String, // httpdns / system / cache
    val updatedAt: Long
)

缓存策略建议:

  • 尊重 HTTPDNS 返回的 TTL。
  • 设置最小 TTL,避免频繁解析。
  • 设置最大 TTL,避免 IP 长时间失效。
  • 允许弱网下短时间使用过期缓存。
  • 网络从 Wi-Fi 切到蜂窝时刷新核心域名。
  • 请求失败时对相关 IP 降权,而不是立刻全量清空。

一个简单的策略:

text 复制代码
缓存未过期 → 使用缓存
缓存已过期但网络差 → 临时使用旧缓存,同时异步刷新
HTTPDNS 成功 → 更新缓存
HTTPDNS 失败 → 回退系统 DNS
系统 DNS 也失败 → 使用可接受时间内的旧缓存兜底

4. IP 质量排序

HTTPDNS 返回多个 IP 时,不应该永远使用第一个。

可以综合这些因素排序:

  • 最近成功率
  • 最近失败时间
  • TCP 连接耗时
  • TLS 握手耗时
  • 请求总耗时
  • 当前网络类型
  • IPv6/IPv4 可用性
  • 服务端返回错误类型

推荐排序逻辑:

text 复制代码
近期成功 IP
  > 低 RTT IP
  > 未失败 IP
  > HTTPDNS 推荐顺序
  > 系统 DNS 结果

注意区分失败类型:

失败类型 是否应该降权 IP
TCP connect timeout 应该
DNS 解析失败 不针对单 IP
TLS 握手失败 谨慎,需要排查证书/SNI
HTTP 5xx 谨慎,可能是服务端问题
HTTP 4xx 不应该
SocketTimeoutException 视阶段而定

四、OkHttp 实战:把 CDN 和 DNS 优化真正落到代码里

OkHttp 是 Android 生态里非常常见的网络库。它本身已经提供了连接池、HTTP/2、缓存、拦截器、事件监听、自定义 DNS 等能力。

我们可以基于这些能力搭建一套完整的网络优化方案。


五、通过 OkHttp Dns 接口接入 HTTPDNS

最推荐的方式是实现 OkHttp 的 Dns 接口,而不是手动替换 URL 中的 host。

1. 基础版自定义 Dns

kotlin 复制代码
class HttpDns(
    private val httpDnsClient: HttpDnsClient,
    private val enableHosts: Set<String>
) : Dns {

    override fun lookup(hostname: String): List<InetAddress> {
        // 只处理白名单域名,避免影响三方 SDK、支付、登录等特殊域名
        if (!enableHosts.contains(hostname)) {
            return Dns.SYSTEM.lookup(hostname)
        }

        val ips = try {
            httpDnsClient.query(hostname)
        } catch (e: Exception) {
            emptyList()
        }

        if (ips.isNotEmpty()) {
            val result = ips.mapNotNull { ip ->
                try {
                    InetAddress.getByName(ip)
                } catch (e: Exception) {
                    null
                }
            }

            if (result.isNotEmpty()) {
                return result
            }
        }

        // 兜底系统 DNS
        return Dns.SYSTEM.lookup(hostname)
    }
}

使用方式:

kotlin 复制代码
val client = OkHttpClient.Builder()
    .dns(
        HttpDns(
            httpDnsClient = myHttpDnsClient,
            enableHosts = setOf(
                "api.example.com",
                "cdn.example.com",
                "upload.example.com"
            )
        )
    )
    .build()

这类方案的好处是:

  • 请求 URL 仍然保持域名形式。
  • HTTPS 证书校验不会被破坏。
  • SNI 仍然可以正常工作。
  • Cookie、重定向、HTTP/2 连接复用更稳定。

2. 加入本地缓存和失败兜底

实际项目中,lookup() 不应该每次都请求 HTTPDNS。可以加入缓存:

kotlin 复制代码
class DnsCache {
    private val cache = ConcurrentHashMap<String, DnsRecord>()

    fun get(host: String): List<String>? {
        val record = cache[host] ?: return null
        return if (System.currentTimeMillis() < record.expireAt) {
            record.ips
        } else {
            null
        }
    }

    fun put(host: String, ips: List<String>, ttlMillis: Long) {
        cache[host] = DnsRecord(
            host = host,
            ips = ips,
            expireAt = System.currentTimeMillis() + ttlMillis,
            source = "httpdns",
            updatedAt = System.currentTimeMillis()
        )
    }
}

增强版 DNS:

kotlin 复制代码
class SmartDns(
    private val httpDnsClient: HttpDnsClient,
    private val dnsCache: DnsCache,
    private val ipQualityStore: IpQualityStore,
    private val enableHosts: Set<String>
) : Dns {

    override fun lookup(hostname: String): List<InetAddress> {
        if (!enableHosts.contains(hostname)) {
            return Dns.SYSTEM.lookup(hostname)
        }

        val cachedIps = dnsCache.get(hostname)
        if (!cachedIps.isNullOrEmpty()) {
            return toInetAddress(ipQualityStore.sort(hostname, cachedIps))
        }

        val httpDnsIps = try {
            httpDnsClient.query(hostname)
        } catch (e: Exception) {
            emptyList()
        }

        if (httpDnsIps.isNotEmpty()) {
            dnsCache.put(hostname, httpDnsIps, ttlMillis = 60_000)
            return toInetAddress(ipQualityStore.sort(hostname, httpDnsIps))
        }

        return Dns.SYSTEM.lookup(hostname)
    }

    private fun toInetAddress(ips: List<String>): List<InetAddress> {
        return ips.mapNotNull { ip ->
            try {
                InetAddress.getByName(ip)
            } catch (e: Exception) {
                null
            }
        }
    }
}

六、不同业务使用不同 OkHttpClient

不要把所有请求都塞进一个完全相同配置的 OkHttpClient

推荐按业务类型拆分:

text 复制代码
apiClient       核心 API 请求
cdnClient       图片、配置、静态资源
uploadClient    上传请求
downloadClient  大文件下载

示例:

kotlin 复制代码
val connectionPool = ConnectionPool(
    10,
    5,
    TimeUnit.MINUTES
)

val baseClient = OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .retryOnConnectionFailure(true)
    .connectTimeout(5, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(10, TimeUnit.SECONDS)
    .eventListenerFactory { NetworkEventListener() }
    .build()

val apiClient = baseClient.newBuilder()
    .dns(apiDns)
    .build()

val cdnClient = baseClient.newBuilder()
    .dns(cdnDns)
    .cache(Cache(File(context.cacheDir, "cdn_cache"), 100L * 1024 * 1024))
    .build()

val uploadClient = baseClient.newBuilder()
    .dns(uploadDns)
    .connectTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(60, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build()

val downloadClient = baseClient.newBuilder()
    .dns(downloadDns)
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .build()

这样做的好处是:

  • API 不会被大文件下载拖慢。
  • 上传可以使用更长的 writeTimeout。
  • CDN 资源可以单独开启缓存。
  • 不同域名可以使用不同 DNS 策略。
  • 监控指标更容易按业务类型分析。

七、CDN 资源启用 OkHttp 缓存

OkHttp 支持 HTTP 缓存,适合 GET 静态资源和部分配置类请求。

kotlin 复制代码
val cache = Cache(
    directory = File(context.cacheDir, "okhttp_cache"),
    maxSize = 100L * 1024 * 1024
)

val cdnClient = OkHttpClient.Builder()
    .cache(cache)
    .build()

如果服务端缓存头配置合理,客户端通常不需要做太多额外逻辑。

服务端推荐返回:

http 复制代码
Cache-Control: public, max-age=300
ETag: "config-v23"

客户端请求时,OkHttp 会根据缓存策略自动处理缓存复用和条件请求。

如果服务端缓存头暂时不完善,可以通过拦截器做临时兜底:

kotlin 复制代码
class CacheControlInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)

        val url = request.url.toString()
        val isStaticResource =
            url.endsWith(".png") ||
            url.endsWith(".jpg") ||
            url.endsWith(".webp") ||
            url.endsWith(".json")

        return if (request.method == "GET" && isStaticResource) {
            response.newBuilder()
                .header("Cache-Control", "public, max-age=300")
                .build()
        } else {
            response
        }
    }
}

不过更推荐从服务端和 CDN 层正确配置缓存头,客户端不要长期维护复杂的缓存补丁逻辑。


八、DNS 预解析:减少首个请求等待时间

对于关键域名,可以在 App 启动、用户登录后、进入关键页面前做预解析。

kotlin 复制代码
class DnsPreloader(
    private val dns: Dns,
    private val executor: ExecutorService
) {
    fun preload(hosts: List<String>) {
        hosts.distinct().forEach { host ->
            executor.execute {
                try {
                    dns.lookup(host)
                } catch (_: Exception) {
                    // ignore
                }
            }
        }
    }
}

使用:

kotlin 复制代码
dnsPreloader.preload(
    listOf(
        "api.example.com",
        "cdn.example.com",
        "upload.example.com"
    )
)

注意事项:

  • 不要在主线程预解析。
  • 不要一次预解析几十个域名。
  • 网络切换后重新预解析核心域名。
  • 预解析结果必须遵守 TTL。
  • 预解析失败不能影响正常请求。

九、CDN 失败切换:只对安全请求做容灾

对于静态资源 GET 请求,可以在主 CDN 失败后切换备用 CDN。

kotlin 复制代码
class CdnFallbackInterceptor(
    private val backupHosts: List<String>
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        if (request.method != "GET") {
            return chain.proceed(request)
        }

        try {
            val response = chain.proceed(request)
            if (response.isSuccessful) {
                return response
            }
            response.close()
        } catch (_: IOException) {
            // try backup hosts
        }

        for (backupHost in backupHosts) {
            val newUrl = request.url.newBuilder()
                .host(backupHost)
                .build()

            val newRequest = request.newBuilder()
                .url(newUrl)
                .build()

            try {
                val response = chain.proceed(newRequest)
                if (response.isSuccessful) {
                    return response
                }
                response.close()
            } catch (_: IOException) {
                // try next host
            }
        }

        return chain.proceed(request)
    }
}

配置:

kotlin 复制代码
val cdnClient = OkHttpClient.Builder()
    .addInterceptor(
        CdnFallbackInterceptor(
            backupHosts = listOf(
                "cdn-b.example.com",
                "cdn-c.example.com"
            )
        )
    )
    .build()

这里有一个重要原则:

CDN 域名切换更适合静态 GET 请求,不要随便用于登录、支付、下单、写入类接口。

原因是写入类接口可能存在幂等性、鉴权、签名、风控、重放等问题。盲目重试或切域名,可能造成重复下单、重复支付、状态不一致。


十、用 EventListener 建立网络性能监控闭环

只做优化不做监控,最后一定会变成玄学。

OkHttp 的 EventListener 可以记录一次请求的完整生命周期,包括:

  • DNS 开始和结束
  • TCP 连接开始和结束
  • TLS 握手开始和结束
  • 请求头发送
  • 响应头接收
  • 请求结束或失败

示例:

kotlin 复制代码
class NetworkEventListener : EventListener() {

    private var callStartNs: Long = 0
    private var dnsStartNs: Long = 0
    private var connectStartNs: Long = 0
    private var secureConnectStartNs: Long = 0

    override fun callStart(call: Call) {
        callStartNs = System.nanoTime()
    }

    override fun dnsStart(call: Call, domainName: String) {
        dnsStartNs = System.nanoTime()
    }

    override fun dnsEnd(
        call: Call,
        domainName: String,
        inetAddressList: List<InetAddress>
    ) {
        val costMs = (System.nanoTime() - dnsStartNs) / 1_000_000
        log("dns cost=$costMs ms, host=$domainName, ips=$inetAddressList")
    }

    override fun connectStart(
        call: Call,
        inetSocketAddress: InetSocketAddress,
        proxy: Proxy
    ) {
        connectStartNs = System.nanoTime()
    }

    override fun connectEnd(
        call: Call,
        inetSocketAddress: InetSocketAddress,
        proxy: Proxy,
        protocol: Protocol?
    ) {
        val costMs = (System.nanoTime() - connectStartNs) / 1_000_000
        log("connect cost=$costMs ms, address=$inetSocketAddress, protocol=$protocol")
    }

    override fun secureConnectStart(call: Call) {
        secureConnectStartNs = System.nanoTime()
    }

    override fun secureConnectEnd(call: Call, handshake: Handshake?) {
        val costMs = (System.nanoTime() - secureConnectStartNs) / 1_000_000
        log("tls cost=$costMs ms")
    }

    override fun responseHeadersEnd(call: Call, response: Response) {
        val costMs = (System.nanoTime() - callStartNs) / 1_000_000
        log("ttfb cost=$costMs ms, code=${response.code}, url=${call.request().url}")
    }

    override fun callEnd(call: Call) {
        val costMs = (System.nanoTime() - callStartNs) / 1_000_000
        log("call cost=$costMs ms, url=${call.request().url}")
    }

    override fun callFailed(call: Call, ioe: IOException) {
        val costMs = (System.nanoTime() - callStartNs) / 1_000_000
        log("call failed cost=$costMs ms, url=${call.request().url}, error=$ioe")
    }

    private fun log(message: String) {
        // 上报到 APM、日志系统或内部监控平台
        println(message)
    }
}

配置:

kotlin 复制代码
val client = OkHttpClient.Builder()
    .eventListenerFactory { NetworkEventListener() }
    .build()

建议上报这些核心指标:

指标 作用
DNS 耗时 判断 DNS 是否拖慢首请求
TCP 连接耗时 判断 IP、网络、跨网质量
TLS 握手耗时 判断连接复用、证书链、协议问题
TTFB 判断 CDN/服务端响应速度
总耗时 用户真实体感
HTTP 状态码 区分网络失败和业务失败
IP 地址 分析 CDN 节点和 IP 质量
网络类型 区分 Wi-Fi、4G、5G、弱网
协议类型 分析 HTTP/1.1、HTTP/2 效果
失败异常 判断是否需要切 IP 或切 CDN

十一、IP 质量库:让调度越来越聪明

有了监控数据,就可以做一个简单的 IP 质量库。

kotlin 复制代码
data class IpStat(
    val ip: String,
    var rttMs: Long = Long.MAX_VALUE,
    var failCount: Int = 0,
    var lastFailedAt: Long = 0L
)

class IpQualityStore {
    private val map = ConcurrentHashMap<String, IpStat>()

    fun sort(host: String, ips: List<String>): List<String> {
        val now = System.currentTimeMillis()

        return ips.sortedWith(
            compareBy<String> { ip ->
                val stat = map["$host-$ip"]
                val recentlyFailed = stat != null && now - stat.lastFailedAt < 60_000
                if (recentlyFailed) 1 else 0
            }.thenBy { ip ->
                map["$host-$ip"]?.rttMs ?: Long.MAX_VALUE
            }
        )
    }

    fun markSuccess(host: String, ip: String, rttMs: Long) {
        val key = "$host-$ip"
        val stat = map.getOrPut(key) { IpStat(ip) }
        stat.rttMs = rttMs
        stat.failCount = 0
    }

    fun markFailed(host: String, ip: String) {
        val key = "$host-$ip"
        val stat = map.getOrPut(key) { IpStat(ip) }
        stat.failCount += 1
        stat.lastFailedAt = System.currentTimeMillis()
    }
}

这套逻辑可以逐步演进:

第一阶段:只做 DNS 耗时和请求耗时上报。

第二阶段:接入 HTTPDNS 和本地缓存。

第三阶段:根据失败率对 IP 降权。

第四阶段:结合地域、运营商、网络类型做动态调度。

第五阶段:配置中心下发域名、CDN、IP 优先级策略。


十二、不要踩坑:HTTPS 下不要粗暴替换 IP

很多人接入 HTTPDNS 时会这么做:

text 复制代码
原始 URL:
https://api.example.com/user/info

替换后:
https://1.2.3.4/user/info

这在 HTTPS 场景下非常容易出问题:

  • 证书是签给 api.example.com 的,不是签给 1.2.3.4 的。
  • SNI 可能不正确。
  • Host 头需要额外处理。
  • HTTP/2 连接复用可能异常。
  • Cookie 域、重定向、证书绑定都可能受影响。

更推荐的方式是:

kotlin 复制代码
OkHttpClient.Builder()
    .dns(customDns)
    .build()

也就是说:URL 仍然使用域名,只是在 DNS 解析阶段返回你想要的 IP。


十三、推荐的整体架构

最终可以形成下面这套架构:

text 复制代码
业务层
  ↓
请求分类:API / CDN / 上传 / 下载
  ↓
不同 OkHttpClient
  ↓
自定义 Dns
  ↓
HTTPDNS 查询
  ↓
本地 DNS 缓存
  ↓
IP 质量排序
  ↓
OkHttp 连接池 / HTTP/2 / TLS
  ↓
请求执行
  ↓
EventListener 采集耗时
  ↓
APM 上报
  ↓
配置中心动态调整 CDN / DNS / 域名策略

可以把网络优化分成三层:

第一层:资源层

  • 静态资源 CDN 化。
  • 图片尺寸和格式优化。
  • 大文件断点续传。
  • URL 版本化。
  • 合理缓存头。

第二层:链路层

  • HTTPDNS。
  • DNS 缓存。
  • IP 排序。
  • 连接复用。
  • HTTP/2。
  • TLS 优化。
  • 多 CDN 容灾。

第三层:治理层

  • EventListener 监控。
  • APM 上报。
  • 失败分类。
  • 配置中心。
  • 灰度策略。
  • 地域和运营商分析。

十四、落地清单

如果你要在项目中落地,可以按这个顺序做:

阶段一:低成本收益

  • 静态资源接入 CDN。
  • 图片 URL 支持尺寸裁剪。
  • 配置合理的 Cache-Control。
  • OkHttp 开启缓存。
  • 复用 OkHttpClient,避免重复创建。
  • 使用连接池。
  • 开启 EventListener 采集基础耗时。

阶段二:DNS 优化

  • 核心域名接入 HTTPDNS。
  • 实现 OkHttp Dns 接口。
  • 增加 DNS 本地缓存。
  • HTTPDNS 失败回退系统 DNS。
  • App 启动预解析核心域名。
  • 网络切换后刷新 DNS 缓存。

阶段三:稳定性和容灾

  • CDN 静态资源支持备用域名。
  • IP 失败降权。
  • 区分连接失败、TLS 失败、HTTP 失败。
  • 上传、下载、API 使用不同 client。
  • 配置中心支持动态调整域名。
  • 弱网场景增加本地缓存兜底。

阶段四:精细化治理

  • 按国家、地区、运营商分析耗时。
  • 按网络类型分析成功率。
  • 按 CDN 节点分析 TTFB。
  • 对慢 IP 自动降权。
  • 对异常 CDN 自动切流。
  • 建立网络质量大盘。

十五、总结

Android 网络优化不是简单地把接口超时时间调大,也不是接一个 CDN 就万事大吉。

真正有效的优化,应该覆盖完整链路:

text 复制代码
DNS 解析是否快?
IP 是否选得准?
CDN 节点是否离用户近?
连接是否复用?
TLS 是否重复握手?
资源是否缓存?
失败是否能快速切换?
慢在哪里是否能被监控到?

对于大多数 Android 项目,推荐从这些点开始:

  1. 静态资源 CDN 化,URL 版本化,正确配置缓存头。
  2. 核心域名接入 HTTPDNS,通过 OkHttp Dns 接口注入。
  3. 不要手动把 HTTPS 域名替换成 IP。
  4. 使用 OkHttp 连接池、HTTP/2、缓存和 EventListener。
  5. 按 API、CDN、上传、下载拆分不同 OkHttpClient。
  6. 对 DNS、连接、TLS、TTFB、总耗时做监控上报。
  7. 基于失败率和 RTT 做 IP 降权和 CDN 容灾。

网络优化最重要的不是一次性写完所有代码,而是建立一个可以持续迭代的体系:

先可观测,再优化;先稳定,再激进;先局部灰度,再全量放开。

当你能清楚知道每一次请求慢在哪里,CDN、DNS 和 OkHttp 的优化才真正开始发挥价值。

相关推荐
赏金术士3 小时前
Android 项目模块化与 Feature 组件实践
android·kotlin·模块化
网安小白的进阶之路5 小时前
B模块 安全通信网络 第二门课IPv6与WLAN 01
网络·安全
学习3人组5 小时前
Cisco ASA防火墙 NAT实验:源NAT+目的NAT(Trust/Untrust双区域,无DMZ)
网络·网络安全
天天进步20156 小时前
Tunnelto 源码解析 #6:数据转发核心:远端 HTTP 请求如何被转发到本地 localhost
网络
志栋智能7 小时前
安全超自动化:应对海量安全警报的唯一解
网络·安全·自动化
summerkissyou19877 小时前
Android-UI-获取屏幕尺寸的方法
android·ui
用户86022504674727 小时前
Kotlin 函数式编程入门与实践指南
android
dxxt_yy7 小时前
鼎讯信通 HD‑095B:能源行业高精度频谱测试解析
网络·能源
最爱睡觉睡觉睡觉9 小时前
CSS → Flutter 对照手册
android·前端