Skip to content

Android 网络优化

目录

  1. 引言
  2. 缓存策略
  3. 请求合并
  4. 数据压缩
  5. 连接池优化
  6. [DNS 预解析](#dns 预解析)
  7. 接口预加载
  8. 弱网优化
  9. 性能监控
  10. 面试考点
  11. 最佳实践
  12. 总结

引言

网络优化是 Android 性能优化的重要组成部分。在网络环境下,应用的性能表现直接影响用户体验。网络延迟、请求失败、流量消耗等问题都会导致用户流失。本文将深入探讨 Android 网络优化的各个方面,提供实用的优化方案和代码示例。

为什么需要网络优化?

  • 提升用户体验:快速响应减少用户等待
  • 节省流量:减少不必要的网络请求和数据传输
  • 降低电量消耗:减少网络活动延长电池寿命
  • 提高稳定性:处理弱网和异常情况

网络性能指标

指标说明目标值
首包时间 (TTFB)从请求到收到第一个字节的时间< 500ms
完全缓冲时间 (TTFB+DL)从请求到所有数据接收完成< 1s
连接建立时间DNS 解析 + TCP 握手 + TLS 握手< 300ms
请求成功率成功请求占比> 99%

缓存策略

缓存层级架构

Android 网络缓存通常分为三层:

┌─────────────────────────────────────┐
│          Memory Cache               │  内存缓存 (最快)
│         (Glide, LruCache)           │
├─────────────────────────────────────┤
│          Disk Cache                 │  磁盘缓存 (持久化)
│       (OkHttp, Room)                │
├─────────────────────────────────────┤
│        Network Cache                │  网络缓存 (服务器)
│   (HTTP Cache-Control, ETag)        │
└─────────────────────────────────────┘

HTTP 缓存机制

Cache-Control 指令

http
Cache-Control: max-age=3600, public
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: must-revalidate
Cache-Control: s-maxage=3600

ETag 与 Last-Modified

http
# 服务器响应
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

# 客户端请求(协商缓存)
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

OkHttp 缓存配置

kotlin
// 配置缓存
val cacheSize = 10 * 1024 * 1024L // 10MB
val cacheDir = context.cacheDir.resolve("http_cache")
val cache = Cache(cacheDir, cacheSize)

val client = OkHttpClient.Builder()
    .cache(cache)
    .connectTimeout(15, TimeUnit.SECONDS)
    .readTimeout(15, TimeUnit.SECONDS)
    .writeTimeout(15, TimeUnit.SECONDS)
    .build()

// 缓存拦截器
class CacheIntercepter(
    private val context: Context,
    private val cacheEnableInterceptor: CacheEnableInterceptor
) : Interceptor {
    
    override fun intercept(chain: Interceptor.Chain): Response {
        val requestBuilder = chain.request().newBuilder()
        
        // 根据网络状态设置缓存策略
        if (!NetworkUtil.isNetworkAvailable(context)) {
            requestBuilder.addHeader("Cache-Control", "public, only-if-cached, max-stale=604800")
        } else {
            requestBuilder.removeHeader("Pragma")
            requestBuilder.removeHeader("Cache-Control")
        }
        
        return chain.proceed(requestBuilder.build())
    }
}

图片缓存优化(Glide)

kotlin
// Glide 缓存配置
Glide.with(context)
    .load(imageUrl)
    .apply(
        RequestOptions()
            .diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存内存和磁盘
            .centerCrop()
    )
    .into(imageView)

// 自定义缓存策略
val diskCache = InternalCacheDiskCacheFactory(context)
val memoryCache = LruResourceCache(memorySizeBytes)

GlideBuilder()
    .setDiskCache(diskCache)
    .setMemoryCache(memoryCache)
    .build()
    .setContext(context)

// 不同场景的缓存策略
enum class DiskCacheStrategy(val strategy: DiskCacheStrategy) {
    NONE(DiskCacheStrategy.NONE),           // 不缓存
    DATA(DiskCacheStrategy.DATA),           // 只缓存原始数据
    RESOURCE(DiskCacheStrategy.RESOURCE),   // 只缓存处理后的资源
    ALL(DiskCacheStrategy.ALL),             // 全部缓存(默认)
    AUTOMATIC(DiskCacheStrategy.AUTOMATIC)  // 自动选择
}

内存缓存实现

kotlin
// LRU 缓存实现
class LruCache<K, V>(private val maxSize: Int) {
    private val cache = LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
    
    @Synchronized
    fun get(key: K): V? {
        return cache[key]
    }
    
    @Synchronized
    fun put(key: K, value: V) {
        cache[key] = value
        if (cache.size > maxSize) {
            cache.keys.iterator().next()?.let { cache.remove(it) }
        }
    }
    
    @Synchronized
    fun clear() {
        cache.clear()
    }
}

// 网络响应内存缓存
class ResponseCache {
    private val memoryCache = LruCache<String, Response>(100)
    private val diskCache = DiskLruCache.open(cacheDir, 1, 1, 100 * 1024 * 1024) // 100MB
    
    fun get(key: String): Response? {
        // 先从内存获取
        memoryCache.get(key)?.let { return it }
        
        // 从磁盘获取
        diskCache.get(key)?.let { snapshot ->
            val response = parseResponse(snapshot.get(0))
            memoryCache.put(key, response)
            return response
        }
        
        return null
    }
    
    fun put(key: String, response: Response) {
        memoryCache.put(key, response)
        
        // 异步写入磁盘
        IOUtils.write(diskCache.edit(key), serializeResponse(response))
    }
}

请求合并

请求合并原理

请求合并是指将多个网络请求合并为一个请求,减少网络交互次数。

场景分析

1. 列表页多接口合并

kotlin
// 原始代码:多个独立请求
fun loadHomePage() {
    api.getUserInfo()
    api.getBannerList()
    api.getRecommendList()
    api.getNoticeList()
}

// 优化后:合并请求
data class HomeRequest(
    val needUserInfo: Boolean = true,
    val needBanner: Boolean = true,
    val needRecommend: Boolean = true,
    val needNotice: Boolean = true
)

data class HomeResponse(
    val userInfo: UserInfo?,
    val bannerList: List<Banner>,
    val recommendList: List<Recommend>,
    val noticeList: List<Notice>
)

fun loadHomePageOptimized() {
    api.getHomeData(HomeRequest()).enqueue(object : Callback<HomeResponse> {
        override fun onResponse(call: Call<HomeResponse>, response: Response<HomeResponse>) {
            response.body()?.let {
                updateUserInfo(it.userInfo)
                updateBannerList(it.bannerList)
                updateRecommendList(it.recommendList)
                updateNoticeList(it.noticeList)
            }
        }
    })
}

2. 搜索请求防抖

kotlin
class SearchManager {
    private val debounceTime = 500L
    private var lastSearchTime = 0L
    private val searchQueue = ConcurrentLinkedQueue<SearchRequest>()
    
    fun search(query: String) {
        val now = System.currentTimeMillis()
        if (now - lastSearchTime < debounceTime) {
            // 队列中已有的相同请求直接取消
            searchQueue.removeIf { it.query == query }
            searchQueue.add(SearchRequest(query, now))
            return
        }
        
        lastSearchTime = now
        performSearch(query)
    }
    
    private fun performSearch(query: String) {
        api.search(query).enqueue(/* callback */)
    }
}

// 使用 RxJava 实现防抖
class RxSearchManager {
    private val searchSubject = PublishSubject.create<String>()
    
    init {
        searchSubject
            .debounce(500, TimeUnit.MILLISECONDS)
            .distinctUntilChanged()
            .flatMap { query -> 
                Observable.fromCallable { api.search(query) }
            }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(/* callback */)
    }
    
    fun search(query: String) {
        searchSubject.onNext(query)
    }
}

3. 批量操作合并

kotlin
// 批量删除
fun deleteItems(ids: List<Long>) {
    // 原始:逐个删除
    // ids.forEach { api.deleteItem(it).enqueue {} }
    
    // 优化:批量删除
    api.deleteItems(ids).enqueue(object : Callback<Void> {
        override fun onResponse(call: Call<Void>, response: Response<Void>) {
            // 处理成功
        }
    })
}

// 批量更新
data class ItemUpdate(val id: Long, val data: ItemData)

fun updateItems(updates: List<ItemUpdate>) {
    api.updateItems(updates).enqueue(/* callback */)
}

4. GraphQL 查询合并

kotlin
// GraphQL 查询合并示例
val query = """
    query HomeQuery {
        user {
            id
            name
            avatar
        }
        bannerList {
            id
            imageUrl
            url
        }
        recommendList {
            id
            title
            summary
        }
    }
"""

val response = gqlClient.query(query).execute()

请求队列管理

kotlin
class RequestQueue {
    private val queue = ConcurrentLinkedQueue<NetworkRequest>()
    private val running = AtomicInteger(0)
    private val maxConcurrent = 5
    
    fun add(request: NetworkRequest) {
        queue.add(request)
        schedule()
    }
    
    private fun schedule() {
        while (running.get() < maxConcurrent) {
            val request = queue.poll() ?: return
            running.incrementAndGet()
            
            Thread {
                try {
                    request.execute()
                } finally {
                    running.decrementAndGet()
                    schedule()
                }
            }.start()
        }
    }
}

数据压缩

Gzip 压缩

OkHttp 自动压缩

kotlin
// OkHttp 默认支持 Gzip 压缩
val client = OkHttpClient.Builder()
    .addInterceptor(object : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()
            val response = chain.proceed(request)
            
            // 检查压缩状态
            if (response.header("Content-Encoding") == "gzip") {
                Log.d("Network", "Response is gzipped")
            }
            
            return response
        }
    })
    .build()

