Skip to content

Commit 5f5f535

Browse files
committed
docs: Update blog posts on Android WebView session consistency and macOS dd speed trick.
1 parent eb3ac5c commit 5f5f535

2 files changed

Lines changed: 91 additions & 235 deletions

File tree

_posts/2025-12-23-android-webview-session-consistency.md

Lines changed: 45 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -6,163 +6,94 @@ categories: android webview
66
tags: android webview okhttp cookie
77
---
88

9-
## 背景
9+
---
10+
layout: post
11+
title: "Android WebView 拦截请求导致的会话不一致问题及解决方案"
12+
date: 2025-12-23 21:00:00 +0000
13+
categories: android webview
14+
tags: android webview okhttp cookie
15+
---
16+
17+
## 问题现象
1018

11-
在 Android 开发中,为了优化网络请求,我们经常会在 WebView 中使用 `shouldInterceptRequest` 方法拦截请求,并替换为 OkHttp 来执行。这样做的好处是可以使用 HTTPDNS、统一的网络配置、请求日志等
19+
在 Android 开发中,常通过重写 WebView `shouldInterceptRequest` 方法,使用自定义网络栈(如 OkHttp)来接管部分资源请求(例如仅拦截 GET 请求以使用 HTTPDNS 或统一缓存)
1220

13-
然而,如果只拦截部分请求(比如只拦截 GET 请求),就可能导致**会话(Session)不一致**的问题。
21+
这种**部分拦截**的策略极易引发一个严重问题:**会话(Session)不一致**
22+
用户在登录后(原生网络栈 POST 请求),后续页面依然处于未登录状态;或图形验证码校验始终不过。
1423

15-
## 问题描述
24+
## 根本原因
1625

17-
典型的场景如下:
26+
会话不一致的根本原因是:**原生 WebView 的 `CookieManager` 与第三方网络库(OkHttp)的 Cookie 存储相互隔离且未同步。**
1827

1928
```
20-
用户登录流程
21-
1. [GET] 加载登录页面 → 拦截走 OkHttp
22-
2. [POST] 提交登录表单 → 走 WebView 原生
23-
3. [GET] 获取用户信息 → 拦截走 OkHttp
29+
用户登录流程的时序割裂
30+
1. [GET] 加载登录页面 → 被拦截,走 OkHttp 请求
31+
2. [POST] 提交登录表单 → 未被拦截,走 WebView 原生请求,服务端返回 Set-Cookie
32+
3. [GET] 获取用户信息 → 被拦截,走 OkHttp 请求
2433
```
2534

26-
问题出现在:
27-
- OkHttp 收到的 `Set-Cookie` 响应头没有同步到 WebView 的 `CookieManager`
28-
- WebView 设置的 Cookie 也可能没有同步到 OkHttp 的请求里
35+
- WebView 原生请求收到的 `Set-Cookie` 会保存在 `CookieManager` 中。
36+
- 后续拦截走 OkHttp 的请求,如果没有提取并携带 `CookieManager` 中的 Cookie,或者 OkHttp 响应的 `Set-Cookie` 没有反向同步给 `CookieManager`,两套网络栈使用的就是不同的 Session 标识。
2937

30-
这就导致 GET 请求和 POST 请求使用的是不同的 Session,服务端自然就认不出你了。
38+
## 解决方案
3139

32-
## 问题代码
40+
核心思路:**在拦截逻辑中,充当 Cookie 的双向同步桥梁。**
3341

