Skip to content

05_网络优化

目录


网络性能概述

1.1 网络性能指标

关键指标

TTFB (Time To First Byte) - 首字节时间
RTT (Round Trip Time) - 往返时间
Throughput - 吞吐量
Latency - 延迟
Jitter - 网络抖动
Packet Loss - 丢包率

Android 中测量

kotlin
// 使用 StrictMode 测量网络操作
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
    .detectNetwork()
    .penaltyLog()
    .build())

// 测量请求时间
val start = System.currentTimeMillis()
client.newCall(request).execute()
val duration = System.currentTimeMillis() - start
Log.d("Network", "Request took $duration ms")

网络性能分类

优秀:< 200ms
良好:200-500ms
一般:500-1000ms
较差:1000-3000ms
差:> 3000ms

1.2 网络性能影响因素

1. 网络类型
   - WiFi:速度快,稳定
   - 4G/5G:速度快,可能不稳定
   - 3G:速度中等
   - 2G:速度慢

2. 网络质量
   - 信号强度
   - 丢包率
   - 延迟

3. 服务器性能
   - 响应时间
   - 并发能力
   - 带宽

4. 客户端性能
   - 解析速度
   - 缓存策略
   - 并发管理

1.3 性能优化思路

┌──────────────────────────────────────┐
│         网络性能优化                  │
├──────────────────────────────────────┤
│  请求端优化                           │
│  ├─ 请求合并                         │
│  ├─ 请求压缩                         │
│  ├─ 连接池                           │
│  └─ DNS 优化                         │
├──────────────────────────────────────┤
│  传输端优化                           │
│  ├─ HTTP/2 多路复用                  │
│  ├─ 数据压缩(Gzip/Brotli)          │
│  └─ 二进制协议(Protobuf)           │
├──────────────────────────────────────┤
│  缓存优化                             │
│  ├─ 内存缓存                         │
│  ├─ 磁盘缓存                         │
│  └─ CDN 缓存                         │
├──────────────────────────────────────┤
│  弱网优化                             │
│  ├─ 降级策略                         │
│  ├─ 离线缓存                         │
│  └─ 预加载                           │
└──────────────────────────────────────┘

请求优化

2.1 请求合并

问题场景

kotlin
// ❌ 多个独立请求
fun loadUserDetails(userId: String) {
    service.getUser(userId)      // 请求 1
    service.getUserProfile(userId)  // 请求 2
    service.getUserFriends(userId)  // 请求 3
    service.getUserPosts(userId)    // 请求 4
}
// 4 次 HTTP 请求,4 次握手,4 次响应

优化方案

kotlin
// ✅ 合并请求
interface ApiService {
    @GET("user/details")
    suspend fun getUserDetails(@Query("id") userId: String): UserDetailsResponse
}

data class UserDetailsResponse(
    val user: User,
    val profile: UserProfile,
    val friends: List<Friend>,
    val posts: List<Post>
)

fun loadUserDetails(userId: String) {
    service.getUserDetails(userId) // 1 次请求
}

GraphQL 方案

graphql
query GetUserDetails($userId: ID!) {
    user(id: $userId) {
        id
        name
        email
        profile {
            avatar
            bio
        }
        friends {
            id
            name
        }
        posts {
            id
            title
            content
        }
    }
}

2.2 请求压缩(Gzip)

开启 Gzip 压缩

kotlin
// OkHttp 默认支持 Gzip
val client = OkHttpClient()

// 验证是否启用
val request = Request.Builder()
    .url("https://api.example.com/data")
    .header("Accept-Encoding", "gzip, deflate")
    .build()

val response = client.newCall(request).execute()
val encoding = response.header("Content-Encoding") // "gzip"

Gzip 压缩效果

原始数据:100KB
Gzip 压缩后:30-40KB
压缩率:60-70%

Brotli 压缩(更高效的压缩)

kotlin
// 需要服务器支持
val client = OkHttpClient.Builder()
    .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
    .build()

// Brotli 压缩率通常比 Gzip 高 15-20%

2.3 连接池优化

默认配置

kotlin
val client = OkHttpClient()
// 默认:maxIdleConnections=5, keepAliveDuration=5min

优化配置

kotlin
val connectionPool = ConnectionPool().apply {
    // 增加最大空闲连接数
    // 根据并发请求数调整
    maxSize = 10
    
    // 延长空闲连接保持时间
    keepAliveDuration = 10L, TimeUnit.MINUTES
}

val client = OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build()

连接池监控

kotlin
connectionPool.evictionListener = { address, reason ->
    when (reason) {
        ConnectionPool.CLOSED -> {
            Log.d("Pool", "Connection closed: $address")
        }
        ConnectionPool.IDLE -> {
            Log.d("Pool", "Connection idle timeout: $address")
        }
        ConnectionPool.MAX_IDLE -> {
            Log.d("Pool", "Connection max idle: $address")
        }
    }
}