手动压缩配置

kotlin
// 服务器端配置(Spring Boot)
@Configuration
class CompressionConfig {
    @Bean
    fun contentCompression(): ContentCompressionConfigurer {
        return ContentCompressionConfigurer { config ->
            config.setMinSize(1024) // 超过 1KB 才压缩
            config.setCompressableMimeTypes("application/json", "text/html", "text/css")
        }
    }
}

// 客户端强制请求压缩
class GzipRequestInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val request = originalRequest.newBuilder()
            .header("Accept-Encoding", "gzip")
            .method(originalRequest.method, originalRequest.body)
            .build()
        
        return chain.proceed(request)
    }
}

Protobuf 序列化

定义.proto 文件

protobuf
// user.proto
syntax = "proto3";

option java_package = "com.example.model";
option java_outer_classname = "UserProto";

message UserInfo {
    string id = 1;
    string name = 2;
    string avatar = 3;
    int32 age = 4;
    repeated string tags = 5;
}

message HomeResponse {
    UserInfo user = 1;
    repeated Banner banners = 2;
    repeated Recommend recommends = 3;
}

message Banner {
    string id = 1;
    string imageUrl = 2;
    string url = 3;
}

message Recommend {
    string id = 1;
    string title = 2;
    string summary = 3;
    int32 type = 4;
}

