Skip to content

网络 / 缓存 / 存储 — 知识详解

更新: 5/15/2026 字数: 0 字 时长: 0 分钟

OkHttp 与 Retrofit 原理内容较多,已拆分为独立文件:

  • okhttp-retrofit-deep.md — OkHttp 拦截器链源码、Dispatcher 调度器、连接池、Retrofit 动态代理、suspend 支持、Converter/CallAdapter

1. HTTP/HTTPS 协议

1.1 TCP 三次握手

客户端              服务端
  │── SYN(seq=x) ──→│        第一次:客户端发起连接
  │←─ SYN+ACK ─────│        第二次:服务端确认并发起连接(seq=y, ack=x+1)
  │── ACK(ack=y+1)─→│        第三次:客户端确认

为什么三次?两次无法确认客户端的接收能力。如果只有两次,服务端无法确认客户端收到了自己的 SYN+ACK。

1.2 TCP 四次挥手

客户端              服务端
  │── FIN ──────→│    第一次:客户端请求关闭
  │←─ ACK ──────│    第二次:服务端确认(可能还有数据要发)
  │←─ FIN ──────│    第三次:服务端数据发完,请求关闭
  │── ACK ──────→│    第四次:客户端确认
  │ TIME_WAIT(2MSL) │

为什么四次?因为 TCP 是全双工,每个方向需要单独关闭。服务端收到 FIN 后可能还有数据要发,所以 ACK 和 FIN 分开发。

TIME_WAIT(2MSL):确保最后一个 ACK 能到达服务端,以及让网络中残留的数据包过期。

1.3 TLS 握手(HTTPS)

客户端                          服务端
  │── ClientHello ──────────→│   支持的TLS版本、加密套件、随机数A
  │←─ ServerHello ──────────│   选定的加密套件、随机数B、证书
  │   验证证书(CA 链)          │
  │── 预主密钥(用证书公钥加密)──→│
  │   双方用 随机数A+B+预主密钥   │
  │   生成对称密钥               │
  │←─ Finished ─────────────│
  │── Finished ─────────────→│
  │   后续用对称密钥加密通信      │

HTTPS = HTTP + TLS。非对称加密交换密钥,对称加密传输数据。

1.4 HTTP/2

  • 多路复用:一个 TCP 连接上并行多个请求/响应(Stream),解决 HTTP/1.1 的队头阻塞
  • 头部压缩:HPACK 算法,维护静态/动态表,减少重复头部传输
  • 服务端推送:服务端主动推送资源
  • 二进制分帧:数据分为 HEADERS 帧和 DATA 帧

1.5 HTTP 缓存

强缓存(不发请求):
  Cache-Control: max-age=3600    优先级高
  Expires: Thu, 01 Dec 2025      绝对时间,有时区问题

协商缓存(发请求验证):
  Last-Modified / If-Modified-Since    精度秒级
  ETag / If-None-Match                 精确到内容hash,优先级高
  命中返回 304 Not Modified

2. OkHttp 源码分析

2.1 整体架构

OkHttpClient
  → newCall(Request) → RealCall
    → execute() / enqueue()
      → getResponseWithInterceptorChain()  // 拦截器链

2.2 拦截器链(责任链模式)

应用拦截器(用户添加)
  → RetryAndFollowUpInterceptor    // 重试和重定向
    → BridgeInterceptor            // 补充请求头(Content-Type、Cookie、gzip)
      → CacheInterceptor           // 缓存处理
        → ConnectInterceptor       // 建立连接(从连接池获取或新建)
          → 网络拦截器(用户添加)
            → CallServerInterceptor // 发送请求、读取响应

每个拦截器调用 chain.proceed(request) 传递给下一个拦截器,形成链式调用。

2.3 连接池(ConnectionPool)

java
// 默认:最多 5 个空闲连接,空闲 5 分钟回收
ConnectionPool(5, 5, TimeUnit.MINUTES)

连接复用条件:相同的 host + port + scheme。HTTP/2 还可以在同一连接上多路复用。

连接池通过后台线程定期清理空闲连接(cleanupRunnable)。

2.4 缓存策略(CacheInterceptor)

OkHttp 的缓存基于 DiskLruCache,遵循 HTTP 缓存规范:

  1. 检查是否有缓存响应
  2. 根据 Cache-Control 判断缓存是否过期
  3. 未过期 → 直接返回缓存(强缓存)
  4. 过期 → 发送条件请求(If-None-Match / If-Modified-Since)
  5. 服务端返回 304 → 使用缓存;200 → 使用新响应并更新缓存

3. Retrofit

3.1 动态代理

kotlin
interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
}

val api = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(ApiService::class.java) // 动态代理

create() 内部使用 Proxy.newProxyInstance 创建代理对象。调用接口方法时:

  1. 解析方法上的注解(@GET、@POST、@Path、@Query 等)
  2. 构建 OkHttp Request
  3. 通过 CallAdapter 适配返回类型(Call、Flow、suspend 函数)
  4. 通过 Converter 转换响应体(Gson、Moshi 等)

3.2 适配器模式

  • CallAdapter:将 OkHttp 的 Call 适配为其他类型(RxJava Observable、Kotlin suspend)
  • Converter:将 ResponseBody 转换为目标类型(JSON → 数据类)

4. 缓存设计

4.1 三级缓存架构

内存缓存(LruCache)
  ↓ 未命中
磁盘缓存(DiskLruCache)
  ↓ 未命中
网络请求
  ↓ 响应
写入磁盘缓存 + 内存缓存