// 获取连接池统计
fun getConnectionPoolStats(pool: ConnectionPool): ConnectionPoolStats {
    // 通过反射获取内部状态
    return ConnectionPoolStats(
        connectionCount = pool.size(),
        maxSize = pool.maxSize()
    )
}

2.4 DNS 优化

问题:DNS 解析耗时

DNS 解析:50-500ms

优化方案 1:DNS 缓存

kotlin
class DnsCache {
    private val cache = LruCache<String, List<InetSocketAddress>>(100)
    
    fun lookup(hostName: String): List<InetSocketAddress> {
        return cache.get(hostName) ?: run {
            val addresses = InetAddress.getAllByName(hostName)
                .toList()
                .map { InetSocketAddress(it, 443) }
            cache.put(hostName, addresses)
            addresses
        }
    }
}

val client = OkHttpClient.Builder()
    .dns(DnsCache())
    .build()

优化方案 2:使用公共 DNS

kotlin
// 使用更快的 DNS 服务器
// Google: 8.8.8.8, 8.8.4.4
// Cloudflare: 1.1.1.1

优化方案 3:HTTP/2 减少 DNS 查询

HTTP/1.1: 每个域名需要独立连接
HTTP/2: 多路复用,单连接处理多域名

2.5 请求预加载

预加载策略

kotlin
class PreloadManager {
    private val preloadQueue = ArrayDeque<PreloadTask>()
    
    fun addPreload(task: PreloadTask) {
        preloadQueue.add(task)
        executeNextPreload()
    }
    
    private fun executeNextPreload() {
        val task = preloadQueue.firstOrNull() ?: return
        
        // 在后台执行预加载
        CoroutineScope(Dispatchers.IO).launch {
            try {
                task.execute()
                // 存储到缓存
                cache.put(task.key, task.result)
            } finally {
                preloadQueue.removeFirst()
                executeNextPreload() // 执行下一个
            }
        }
    }
}

data class PreloadTask(
    val key: String,
    val execute: suspend () -> Any
)

// 使用示例
class NextPagePreload : PreloadTask(
    key = "page_${currentPage + 1}",
    execute = {
        service.getUsers(page = currentPage + 1)
    }
)

// 用户滚动到页面底部时预加载下一页
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        val visibleItemCount = recyclerView.childCount
        val totalItemCount = recyclerView.adapter?.itemCount ?: 0
        val lastVisibleItem = 
            (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
        
        // 到达底部前 2 页开始预加载
        if (lastVisibleItem + visibleItemCount >= totalItemCount - 2) {
            preloadManager.addPreload(NextPagePreload())
        }
    }
})

2.6 减少请求头大小

自定义 User-Agent

kotlin
// 过长的 User-Agent
val userAgent = "Mozilla/5.0 (Linux; Android 14; SM-G998B Build/UP1A.231005.007; " +
    "wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/120.0.0.0 " +
    "Mobile Safari/537.36" // 150+ 字符

// 优化的 User-Agent
val userAgent = "MyApp/1.0 (Android 14)" // 25 字符

减少 Cookie

kotlin
// 只携带必要的 Cookie
class CookieFilterInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val cookieHeader = request.header("Cookie")
        
        val filteredCookies = cookieHeader
            ?.split(";")
            ?.filter { it.contains("session=") || it.contains("token=") }
            ?.joinToString("; ")
        
        val builder = request.newBuilder()
        if (filteredCookies.isNullOrBlank()) {
            builder.removeHeader("Cookie")
        } else {
            builder.header("Cookie", filteredCookies)
        }
        
        return chain.proceed(builder.build())
    }
}

响应优化

3.1 使用轻量级序列化

JSON vs Protobuf

kotlin
// JSON (1KB)
"""
{
    "userId": 123,
    "userName": "张三",
    "userEmail": "zhangsan@example.com",
    "userPosts": [
        {
            "postId": 1,
            "postTitle": "标题",
            "postContent": "内容"
        }
    ]
}
"""

// Protobuf (约 200 字节)
// 二进制格式,解析更快

Protobuf 定义

proto
syntax = "proto3";

message User {
    int32 user_id = 1;
    string user_name = 2;
    string user_email = 3;
    repeated Post posts = 4;
}

message Post {
    int32 post_id = 1;
    string post_title = 2;
    string post_content = 3;
}

Android 中使用

kotlin
// 生成 Kotlin 代码
val user = User.newBuilder()
    .setUserId(123)
    .setName("张三")
    .build()