Gradle 配置

groovy
// build.gradle
plugins {
    id "com.google.protobuf" version "0.9.4"
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.25.1"
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option "lite"
                }
            }
        }
    }
}

dependencies {
    implementation "com.google.protobuf:protobuf-javalite:3.25.1"
}

使用 Protobuf 传输

kotlin
// 序列化
fun serializeToProtobuf(data: HomeResponse): ByteArray {
    return data.toByteArray()
}

// 反序列化
fun deserializeFromProtobuf(data: ByteArray): HomeResponse {
    return HomeResponse.parseFrom(data)
}

// OkHttp Protobuf 转换器
class ProtobufConverter : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<out Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *> {
        return object : Converter<ResponseBody, HomeResponse> {
            override fun convert(body: ResponseBody): HomeResponse {
                return HomeResponse.parseFrom(body.bytes())
            }
        }
    }
}

// Retrofit 配置
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(ProtobufConverter())
    .build()

压缩效果对比

格式原始大小压缩后大小压缩率解析时间
JSON100KB30KB (Gzip)70%5ms
JSON100KB20KB (Protobuf)80%3ms
JSON100KB25KB (Protobuf + Gzip)75%4ms

连接池优化

OkHttp 连接池配置

kotlin
val client = OkHttpClient.Builder()
    .connectionPool(ConnectionPool(
        maxSize = 5,              // 空闲连接最大数
        keepAliveDuration = 5,    // 空闲连接保持时间
        TimeUnit.MINUTES
    ))
    .dns(Dns.SYSTEM)
    .build()

