网络 / 缓存 / 存储 — 知识详解
更新: 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 Modified2. 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)
// 默认:最多 5 个空闲连接,空闲 5 分钟回收
ConnectionPool(5, 5, TimeUnit.MINUTES)连接复用条件:相同的 host + port + scheme。HTTP/2 还可以在同一连接上多路复用。
连接池通过后台线程定期清理空闲连接(cleanupRunnable)。
2.4 缓存策略(CacheInterceptor)
OkHttp 的缓存基于 DiskLruCache,遵循 HTTP 缓存规范:
- 检查是否有缓存响应
- 根据 Cache-Control 判断缓存是否过期
- 未过期 → 直接返回缓存(强缓存)
- 过期 → 发送条件请求(If-None-Match / If-Modified-Since)
- 服务端返回 304 → 使用缓存;200 → 使用新响应并更新缓存
3. Retrofit
3.1 动态代理
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 创建代理对象。调用接口方法时:
- 解析方法上的注解(@GET、@POST、@Path、@Query 等)
- 构建 OkHttp Request
- 通过 CallAdapter 适配返回类型(Call、Flow、suspend 函数)
- 通过 Converter 转换响应体(Gson、Moshi 等)
3.2 适配器模式
- CallAdapter:将 OkHttp 的 Call 适配为其他类型(RxJava Observable、Kotlin suspend)
- Converter:将 ResponseBody 转换为目标类型(JSON → 数据类)
4. 缓存设计
4.1 三级缓存架构
内存缓存(LruCache)
↓ 未命中
磁盘缓存(DiskLruCache)
↓ 未命中
网络请求
↓ 响应
写入磁盘缓存 + 内存缓存4.2 LruCache
// 基于 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 的官方替代:
// 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 模式
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 / OkHttpStreamFetcher7.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
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
| 维度 | Protobuf | JSON |
|---|---|---|
| 格式 | 二进制 | 文本 |
| 体积 | 小(约 JSON 的 1/3-1/10) | 大 |
| 解析速度 | 快(直接映射内存) | 慢(需要解析字符串) |
| 可读性 | 不可读 | 可读 |
| Schema | 需要 .proto 文件定义 | 无 Schema(灵活但不安全) |
| 适用场景 | 高性能、大数据量、RPC | REST API、调试友好 |
Android 中 Protobuf 的应用:gRPC 通信、DataStore Proto 模式、MMKV 内部编码。