// 序列化
val data = user.toByteArray()
val size = data.size // 约 50 字节

// 反序列化
val parsed = User.parseFrom(data)

性能对比

JSON:
- 大小:1KB
- 解析时间:5ms

Protobuf:
- 大小:200 字节 (80% 节省)
- 解析时间:1ms (80% 更快)

3.2 增量更新

问题场景

kotlin
// 每次请求返回完整数据
GET /api/feed
返回:100 条完整帖子数据 (500KB)

优化:增量更新

kotlin
// 只返回变化的数据
data class IncrementalUpdate(
    val lastUpdated: Long,
    val newItems: List<Item>,
    val updatedItems: List<Item>,
    val removedItemIds: List<String>
)

// 客户端维护本地状态
class FeedManager {
    private val items = mutableListOf<Item>()
    private var lastUpdateTime = 0L
    
    suspend fun refresh() {
        val update = service.getIncrementalUpdate(lastUpdateTime)
        
        // 更新本地数据
        update.newItems.forEach { items.add(it) }
        update.updatedItems.forEach { updated ->
            val index = items.indexOfFirst { it.id == updated.id }
            if (index != -1) {
                items[index] = updated
            }
        }
        update.removedItemIds.forEach { items.removeAll { item -> item.id == it } }
        
        lastUpdateTime = update.lastUpdated
    }
}

3.3 服务端游标 vs 客户端分页

客户端分页(传统)

kotlin
GET /api/users?page=1&size=20
GET /api/users?page=2&size=20
GET /api/users?page=3&size=20

问题:
- 数据更新时页码可能错乱
- 不能获取特定位置的数据

服务端游标(推荐)

kotlin
GET /api/users?cursor=abc123&limit=20

响应:
{
    "data": [...],
    "nextCursor": "xyz789",
    "hasMore": true
}

GET /api/users?cursor=xyz789&limit=20

实现

kotlin
sealed class CursorPagedSource<out T> : PagingSource<String, T>() {
    data class Config(val key: String, val limit: Int)
    
    override suspend fun load(params: LoadParams<String>): LoadResult<String, T> {
        val cursor = params.key ?: INITIAL_CURSOR
        
        val response = service.getWithCursor(cursor, params.loadSize)
        
        return LoadResult.Page(
            data = response.data,
            prevKey = response.prevCursor,
            nextKey = response.nextCursor ?: null
        )
    }
}

// 使用 Paging 3
val pagingData = Pager(Config(INITIAL_CURSOR, 20)) {
    CursorPagedSource()
}.flow

3.4 响应体压缩

Gzip 配置

kotlin
// OkHttp 自动处理 Gzip 解压
val response = client.newCall(request).execute()
val body = response.body?.string() // 自动解压

自定义压缩

kotlin
class CompressionInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())
        
        val encoding = response.header("Content-Encoding")
        return when (encoding) {
            "gzip" -> response
            "br" -> response // OkHttp 自动处理
            else -> response
        }
    }
}

缓存策略

4.1 多级缓存架构

┌─────────────────────────────────────┐
│          内存缓存                    │
│         (LruCache)                  │
│        快速访问                      │
└──────────────┬──────────────────────┘
               │ 未命中

┌─────────────────────────────────────┐
│          磁盘缓存                    │
│      (OkHttp Cache)                 │
│        持久化                        │
└──────────────┬──────────────────────┘
               │ 未命中

┌─────────────────────────────────────┐
│          网络请求                    │
│         (服务器)                     │
│        获取最新数据                   │
└─────────────────────────────────────┘

实现

kotlin
class MultiLevelCache<T>(
    private val memoryCache: LruCache<String, T>,
    private val diskCache: Cache
) {
    fun get(key: String): T? {
        // 1. 检查内存缓存
        memoryCache.get(key)?.let { return it }
        
        // 2. 检查磁盘缓存
        diskCache.get(key)?.let { data ->
            val parsed = parse(data)
            memoryCache.put(key, parsed)
            return parsed
        }
        
        return null
    }
    
    fun put(key: String, value: T) {
        memoryCache.put(key, value)
        diskCache.put(key, serialize(value))
    }
}

4.2 OkHttp 缓存配置

基本配置

kotlin
val cache = Cache(
    directory = File(context.cacheDir, "http"),
    maxSize = 100L * 1024 * 1024 // 100MB
)

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

缓存控制策略

kotlin
// 1. 强制缓存 1 小时
val request = Request.Builder()
    .url("https://api.example.com/data")
    .header("Cache-Control", "max-age=3600")
    .build()

// 2. 不使用缓存
val request = Request.Builder()
    .url("https://api.example.com/data")
    .header("Cache-Control", "no-cache")
    .build()