// 全局连接池管理
object ConnectionPoolManager {
    private val pool = ConnectionPool(5, 5, TimeUnit.MINUTES)
    
    fun getClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectionPool(pool)
            .build()
    }
    
    fun shutdown() {
        pool.evictAll()
    }
    
    fun getStats(): ConnectionPoolStats {
        return pool.run {
            ConnectionPoolStats(
                totalSize = this.size(),
                connectionCount = this.connectionCount()
            )
        }
    }
}

多域名连接池

kotlin
// 为不同域名配置独立连接池
class MultiDomainConnectionPool {
    private val pools = ConcurrentHashMap<String, ConnectionPool>()
    
    fun getPool(domain: String): ConnectionPool {
        return pools.computeIfAbsent(domain) {
            ConnectionPool(5, 5, TimeUnit.MINUTES)
        }
    }
    
    fun getClient(domain: String): OkHttpClient {
        return OkHttpClient.Builder()
            .connectionPool(getPool(domain))
            .build()
    }
}

连接复用优化

kotlin
class ConnectionReuseInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val connection = (chain as RealInterceptorChain).connection()
        
        if (connection != null) {
            Log.d("Network", "Connection reused: ${connection.route()}")
        } else {
            Log.d("Network", "New connection created")
        }
        
        return chain.proceed(request)
    }
}

// 配置 Keep-Alive
val client = OkHttpClient.Builder()
    .addInterceptor(ConnectionReuseInterceptor())
    .connectionPool(ConnectionPool(10, 5, TimeUnit.MINUTES))
    .build()

DNS 预解析

DNS 缓存配置

kotlin
// 自定义 DNS 解析器
class CachedDns : Dns {
    private val cache = object : LinkedHashMap<String, List<InetSocketAddress>>(16, 0.75f, true) {
        override fun removeEldestEntry(eldest: MutableEntry<String, List<InetSocketAddress>>): Boolean {
            return size > MAX_CACHE_SIZE
        }
    }
    private val expireTime = 5 * 60 * 1000L // 5 分钟
    private val lock = Any()
    
    override fun lookup(hostName: String): List<InetSocketAddress> {
        synchronized(lock) {
            val cached = cache[hostName]
            if (cached != null && !isExpired(hostName)) {
                return cached
            }
        }
        
        // DNS 解析
        val addresses = Dns.SYSTEM.lookup(hostName)
        synchronized(lock) {
            cache[hostName] = addresses
        }
        return addresses
    }
    
    private fun isExpired(hostName: String): Boolean {
        // 实现过期检查
        return false
    }
}

// OkHttp 配置
val client = OkHttpClient.Builder()
    .dns(CachedDns())
    .build()

预解析实现

kotlin
class DnsPreResolver {
    private val client: OkHttpClient
    private val dnsCache = ConcurrentHashMap<String, List<InetSocketAddress>>()
    
    init {
        client = OkHttpClient.Builder()
            .dns(CachedDns())
            .build()
    }
    