4.2 LruCache

java
// 基于 LinkedHashMap(accessOrder=true)实现
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8; // 使用最大内存的 1/8

LruCache<String, Bitmap> cache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getByteCount() / 1024;
    }
};

LinkedHashMap 的 accessOrder=true 模式:每次访问元素会将其移到链表尾部,头部就是最久未使用的元素。超过容量时移除头部。


5. 本地存储方案

5.1 SharedPreferences 的问题

  • 全量读写:每次修改都要序列化整个 Map 写入 XML 文件
  • ANR 风险apply() 异步写入,但 Activity onStop 时 QueuedWork.waitToFinish() 会同步等待写入完成
  • 不支持多进程:MODE_MULTI_PROCESS 已废弃,不可靠
  • 类型不安全:存取时需要手动指定类型

5.2 MMKV

MMKV 使用 mmap 内存映射文件:

进程内存空间          文件系统
┌──────────┐     ┌──────────┐
│ 虚拟内存页 │ ←→  │ 磁盘文件  │
└──────────┘     └──────────┘
     mmap 映射

优势:

  • 写入即持久化:写入内存页后由 OS 负责刷盘,不需要手动 fsync
  • 增量写入:使用 protobuf 编码,append 写入,不需要全量序列化
  • 多进程安全:文件锁 + mmap 共享内存
  • 性能:比 SP 快 10-100 倍

5.3 DataStore

Jetpack DataStore 是 SP 的官方替代:

kotlin
// Preferences DataStore(键值对)
val dataStore = context.createDataStore(name = "settings")
val DARK_MODE = booleanPreferencesKey("dark_mode")

// 读取
val darkMode: Flow<Boolean> = dataStore.data.map { it[DARK_MODE] ?: false }

// 写入
dataStore.edit { it[DARK_MODE] = true }

优势:基于 Flow 的异步 API、线程安全、不会阻塞 UI 线程、支持 Proto 序列化。


6. Repository 模式

kotlin
class UserRepository(
    private val api: ApiService,
    private val userDao: UserDao,
    private val cache: LruCache<Int, User>
) {
    fun getUser(id: Int): Flow<User> = flow {
        // 1. 内存缓存
        cache.get(id)?.let { emit(it); return@flow }

        // 2. 数据库缓存
        userDao.getUser(id)?.let {
            cache.put(id, it)
            emit(it)
        }

        // 3. 网络请求
        val user = api.getUser(id)
        userDao.insert(user)
        cache.put(id, user)
        emit(user)
    }.flowOn(Dispatchers.IO)
}

Repository 是数据层的统一入口,封装多个数据源的访问逻辑,对上层(ViewModel)屏蔽数据来源细节。


7. Glide 源码分析

7.1 整体架构

Glide.with(context)     → RequestManager(生命周期管理)
    .load(url)          → RequestBuilder(构建请求)
    .into(imageView)    → 触发请求
        → Engine(调度核心)
            → 查活动资源 → 查内存缓存 → 查磁盘缓存 → 网络请求

7.2 生命周期管理

Glide.with(activity) 会向 Activity 注入一个空的 SupportRequestManagerFragment,通过 Fragment 的生命周期回调管理请求:

  • onStart → 恢复请求
  • onStop → 暂停请求
  • onDestroy → 取消请求并释放资源

7.3 四级缓存

1. 活动资源(ActiveResources)
   - WeakReference 持有正在使用的资源
   - 引用计数,使用中的资源不会被 LRU 回收

2. 内存缓存(MemoryCache / LruResourceCache)
   - LruCache 实现
   - 资源不再使用时从活动资源移到内存缓存

3. 磁盘缓存(DiskLruCache)
   - DATA:原始数据(网络下载的原图)
   - RESOURCE:变换后的数据(裁剪/缩放后的图)
   - 默认策略 AUTOMATIC:远程数据缓存原图,本地数据缓存变换后的图

4. 网络/数据源
   - HttpUrlFetcher / OkHttpStreamFetcher

7.4 缓存 Key

缓存 key 由多个因素组成:URL + 宽高 + 变换参数 + 签名等。所以同一张图片不同尺寸的 ImageView 会有不同的缓存。

7.5 Bitmap 复用

Glide 维护了一个 BitmapPool(LruBitmapPool),解码新图片时优先从 Pool 中获取合适大小的 Bitmap(inBitmap),避免频繁分配和回收内存。


8. WebSocket

8.1 与 HTTP 的区别

  • HTTP:请求-响应模式,客户端发起
  • WebSocket:全双工通信,服务端可以主动推送

8.2 连接过程

客户端发送 HTTP 升级请求:
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xxx

服务端响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: yyy

→ 后续通过 WebSocket 帧通信

8.3 OkHttp WebSocket

kotlin
val client = OkHttpClient()
val request = Request.Builder().url("wss://example.com/ws").build()

client.newWebSocket(request, object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        webSocket.send("Hello")
    }
    override fun onMessage(webSocket: WebSocket, text: String) {
        // 收到消息
    }
    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        // 连接失败,需要重连
    }
})

9. Protobuf vs JSON

维度ProtobufJSON
格式二进制文本
体积小(约 JSON 的 1/3-1/10)
解析速度快(直接映射内存)慢(需要解析字符串)
可读性不可读可读
Schema需要 .proto 文件定义无 Schema(灵活但不安全)
适用场景高性能、大数据量、RPCREST API、调试友好

Android 中 Protobuf 的应用:gRPC 通信、DataStore Proto 模式、MMKV 内部编码。