// 3. 离线模式(只使用缓存)
val request = Request.Builder()
    .url("https://api.example.com/data")
    .header("Cache-Control", "only-if-cached, max-stale=604800")
    .build()

缓存拦截器

kotlin
class CacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        
        // 检查网络状态
        val isConnected = isNetworkAvailable()
        
        if (!isConnected) {
            // 离线模式
            return chain.proceed(
                request.newBuilder()
                    .header("Cache-Control", "only-if-cached, max-stale=604800")
                    .build()
            )
        }
        
        val response = chain.proceed(request)
        
        // 根据响应类型设置缓存
        return when {
            response.code == 200 && shouldCache(request.url) -> {
                response.newBuilder()
                    .header("Cache-Control", "max-age=3600")
                    .build()
            }
            else -> response
        }
    }
    
    private fun shouldCache(url: HttpUrl): Boolean {
        return url.host.contains("api.") && !url.pathSegments.contains("auth")
    }
}

4.3 缓存失效策略

时间-based 失效

kotlin
class TimeBasedCache {
    data class CacheEntry<T>(
        val data: T,
        val timestamp: Long,
        val ttl: Long
    ) {
        fun isExpired(): Boolean = System.currentTimeMillis() - timestamp > ttl
    }
    
    private val cache = LruCache<String, CacheEntry<Any>>(100)
    
    fun <T> get(key: String, ttl: Long): T? {
        val entry = cache.get(key) as? CacheEntry<T> ?: return null
        
        return if (entry.isExpired()) {
            cache.remove(key)
            null
        } else {
            entry.data
        }
    }
    
    fun <T> put(key: String, data: T, ttl: Long) {
        cache.put(key, CacheEntry(data, System.currentTimeMillis(), ttl))
    }
}

版本-based 失效

kotlin
class VersionedCache {
    private var cacheVersion = 1
    private val cache = LruCache<String, Any>(100)
    
    fun invalidate() {
        cacheVersion++
        cache.evictAll()
    }
    
    fun get(key: String): Any? {
        val versionedKey = "$cacheVersion:$key"
        return cache.get(versionedKey)
    }
    
    fun put(key: String, value: Any) {
        val versionedKey = "$cacheVersion:$key"
        cache.put(versionedKey, value)
    }
}

手动失效

kotlin
class ManualInvalidateCache {
    private val cache = LruCache<String, Any>(100)
    
    fun invalidate(key: String) {
        cache.remove(key)
    }
    
    fun invalidateAll() {
        cache.evictAll()
    }
    
    fun invalidatePattern(pattern: Regex) {
        // 使用正则匹配失效
    }
}

弱网优化

5.1 弱网检测

网络质量检测

kotlin
class NetworkQualityMonitor {
    enum class NetworkQuality {
        EXCELLENT, GOOD, FAIR, POOR, OFFLINE
    }
    
    private val _quality = MutableStateFlow(NetworkQuality.EXCELLENT)
    val quality: StateFlow<NetworkQuality> = _quality
    
    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            testNetworkQuality()
        }
        
        override fun onLost(network: Network) {
            _quality.value = NetworkQuality.OFFLINE
        }
    }
    
    private fun testNetworkQuality() {
        CoroutineScope(Dispatchers.IO).launch {
            val start = System.currentTimeMillis()
            val result = try {
                client.newCall(
                    Request.Builder()
                        .url("https://www.google.com/generate_204")
                        .build()
                ).execute()
                
                val duration = System.currentTimeMillis() - start
                when {
                    duration < 300 -> NetworkQuality.EXCELLENT
                    duration < 1000 -> NetworkQuality.GOOD
                    duration < 3000 -> NetworkQuality.FAIR
                    else -> NetworkQuality.Poor
                }
            } catch (e: Exception) {
                NetworkQuality.OFFLINE
            }
            
            _quality.value = result
        }
    }
}

5.2 降级策略

根据网络质量调整

kotlin
class AdaptiveLoadManager(private val qualityMonitor: NetworkQualityMonitor) {
    
    fun loadImage(url: String, imageView: ImageView) {
        val quality = qualityMonitor.quality.value
        
        val options = when (quality) {
            NetworkQuality.EXCELLENT -> {
                // 高质量图片
                Glide.with(imageView.context)
                    .load(url)
                    .override(1080, 1920)
            }
            NetworkQuality.GOOD -> {
                // 中等质量
                Glide.with(imageView.context)
                    .load(url)
                    .override(720, 1280)
            }
            NetworkQuality.FAIR -> {
                // 低质量
                Glide.with(imageView.context)
                    .load(url)
                    .override(480, 800)
            }
            NetworkQuality.POOR -> {
                // 只加载缩略图
                Glide.with(imageView.context)
                    .load(url)
                    .override(240, 400)
            }
            NetworkQuality.OFFLINE -> {
                // 显示缓存或占位图
                Glide.with(imageView.context)
                    .load(R.drawable.offline_placeholder)
            }
        }
        
        options.into(imageView)
    }
    