    fun preloadDns(hostnames: Collection<String>) {
        hostnames.forEach { hostname ->
            // 异步预解析
            Executors.newSingleThreadExecutor().execute {
                try {
                    val addresses = dnsCache.computeIfAbsent(hostname) {
                        InetAddress.getAllByName(hostname).map { 
                            InetSocketAddress(it, 443)
                        }
                    }
                    Log.d("DnsPreResolver", "Pre-resolved $hostname: $addresses")
                } catch (e: Exception) {
                    Log.e("DnsPreResolver", "Failed to preload $hostname", e)
                }
            }
        }
    }
    
    // 应用启动时预解析常用域名
    fun preloadCommonDomains() {
        val commonDomains = listOf(
            "api.example.com",
            "cdn.example.com",
            "static.example.com"
        )
        preloadDns(commonDomains)
    }
}

// 在 Application 中使用
class MyApplication : Application() {
    private val dnsPreResolver = DnsPreResolver()
    
    override fun onCreate() {
        super.onCreate()
        dnsPreResolver.preloadCommonDomains()
    }
}

接口预加载

预加载策略

kotlin
class PreloadManager {
    private val preloadQueue = ArrayDeque<PreloadTask>()
    private val executor = Executors.newSingleThreadExecutor()
    
    fun addPreload(task: PreloadTask) {
        preloadQueue.add(task)
        schedulePreload()
    }
    
    private fun schedulePreload() {
        val task = preloadQueue.peekFirst() ?: return
        
        if (shouldPreload()) {
            preloadQueue.pollFirst()
            executor.execute {
                try {
                    task.execute()
                } catch (e: Exception) {
                    Log.e("Preload", "Preload failed", e)
                }
            }
        }
    }
    
    private fun shouldPreload(): Boolean {
        // 只在 Wi-Fi 且电量充足时预加载
        return isWifiConnected() && batteryLevel > 20
    }
}

// 预加载任务
class PreloadTask(
    private val url: String,
    private val callback: (Response) -> Unit
) {
    fun execute() {
        val request = Request.Builder()
            .url(url)
            .get()
            .build()
        
        val response = client.newCall(request).execute()
        callback(response)
    }
}

场景化预加载

kotlin
// 下一页预加载
class PaginationPreloader {
    private var currentPage = 1
    private val client: OkHttpClient
    
    fun preloadNextPage(baseUrl: String) {
        val nextPage = currentPage + 1
        val url = "$baseUrl?page=$nextPage"
        
        client.newCall(Request.Builder().url(url).build())
            .enqueue(object : Callback {
                override fun onResponse(call: Call, response: Response) {
                    // 缓存到内存
                    response.body()?.string()?.let {
                        memoryCache.put(url, it)
                    }
                }
            })
    }
    
    fun loadNextPage(): String? {
        val url = "$baseUrl?page=$currentPage"
        return memoryCache.get(url).apply {
            currentPage++
        }
    }
}

// 推荐内容预加载
class RecommendPreloader {
    fun preloadRecommend() {
        api.getRecommendList(PreloadRequest()).enqueue(object : Callback<RecommendResponse> {
            override fun onResponse(call: Call<RecommendResponse>, response: Response<RecommendResponse>) {
                response.body()?.let {
                    cacheRecommend(it)
                }
            }
        })
    }
}

弱网优化

网络质量检测

kotlin
class NetworkQualityMonitor {
    enum class NetworkQuality {
        EXCELLENT, GOOD, FAIR, POOR, OFFLINE
    }
    
    private var currentQuality = NetworkQuality.EXCELLENT
    