34-
```kotlin
35-
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
36-
// 只拦截 GET 请求
37-
if ("GET".equals(request?.method, true)) {
38-
return getResponseByOkHttp(request)
39-
}
40-
return super.shouldInterceptRequest(view, request)
41-
}
42+
> 注:`WebResourceRequest.requestHeaders` 在调用 `shouldInterceptRequest` 时,系统已经自动从 `CookieManager` 注水了最新的 Cookie,因此只需要关注**响应后**的 Cookie 回写。
4243
44+
在拦截实现中增加响应后的 Cookie 同步逻辑:
45+
46+
```kotlin
4347
private fun getResponseByOkHttp(request: WebResourceRequest?): WebResourceResponse? {
4448
request ?: return null
4549
val url = request.url.toString()
4650
val requestBuilder = okhttp3.Request.Builder()
4751
.url(url)
4852
.method(request.method, null)
4953

50-
// ❌ 问题1:直接使用 requestHeaders,未确认 Cookie 来源
54+
// 1. 携带 WebView 已有的 Cookie (系统已注入到 requestHeaders)
5155
request.requestHeaders?.forEach { (key, value) ->
5256
requestBuilder.addHeader(key, value)
5357
}
5458

5559
val response = okhttpClient.newCall(requestBuilder.build()).execute()
5660

57-
// ❌ 问题2:响应中的 Set-Cookie 被直接丢弃了!
61+
// 2. 关键:同步响应头中的 Set-Cookie 到 WebView的 CookieManager
62+
// 必须在判断状态码之前同步,以防 302 重定向丢失 Cookie
63+
syncCookiesToManager(url, response)
64+
65+
if (response.code != 200) {
66+
response.close()
67+
return null
68+
}
5869

5970
val body = response.body ?: return null
60-
return WebResourceResponse(...)
71+
// 构建并返回 WebResourceResponse...
6172
}
62-
```
63-
64-
## 解决方案
65-
66-
核心思路:**将 OkHttp 响应中的 `Set-Cookie` 同步到 WebView 的 `CookieManager`**
6773

68-
### 修复后的代码
69-
70-
```kotlin
7174
/**
72-
* 将响应中的 Set-Cookie 同步到 CookieManager
75+
* 将 OkHttp 响应中的 Set-Cookie 写入 CookieManager
7376
*/
7477
private fun syncCookiesToManager(url: String, response: okhttp3.Response) {
7578
val cookies = response.headers("Set-Cookie")
7679
if (cookies.isNotEmpty()) {
7780
val cookieManager = android.webkit.CookieManager.getInstance()
7881
cookies.forEach {
79-
Timber.d("syncCookiesToManager: $url -> $it")
8082
cookieManager.setCookie(url, it)
8183
}
82-
cookieManager.flush() // 确保立即持久化
83-
}
84-
}
85-
86-
private fun getResponseByOkHttp(request: WebResourceRequest?): WebResourceResponse? {
87-
request ?: return null
88-
val url = request.url.toString()
89-
val requestBuilder = okhttp3.Request.Builder()
90-
.url(url)
91-
.method(request.method, null)
92-
93-
// ✅ 直接使用 requestHeaders
94-
// WebView 在调用 shouldInterceptRequest 时已经从 CookieManager 获 取了 Cookie
95-
request.requestHeaders?.forEach { (key, value) ->
96-
requestBuilder.addHeader(key, value)
97-
}
98-
99-
val response = okhttpClient.newCall(requestBuilder.build()).execute()
100-
101-
// ✅ 关键:同步响应中的 Cookie 到 CookieManager
102-
// 注意:要在判断状态码之前同步,因为 302 重定向也可能带 Set-Cookie
103-
syncCookiesToManager(url, response)
104-
105-
if (response.code != 200) {
106-
response.close()
107-
return null
108-
}
109-
110-
val body = response.body ?: return null
111-
// ... 构建 WebResourceResponse
112-
}
113-
```
114-
115-
### 关键点解释
116-
117-
1. **为什么只需要处理响应的 Cookie?**
118-
119-
因为 `WebResourceRequest.requestHeaders` 已经包含了 WebView 从 `CookieManager` 获取的 Cookie。WebView 在调用 `shouldInterceptRequest` 之前 ,会自动把当前 URL 对应的 Cookie 放进 `requestHeaders` 里。
120-
121-
2. **为什么要在判断状态码之前同步 Cookie?**
122-
123-
HTTP 302 重定向响应也可能携带 `Set-Cookie`。如果先判断状态码再同步,就会丢失这些 Cookie。
124-
125-
3. **`flush()` 的作用**
126-
127-
`CookieManager.setCookie()` 是异步的,调用 `flush()` 可以确保 Cookie 立即持久化到磁盘,避免因为进程被杀而丢失。
128-
129-
## 验证方法
130-
131-
1.`syncCookiesToManager` 添加日志,确认 Cookie 被正确同步
132-
2. 测试登录流程:登录 → 刷新页面 → 确认用户状态保持
133-
3. 测试验证码场景:获取验证码图片 → 提交验证码 → 确认校验通过
134-
135-
## 其他注意事项
136-
137-
### 不适合拦截的域名
138-
139-
某些第三方域名(如支付页面)可能有额外的安全校验,建议排除:
140-
141-
```kotlin
142-
val FILTER_HOSTS = listOf(
143-
"qq.com", // 腾讯
144-
"alipay.com", // 支付宝
145-
// ...
146-
)
147-
148-
private fun shouldInterceptToOkhttp(request: WebResourceRequest?): Boolean {
149-
val url = request?.url ?: return false
150-
if ("GET".equals(request.method, true)) {
151-
// 过滤掉不需要拦截的域名
152-
return FILTER_HOSTS.none { url.toString().contains(it) }
84+
cookieManager.flush() // 确保立即持久化到本地,防止进程被杀导致丢失
15385
}
154-
return false
15586
}
15687
```
15788

158-
### HTTPDNS 可能带来的问题
159-
160-
使用 HTTPDNS 会让请求直连 IP,但 HTTP 请求的 `Host` 头仍然是域名。某些 服务端可能对 IP 和 Host 做额外校验,需要注意。
89+
## 扩展建议
16190

162-
## 总结
91+
**1. 过滤第三方域名**
92+
并非所有请求都适合拦截。涉及第三方支付、授权的网页,建议通过白名单/黑名单机制过滤,让原生 WebView 自行处理,避免引发未知的安全或跨域 Cookie 异常。
16393

164-
WebView 请求拦截是把双刃剑。用得好可以优化网络性能,用不好就会导致各种 诡异的会话问题。关键是要记住:
94+
**2. HTTPDNS 附带效应**
95+
使用 HTTPDNS 替换域名为 IP 后发起请求,其真实的 `Host` 头需要手动补全,否则可能触发 CDN 阻断或服务端 Host 校验严格环境下的 400 错误。
16596

166-
> **拦截了请求,就要接管 Cookie 同步的责任。**
97+
## 小结
16798

168-
希望这篇文章能帮助你少踩一个坑
99+
WebView 请求拦截是优化利器,但**拦截了请求,就必须完整接管并维护 HTTP 的状态机(Cookie/Session)**。构建稳定的双向 Cookie 同步机制,是此类架构落地的必修课

0 commit comments

Comments
 (0)