    fun loadList(size: Int): Int {
        val quality = qualityMonitor.quality.value
        
        return when (quality) {
            NetworkQuality.EXCELLENT -> size
            NetworkQuality.GOOD -> size
            NetworkQuality.FAIR -> size / 2
            NetworkQuality.POOR -> size / 4
            NetworkQuality.OFFLINE -> 0 // 使用缓存
        }
    }
}

5.3 离线缓存

离线数据管理

kotlin
class OfflineDataManager {
    private val database = Room.databaseBuilder(
        context,
        OfflineDatabase::class.java,
        "offline.db"
    ).build()
    
    suspend fun saveForOffline(page: Page) {
        database.pageDao().insert(page)
    }
    
    suspend fun getOfflinePage(pageNumber: Int): Page? {
        return database.pageDao().get(pageNumber)
    }
    
    fun isOfflineDataAvailable(pageNumber: Int): Boolean {
        // 检查缓存
    }
}

// 使用
class Repository {
    private val offlineManager = OfflineDataManager()
    
    suspend fun getData(page: Int): List<Item> {
        return withContext(Dispatchers.IO) {
            // 1. 尝试网络请求
            try {
                val data = networkService.getData(page)
                offlineManager.saveForOffline(page)
                data
            } catch (e: Exception) {
                // 2. 网络失败,返回缓存
                offlineManager.getOfflinePage(page)?.items ?: emptyList()
            }
        }
    }
}

5.4 数据压缩传输

启用 Gzip

kotlin
// 服务端配置
// Content-Encoding: gzip

// 客户端自动处理
val client = OkHttpClient() // 默认支持 Gzip

自定义压缩

kotlin
class CompressRequestBodyInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        
        if (request.body != null) {
            val originalBody = request.body!!
            val compressed = GzipCompressor.compress(originalBody.bytes())
            
            val compressedBody = RequestBody.create(
                originalBody.contentType(),
                compressed
            )
            
            val newRequest = request.newBuilder()
                .header("Content-Encoding", "gzip")
                .body(compressedBody)
                .build()
            
            return chain.proceed(newRequest)
        }
        
        return chain.proceed(request)
    }
}

5.5 请求优先级

重要请求优先

kotlin
class PriorityDispatcher {
    enum class Priority { HIGH, NORMAL, LOW }
    
    private val highQueue = LinkedBlockingQueue<Request>()
    private val normalQueue = LinkedBlockingQueue<Request>()
    private val lowQueue = LinkedBlockingQueue<Request>()
    
    fun enqueue(request: Request, priority: Priority) {
        when (priority) {
            Priority.HIGH -> highQueue.add(request)
            Priority.NORMAL -> normalQueue.add(request)
            Priority.LOW -> lowQueue.add(request)
        }
        processNext()
    }
    
    private fun processNext() {
        val request = when {
            highQueue.isNotEmpty() -> highQueue.poll()
            normalQueue.isNotEmpty() -> normalQueue.poll()
            lowQueue.isNotEmpty() -> lowQueue.poll()
            else -> return
        }
        
        CoroutineScope(Dispatchers.IO).launch {
            execute(request)
            processNext() // 处理下一个
        }
    }
}

// 使用
priorityDispatcher.enqueue(userRequest, Priority.HIGH)
priorityDispatcher.enqueue(imageRequest, Priority.LOW)

5.6 超时和重试

配置超时

kotlin
val client = OkHttpClient.Builder()
    // 连接超时
    .connectTimeout(5, TimeUnit.SECONDS)
    
    // 读取超时
    .readTimeout(15, TimeUnit.SECONDS)
    
    // 写入超时
    .writeTimeout(10, TimeUnit.SECONDS)
    
    // DNS 超时
    .dns(DnsCache())
    
    .build()

智能重试

kotlin
class RetryInterceptor : Interceptor {
    private val maxRetry = 3
    private val retryDelay = 1000L
    
    override fun intercept(chain: Interceptor.Chain): Response {
        var attempt = 0
        var response = chain.proceed(chain.request())
        
        while (!response.isSuccessful && attempt < maxRetry) {
            attempt++
            
            // 可重试的状态码
            if (response.code !in listOf(400, 401, 403, 404, 405)) {
                // 指数退避
                val delay = retryDelay * attempt
                Thread.sleep(delay)
                
                response = chain.proceed(chain.request())
            } else {
                break
            }
        }
        
        return response
    }
}

图片加载优化

6.1 图片懒加载