    fun detectNetworkQuality(): NetworkQuality {
        val connectivityManager = 
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        
        val networkInfo = connectivityManager.activeNetworkInfo ?: return NetworkQuality.OFFLINE
        
        when (networkInfo.typeInfo?.subtype) {
            // 5G/4G
            TelephonyManager.NETWORK_TYPE_LTE,
            TelephonyManager.NETWORK_TYPE_NR -> NetworkQuality.EXCELLENT
            // 3G
            TelephonyManager.NETWORK_TYPE_UMTS,
            TelephonyManager.NETWORK_TYPE_EVDO_0 -> NetworkQuality.GOOD
            // 2G
            TelephonyManager.NETWORK_TYPE_GPRS,
            TelephonyManager.NETWORK_TYPE_EDGE -> NetworkQuality.Poor
            else -> NetworkQuality.FAIR
        }
    }
    
    // 实际网络速度测试
    fun testNetworkSpeed(): NetworkSpeed {
        val startTime = System.currentTimeMillis()
        val testUrl = "https://speedtest.example.com/testfile.bin"
        
        val request = Request.Builder().url(testUrl).build()
        val response = client.newCall(request).execute()
        
        val bytes = response.body()?.bytes() ?: return NetworkSpeed(0)
        val duration = System.currentTimeMillis() - startTime
        
        val speedMbps = (bytes.size * 8.0 / duration / 1_000_000)
        return NetworkSpeed(speedMbps)
    }
}

弱网优化策略

kotlin
class WeakNetworkOptimizer {
    private val qualityMonitor = NetworkQualityMonitor()
    
    fun optimizeRequest(request: Request, quality: NetworkQuality): Request {
        return when (quality) {
            NetworkQuality.OFFLINE -> {
                // 离线模式,使用缓存
                request.newBuilder()
                    .addHeader("Cache-Control", "public, only-if-cached, max-stale=604800")
                    .build()
            }
            NetworkQuality.POOR -> {
                // 差网络,降低质量,增加超时
                request.newBuilder()
                    .addHeader("Accept-Encoding", "identity") // 不压缩
                    .addHeader("X-Quality", "low")
                    .build()
            }
            NetworkQuality.FAIR -> {
                // 一般网络,中等质量
                request.newBuilder()
                    .addHeader("X-Quality", "medium")
                    .build()
            }
            else -> request
        }
    }
    
    // 图片质量自适应
    fun getImageQualityUrl(quality: NetworkQuality, baseUrl: String, width: Int): String {
        val qualityParam = when (quality) {
            NetworkQuality.OFFLINE -> "cache"
            NetworkQuality.POOR -> "60"
            NetworkQuality.FAIR -> "80"
            else -> "100"
        }
        return "$baseUrl?width=$width&quality=$qualityParam"
    }
}

断点续传

kotlin
class ResumeDownloadManager {
    private val cacheDir = context.filesDir.resolve("downloads")
    
    fun downloadWithResume(url: String, fileName: String) {
        val cacheFile = File(cacheDir, fileName)
        val fileSize = cacheFile.length()
        
        val request = Request.Builder()
            .url(url)
            .header("Range", "bytes=$fileSize-")
            .build()
        
        client.newCall(request).enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                if (response.code == 206) { // Partial Content
                    val outputStream = FileOutputStream(cacheFile, true)
                    response.body()?.byteStream()?.copyTo(outputStream)
                }
            }
        })
    }
}

重试机制

kotlin
class RetryInterceptor : Interceptor {
    private val maxRetries = 3
    private val retryDelay = 1000L
    
    override fun intercept(chain: Interceptor.Chain): Response {
        var attempt = 0
        var response: Response? = null
        
        while (attempt < maxRetries) {
            try {
                response = chain.proceed(chain.request())
                if (response.isSuccessful) {
                    return response!!
                }
            } catch (e: IOException) {
                // 网络错误,重试
            }
            
            attempt++
            if (attempt < maxRetries) {
                Thread.sleep(retryDelay * attempt) // 指数退避
            }
        }
        
        throw IOException("Request failed after $maxRetries attempts")
    }
}

// RxJava 重试
api.getData()
    .retryWhen { errors ->
        errors.withIndex()
            .flatMapIterable { (index, error) ->
                if (index < 3) {
                    val delay = (index + 1) * 1000L
                    listOf(delay)
                } else {
                    throw error
                }
            }
            .flatMapObservable { delay ->
                Observable.timer(delay, TimeUnit.MILLISECONDS)
            }
    }
    .subscribe(/* callback */)

性能监控

网络监控拦截器

kotlin
class NetworkMonitorInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val startTime = System.currentTimeMillis()
        
        val response = chain.proceed(request)
        
        val endTime = System.currentTimeMillis()
        val duration = endTime - startTime
        
        // 记录网络请求
        NetworkMonitor.record(
            url = request.url.toString(),
            method = request.method,
            code = response.code,
            duration = duration,
            responseSize = response.body?.contentLength() ?: 0,
            timestamp = System.currentTimeMillis()
        )
        
        // 性能告警
        if (duration > 3000) {
            Log.w("Network", "Slow request: ${request.url} took $duration ms")
        }
        
        return response
    }
}

// 网络监控数据
data class NetworkMetrics(
    val url: String,
    val method: String,
    val code: Int,
    val duration: Long,
    val responseSize: Long,
    val timestamp: Long
)

object NetworkMonitor {
    private val metrics = ConcurrentHashMap<String, MutableList<NetworkMetrics>>()
    
    fun record(metrics: NetworkMetrics) {
        val url = metrics.url.split("?")[0]
        metrics.computeIfAbsent(url) { mutableListOf() }.add(metrics)
    }
    
    fun getAverageDuration(url: String): Long {
        return metrics[url]?.map { it.duration }?.average()?.toLong() ?: 0
    }
    
    fun getErrorRate(url: String): Double {
        val list = metrics[url] ?: return 0.0
        val errors = list.count { it.code >= 400 }
        return errors.toDouble() / list.size
    }
}

实时网络监控

kotlin
class RealTimeNetworkMonitor {
    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            Log.d("Network", "Network available")
        }
        
        override fun onLost(network: Network) {
            Log.d("Network", "Network lost")
        }
        
        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
        ) {
            val subtype = networkCapabilities.transportType
            Log.d("Network", "Network changed: $subtype")
        }
    }
    
    fun start() {
        val connectivityManager = 
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        connectivityManager.registerDefaultNetworkCallback(networkCallback)
    }
    
    fun stop() {
        val connectivityManager = 
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }
}

面试考点

基础考点

1. HTTP 缓存机制

问题: HTTP 缓存的强缓存和协商缓存有什么区别?

回答:

  • 强缓存: 直接使用本地缓存,不请求服务器。通过 Cache-Control、Expires 控制
  • 协商缓存: 先请求服务器,服务器判断缓存是否有效。通过 ETag、Last-Modified 控制
http
# 强缓存
Cache-Control: max-age=3600

# 协商缓存
ETag: "abc123"
If-None-Match: "abc123"  # 返回 304 则使用缓存

2. OkHttp 连接池

问题: OkHttp 连接池如何工作的?

回答:

  • OkHttp 使用 ConnectionPool 管理空闲连接
  • 默认保持 5 个空闲连接,5 分钟后关闭
  • 相同域名的请求会复用连接
  • 支持 HTTP/2 多路复用

3. 弱网优化

问题: 弱网环境下如何优化应用体验?

回答:

  • 图片质量自适应
  • 减少请求大小(Gzip/Protobuf)
  • 实现断点续传
  • 增加重试机制
  • 离线缓存策略

进阶考点

1. 请求合并优化

问题: 如何实现多个 API 请求的合并?

回答:

  • 后端合并接口(GraphQL 模式)
  • 前端请求队列合并
  • 使用单例 OkHttpClient 复用连接
  • 防抖和节流机制

2. DNS 预解析

问题: DNS 预解析如何提升性能?

回答:

  • 减少 DNS 查询时间(通常 50-200ms)
  • 应用启动时预解析常用域名
  • 实现 DNS 缓存机制
  • 使用 DNS 预取(DNS Prefetch)