RecyclerView 懒加载

kotlin
class ImageAdapter(
    private val items: List<Item>,
    private val imageLoader: ImageLoader
) : RecyclerView.Adapter<ImageAdapter.ViewHolder>() {
    
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val imageView: ImageView = itemView.findViewById(R.id.image)
    }
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = items[position]
        
        // 检查是否可见
        if (isViewVisible(holder.itemView)) {
            imageLoader.load(item.imageUrl, holder.imageView)
        } else {
            // 显示占位图
            holder.imageView.setImageResource(R.drawable.placeholder)
        }
    }
    
    private fun isViewVisible(view: View): Boolean {
        val layoutManager = view.parent as? RecyclerView?.layoutManager
        return layoutManager?.findFirstVisibleItemPosition()?.let { first ->
            val last = layoutManager.findLastVisibleItemPosition()
            val position = (view.parent as RecyclerView.ViewHolder).position
            position in first..last
        } ?: false
    }
    
    override fun onViewRecycled(holder: ViewHolder) {
        // 取消未完成的加载
        imageLoader.cancel(holder.imageView)
        super.onViewRecycled(holder)
    }
}

6.2 图片尺寸优化

按需加载

kotlin
// ❌ 错误:加载完整尺寸图片
Glide.with(context)
    .load("https://example.com/image.jpg")
    .into(imageView)

// ✅ 正确:根据视图尺寸加载
val targetWidth = imageView.width
val targetHeight = imageView.height

Glide.with(context)
    .load("https://example.com/image.jpg")
    .override(targetWidth, targetHeight)
    .into(imageView)

多尺寸图片

kotlin
// 服务器提供多尺寸
data class ImageUrls(
    val thumbnail: String,  // 100x100
    val medium: String,     // 400x400
    val large: String       // 1000x1000
)

fun loadImage(imageUrls: ImageUrls, targetWidth: Int, targetHeight: Int) {
    val url = when {
        targetWidth < 200 -> imageUrls.thumbnail
        targetWidth < 600 -> imageUrls.medium
        else -> imageUrls.large
    }
    
    Glide.with(context)
        .load(url)
        .into(imageView)
}

6.3 图片格式优化

WebP 格式

kotlin
// WebP 比 JPEG 小 30%
// 比 PNG 小 26%

// Glide 自动支持 WebP
Glide.with(context)
    .load("https://example.com/image.webp")
    .into(imageView)

// 优先加载 WebP
Glide.with(context)
    .load("https://example.com/image")
    .format(PictureFormat.WEBP)
    .into(imageView)

图片压缩

kotlin
class ImageCompressor {
    fun compress(image: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap {
        var options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeByteArray(image.toByteArray(), 0, image.toByteArray().size, options)
        
        val outWidth = options.outWidth
        val outHeight = options.outHeight
        var sampleSize = 1
        
        if (outWidth > maxWidth || outHeight > maxHeight) {
            sampleSize = (outWidth.toFloat() / maxWidth).toInt()
        }
        
        options.inSampleSize = sampleSize
        options.inJustDecodeBounds = false
        
        return BitmapFactory.decodeByteArray(image.toByteArray(), 0, image.toByteArray().size, options)
    }
}

6.4 图片缓存优化

Glide 缓存配置

kotlin
// 内存缓存配置
GlideMemoryCacheModule(
    LruResourceCache(context),
    LruBitmapCache(context)
)

// 磁盘缓存配置
Glide.with(context)
    .setDiskCache(DiskLruCacheWrapper(
        context.cacheDir.resolve("glide"),
        100L * 1024 * 1024 // 100MB
    ))

缓存策略

kotlin
Glide.with(context)
    .load(url)
    .skipMemoryCache(false)    // 保存到内存缓存
    .diskCacheStrategy(DiskCacheStrategy.ALL) // 磁盘缓存策略
    .into(imageView)

性能监控

7.1 请求性能监控

拦截器监控

kotlin
class PerformanceMonitoringInterceptor : Interceptor {
    private val metrics = mutableListOf<NetworkMetric>()
    