3. 连接复用

问题: 如何提高连接复用率?

回答:

  • 使用单例 OkHttpClient
  • 合理配置连接池参数
  • 避免频繁创建新客户端
  • 使用 HTTP/2 多路复用

高级考点

1. QUIC 协议

问题: QUIC 协议相比 TCP 有什么优势?

回答:

  • 基于 UDP,减少握手延迟
  • 支持多路复用,避免队头阻塞
  • 连接迁移支持
  • 加密更安全

2. 网络监控体系

问题: 如何设计完善的网络监控系统?

回答:

  • 请求成功率监控
  • 响应时间监控(P50/P95/P99)
  • 错误类型分类
  • 实时告警机制
  • 性能趋势分析

3. 离线优先架构

问题: 如何实现离线优先的应用架构?

回答:

  • 本地数据库同步
  • 请求队列管理
  • 数据冲突解决
  • 后台同步机制

最佳实践

网络层架构

kotlin
// 网络层架构
interface NetworkService {
    fun <T> request(url: String): Flow<T>
}

class NetworkServiceImpl : NetworkService {
    private val client = OkHttpClient.Builder()
        .connectionPool(ConnectionPool(10, 5, TimeUnit.MINUTES))
        .addInterceptor(HeaderInterceptor())
        .addInterceptor(RetryInterceptor())
        .addInterceptor(CacheInterceptor())
        .addInterceptor(MonitorInterceptor())
        .build()
    
    override fun <T> request(url: String): Flow<T> {
        return flow {
            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
            emit(parseResponse(response))
        }
    }
}

常见错误

1. 内存泄漏

kotlin
// ❌ 错误:未取消请求
class ViewModel : ViewModel() {
    private val call = api.getData().enqueue(/* callback */)
}

// ✅ 正确:使用复合键取消
class ViewModel : ViewModel() {
    private val compositeDisposable = CompositeDisposable()
    
    init {
        compositeDisposable.add(
            api.getData()
                .subscribe(/* callback */)
        )
    }
    
    override fun onCleared() {
        compositeDisposable.clear()
    }
}

2. 主线程网络请求

kotlin
// ❌ 错误:主线程请求
val response = client.newCall(request).execute()

// ✅ 正确:异步请求
client.newCall(request).enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
        // 处理响应
    }
})

// ✅ 或使用协程
coroutineScope {
    val response = withContext(Dispatchers.IO) {
        client.newCall(request).execute()
    }
}

3. 未处理异常

kotlin
// ❌ 错误:未处理异常
client.newCall(request).enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {}
    override fun onFailure(call: Call, e: IOException) {} // 未处理
})

// ✅ 正确:完整异常处理
client.newCall(request).enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {}
    override fun onFailure(call: Call, e: IOException) {
        showError("网络错误:${e.message}")
        retryOrShowCache()
    }
})

总结

网络优化是 Android 性能优化的核心内容之一。通过缓存策略、请求合并、数据压缩、连接池优化、DNS 预解析、接口预加载、弱网优化和性能监控等手段,可以显著提升应用的网络性能。

关键要点

  1. 缓存策略:合理配置 HTTP 缓存和应用层缓存
  2. 请求合并:减少网络请求次数
  3. 数据压缩:使用 Gzip 和 Protobuf 减少传输量
  4. 连接池:复用连接,减少握手开销
  5. DNS 预解析:提前解析域名
  6. 接口预加载:提前加载可能需要的数据
  7. 弱网优化:适应不同网络环境
  8. 性能监控:实时监控网络性能

性能提升效果

优化项优化前优化后提升
首屏加载时间3.5s1.2s65%
网络请求次数20 次8 次60%
流量消耗5MB2MB60%
请求成功率95%99.5%4.5%

通过系统的网络优化,可以显著提升用户体验,减少流量消耗,降低电量消耗,提高应用稳定性。