    override fun intercept(chain: Interceptor.Chain): Response {
        val start = System.currentTimeMillis()
        val url = chain.request().url.toString()
        
        val response = chain.proceed(chain.request())
        
        val end = System.currentTimeMillis()
        val duration = end - start
        
        val metric = NetworkMetric(
            url = url,
            method = chain.request().method,
            code = response.code,
            size = response.body?.contentLength() ?: 0,
            duration = duration,
            timestamp = start
        )
        
        synchronized(metrics) {
            metrics.add(metric)
        }
        
        Log.d("Network", "$url took $duration ms (${metric.size} bytes)")
        
        return response
    }
}

data class NetworkMetric(
    val url: String,
    val method: String,
    val code: Int,
    val size: Long,
    val duration: Long,
    val timestamp: Long
)

7.2 性能报告生成

生成性能报告

kotlin
class PerformanceReportGenerator {
    fun generateReport(metrics: List<NetworkMetric>): PerformanceReport {
        val totalRequests = metrics.size
        val avgDuration = metrics.average { it.duration }
        val avgSize = metrics.average { it.size }
        val slowRequests = metrics.filter { it.duration > 1000 }
        val errors = metrics.filter { it.code >= 400 }
        
        return PerformanceReport(
            totalRequests = totalRequests,
            avgDuration = avgDuration,
            avgSize = avgSize,
            slowRequestCount = slowRequests.size,
            errorCount = errors.size,
            slowRequests = slowRequests,
            errors = errors
        )
    }
}

data class PerformanceReport(
    val totalRequests: Int,
    val avgDuration: Double,
    val avgSize: Double,
    val slowRequestCount: Int,
    val errorCount: Int,
    val slowRequests: List<NetworkMetric>,
    val errors: List<NetworkMetric>
)

7.3 实时性能监控

监控 Dashboard

kotlin
class NetworkMonitor {
    private val _metrics = MutableStateFlow<List<NetworkMetric>>(emptyList())
    val metrics: StateFlow<List<NetworkMetric>> = _metrics
    
    private val _currentRequests = MutableStateFlow(0)
    val currentRequests: StateFlow<Int> = _currentRequests
    
    fun onRequestStart() {
        synchronized(_currentRequests) {
            _currentRequests.value++
        }
    }
    
    fun onRequestEnd(metric: NetworkMetric) {
        synchronized(_currentRequests) {
            _currentRequests.value--
        }
        
        synchronized(metrics) {
            _metrics.value = _metrics.value + metric
        }
    }
    
    fun getStats(): NetworkStats {
        val allMetrics = _metrics.value
        
        return NetworkStats(
            totalRequests = allMetrics.size,
            avgDuration = allMetrics.average { it.duration },
            totalBytes = allMetrics.sumOf { it.size },
            errorRate = allMetrics.count { it.code >= 400 }.toDouble() / allMetrics.size
        )
    }
}

代码示例

8.1 完整的网络优化方案

kotlin
object OptimizedApiClient {
    
    // 1. 连接池配置
    private val connectionPool = ConnectionPool().apply {
        maxSize = 10
        keepAliveDuration = 10L, TimeUnit.MINUTES
    }
    
    // 2. 缓存配置
    private val httpCache = Cache(
        File(FileUtils.getCacheDir(), "http"),
        100L * 1024 * 1024 // 100MB
    )
    
    // 3. DNS 缓存
    private val dnsCache = DnsCache()
    
    // 4. 创建 OkHttpClient
    val client: OkHttpClient = OkHttpClient.Builder()
        .connectionPool(connectionPool)
        .cache(httpCache)
        .dns(dnsCache)
        .connectTimeout(5, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .addInterceptor(PerformanceMonitoringInterceptor())
        .addInterceptor(CacheInterceptor())
        .addInterceptor(RetryInterceptor())
        .build()
    
    // 5. 创建 Retrofit
    val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .build()
    
    // 6. API Service
    fun <T> create(service: Class<T>): T = retrofit.create(service)
}

// 使用示例
class UserRepository {
    private val api = OptimizedApiClient.retrofit.create(UserApi::class.java)
    private val memoryCache = LruCache<User>(20)
    
    suspend fun getUser(userId: String): User {
        // 检查内存缓存
        memoryCache.get(userId)?.let { return it }
        
        // 网络请求(自动使用缓存)
        val user = api.getUser(userId)
        
        // 存入缓存
        memoryCache.put(userId, user)
        
        return user
    }
    
    suspend fun getUsers(page: Int, size: Int): List<User> {
        return api.getUsers(page, size)
    }
}

8.2 图片加载优化示例

kotlin
class OptimizedImageLoader {
    
    fun loadOptimized(
        context: Context,
        url: String,
        imageView: ImageView,
        placeholder: Int = R.drawable.placeholder,
        error: Int = R.drawable.error
    ) {
        // 获取目标尺寸
        val requestOptions = RequestOptions()
            .placeholder(placeholder)
            .error(error)
            .centerCrop()
            .override(
                imageView.width.takeIf { it > 0 },
                imageView.height.takeIf { it > 0 }
            )
            // 启用 WebP
            .format(PictureFormat.WEBP)
            // 内存缓存
            .skipMemoryCache(false)
            // 磁盘缓存
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            // 淡入动画
            .transition(withCrossFade())
    
        Glide.with(context)
            .load(url)
            .apply(requestOptions)
            .into(imageView)
    }
    
    // 列表图片优化
    fun loadForList(
        context: Context,
        url: String,
        imageView: ImageView,
        position: Int
    ) {
        // 根据列表项尺寸加载
        val targetWidth = imageView.width.takeIf { it > 0 } ?: 200
        val targetHeight = imageView.height.takeIf { it > 0 } ?: 200
        
        Glide.with(context)
            .load(url)
            .override(targetWidth, targetHeight)
            .centerCrop()
            .placeholder(R.drawable.placeholder)
            .error(R.drawable.error)
            .tag("list_$position")
            .into(imageView)
    }
    
    // 取消请求
    fun cancel(view: ImageView) {
        Glide.with(view.context)
            .clear(view)
    }
}

// RecyclerView 中使用
class OptimizedImageAdapter : RecyclerView.Adapter<OptimizedImageAdapter.ViewHolder>() {
    private val items = mutableListOf<String>()
    private val imageLoader = OptimizedImageLoader()
    
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val imageView: ImageView = itemView.findViewById(R.id.image)
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_image, parent, false)
        return ViewHolder(view)
    }
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val url = items[position]
        imageLoader.loadForList(holder.itemView.context, url, holder.imageView, position)
    }
    
    override fun onViewRecycled(holder: ViewHolder) {
        imageLoader.cancel(holder.imageView)
        super.onViewRecycled(holder)
    }
    
    override fun getItemCount() = items.size
}

面试考点

基础题

1. 如何优化网络请求速度?

1. 连接池:复用 TCP 连接
2. HTTP/2:多路复用
3. 压缩:Gzip/Brotli
4. 缓存:内存 + 磁盘
5. 减少请求数:合并请求
6. 懒加载:按需加载
7. CDN:就近访问

2. 什么是弱网优化?

1. 降级策略:降低图片质量
2. 离线缓存:使用本地数据
3. 请求优先级:重要请求优先
4. 超时重试:智能重试机制
5. 数据压缩:减少传输量

3. 缓存策略有哪些?

1. 内存缓存:快速访问
2. 磁盘缓存:持久化
3. CDN 缓存:就近访问
4. 浏览器缓存:客户端缓存
5. 组合缓存:多级缓存

进阶题

1. 如何实现请求合并?

1. 批量请求:一次请求多个资源
2. GraphQL:按需提供数据
3. 接口聚合:服务端合并
4. 延迟合并:前端等待后合并

2. HTTP/2 对性能的提升?

1. 多路复用:单连接并发
2. 头部压缩:减少传输
3. 服务器推送:主动推送
4. 二进制协议:解析更快

3. 图片加载优化方案?

1. 按需尺寸:根据视图加载
2. 懒加载:可见区域加载
3. 格式优化:WebP 格式
4. 多级缓存:内存 + 磁盘
5. 占位图:提升体验

高级题

1. 如何实现网络性能监控?

1. 拦截器:记录请求时间
2. 埋点:上报性能数据
3. 告警:异常检测
4. 分析:性能瓶颈定位
5. 优化:持续改进

2. 弱网环境下的最佳实践?

1. 检测网络质量
2. 自适应降级
3. 离线优先
4. 预加载
5. 数据同步

3. 如何优化列表加载性能?

1. 分页加载
2. 预加载下一页
3. 图片懒加载
4. 缓存利用
5. 异步加载

总结

核心优化点

  1. 请求端优化

    • 请求合并
    • 连接池
    • DNS 优化
  2. 传输优化

    • 数据压缩
    • HTTP/2
    • 二进制协议
  3. 缓存策略

    • 多级缓存
    • 缓存失效
    • 离线缓存
  4. 弱网优化

    • 降级策略
    • 离线优先
    • 智能重试
  5. 性能监控

    • 请求监控
    • 性能报告
    • 持续优化

最佳实践

kotlin
// 1. 全局共享 OkHttpClient
object ApiClient {
    val client: OkHttpClient = OkHttpClient.Builder()
        .connectionPool(ConnectionPool(10, 10, TimeUnit.MINUTES))
        .cache(Cache(cacheDir, 100 * 1024 * 1024))
        .build()
}

// 2. 使用缓存
client.cache().edit().apply {
    // 配置缓存策略
}

// 3. 图片优化
Glide.with(context)
    .load(url)
    .override(width, height)
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .into(imageView)

// 4. 监控性能
client.addInterceptor(MonitoringInterceptor())

// 5. 弱网降级
when (networkQuality) {
    EXCELLENT -> loadFullQuality()
    POOR -> loadLowQuality()
    OFFLINE -> loadCached()
}

本文约 15000 字,涵盖了网络优化的核心知识点。