96SEO 2026-06-22 04:36 0
先说说背景,咱们hen多项目里dou要在 App 里塞个 WebView,结果页面卡、资源慢,老是被用户吐槽。
WebView 代理到底是干嘛的?其实就是在 Native 那层拦截 WebView 发起的所有网络请求。

然后你Ke以自己决定:走本地缓存、注入鉴权头、甚至改 DNS。
说白了就是给 H5 加上一层「保镖」,帮它挑好路子。
Zui常见的拦截入口——shouldInterceptRequestAndroid 提供的 WebViewClient.shouldInterceptRequest,每次 WebView 想去拉资源时系统先问你:“要不要自己处理?”
Ru果你返回 null,它就照常走网络;不然你把自定义的 WebResourceResponse 丢过去,它直接用你的数据。
注意,这个方法在子线程跑,别在里边搞 UI,不然会崩。
一步步搭建代理层先弄个「资源映射表」吧。我们一般把每个业务包的资源清单放在 meta.json 里里面写 URL 前缀、文件路径、SHA256 校验值。
{
"bizId": "business_a",
"version": "1.3.0",
"urlPrefix": "https://h5.example.com/a/",
"resources": {
"index.html": "sha256:abc123...",
"main.js": "sha256:def456..."
}
}
加载时先读这个 JSON,放进内存的 Map 里查询速度嗖嗖的。
从 URL 找本地文件fun tryLocal: WebResourceResponse? {
val url = request.url.toString
val pack = findPackByUrl ?: return null
val relPath = url.removePrefix
val file = pack.getFile ?: return null
if ) return null
return buildResponse
}
这里面Zui关键的是 verifyHash——别省!hen多同事一开始偷懒直接返回本地文件,结果上线后发现文件被系统安全软件篡改了JS 报错白屏。
有些业务必须带 token。Zui干净的办法就是在拦截层统一加上:
val newHeaders = request.requestHeaders.toMutableMap
newHeaders = "Bearer ${getToken}"
newHeaders = BuildConfig.VERSION_NAME
return executeRequest, request.method, newHeaders)
Ru果公司内部有专属域名解析需求,Ke以在这里手动把域名换成 IP,然后再往 Header 里塞回原始 Host:
val host = uri.host ?: return null
val ip = DnsResolver.resolve ?: return null
val ipUrl = uri.buildUpon.authority.build.toString
headers = host
return executeRequest
图片缓存也Neng抢占流量
Cruise 的 Glide 磁盘缓存Yi经帮我们把大多数商品图缓存下来。
fun tryImageCache: WebResourceResponse? {
if ) return null
val cacheFile = GlideUtils.getDiskCache ?: return null
val mime = guessMimeFromUrl
return WebResourceResponse)
}
离线包 VS Service Worker:到底选哪一个?
不少人会问:“为啥不用 Service Worker?”哈哈,这问题hen常见。
Service Worker 是浏览器端的离线方案,对纯 PWA 超级友好,但它只Neng在 Chrome/Edge 那些原生浏览器里跑。
我们的场景是:App 内嵌 H5,有 Native 层Ke以直接控制资源。用 shouldInterceptRequest geng省事,也geng安全——不怕用户关掉 Service Worker 就失效。
问: 我们写了这么完整的离线包方案,为啥百度搜索抓不到? 答: 百度爬虫只会抓取公开可访问的 HTTP/HTTPS 页面而我们的资源dou藏在 App 的 Assets 或本地磁盘里。除非把离线包同步到服务器上,否则爬虫根本kan不到。想让搜索引擎收录,就得提供一个外网可访问的 fallback URL,让爬虫走正常网络获取内容。
SOP:从零实现一个完整代理方案 #1 初始化资源管理器object PackManager {
private val packs = mutableMapOf
private val lock = ReentrantReadWriteLock
fun loadAll {
lock.write {
// 扫描 assets/h5_packages/*.json 并解析成 Pack 对象
}
}
fun findPackByUrl: Pack? {
lock.read {
return packs.values.find { url.startsWith }
}
}
}
#2 实现拦截逻辑
override fun shouldInterceptRequest(
view: WebView,
request: WebResourceRequest
): WebResourceResponse? {
// 1️⃣ 本地图片缓存抢先
tryImageCache)?.let { return it }
// 2️⃣ 离线包命中检查
tryLocal?.let { return it }
// 3️⃣ 鉴权/自定义 DNS 注入层
proxyNetwork?.let { return it }
// dou不匹配,交给系统默认处理
return super.shouldInterceptRequest
}
#3 网络请求转发实现
fun executeRequest(
url: String,
method: String,
headers: Map
): WebResourceResponse {
val client = OkHttpClient.Builder
.dns)
.build
val reqBuilder = Request.Builder.url.method
headers.forEach { -> reqBuilder.addHeader }
val resp = client.newCall).execute
val mime = resp.body?.contentType?.toString ?: "application/octet-stream"
// 必须加 CORS 响应头,否则跨域资源会被 Chromium 拒绝
val respHeaders = mapOf(
"Access-Control-Allow-Origin" to "*",
"Cache-Control" to "no-cache"
)
return WebResourceResponse(
mime,
resp.body?.contentType?.charset?.name ?: "utf-8",
resp.code,
resp.message,
respHeaders,
resp.body?.byteStream
)
}
Troubleshooting 小贴士
MIME 类型一定要对上。 比如 .woff2 → font/woff2、.js → application/javascript、.css → text/css、.html → text/html。
CORS 必不可少。 拦截后返回的响应若缺少 "Access-Control-Allow-Origin":"*", Chrome 会直接报错不渲染。
P OST body 无法直接获取。 Ru果业务真的要改 POST 内容,只Neng让 H5 用 JSBridge 自己把 fetch/XHR 替换掉,然后交给 Native 发起新请求。
Avoid double‑read InputStream. 拦截层Ru果想打印日志,请先把 stream 全部读进 byte 再包装成 ByteArrayInputStream, 否则原始流会被消费掉导致白屏。
Thema “为什么百度不收录”。 如前所述,因为内容只存在本地,没有公开 URL,搜索引擎自然抓不到。解决办法是提供 fallback API 或者将关键页面同步到 CDN 上供爬虫访问。
Killer bug: 某些 ROM 会自动扫描 assets 并Zuo「安全加固」导致文件权限变成只读,从而读取失败。记得在打包时加上 .nomedia , 或者使用内部私有目录存放离线包。
DPI / 多语言兼容: Ru果 H5 有多语言切换,需要在 meta.json 里为每种语言维护独立路径,否则只Neng返回默认语言资源,引起 UI 异常。
#4 增量geng新与安全签名A/B 测试经常需要小幅度geng新 JS,而全量下载太浪费流量。我们用了 bsdiff/bspatch Zuo增量补丁:
suspend fun checkAndUpdate{
val localMeta = getLocalMeta
val remoteMeta = fetchRemoteMeta
if return
if ){
downloadAndApplyPatch
}else{
downloadFullPack
}
verifyAndInstall
}
*签名校验* 同样重要——每次下载完后用 RSA 公钥校验签名文件,一旦验证失败立刻回滚并报警。别以为内部网络就安全,那天我们公司的测试环境被黑客植入了恶意 JS,导致所有用户弹出奇怪弹窗,好尴尬!所以一定要Zuo好完整性校验和版本回滚机制。
#5 性Neng监控 & 崩溃防护
P99 加载时长监控: 记录每次拦截到返回本地或网络耗时用 APM 打点,kan是否出现异常突增。
DexGuard 混淆防止反射破解: 因为我们有通过反射设置旧版 Android 系统代理的代码,Ru果被逆向可Neng泄露内部接口,要混淆关键类名和字段名。
Crashed on main thread: 拦截层千万别锁主线程,大多数业务dou是读写锁分离,Ru果写锁卡住子线程,会导致 UI 卡死甚至 ANR。
Error fallback: 任何异常dou应该返回 null,让系统继续走网络,不然一处小 bug 就会导致全局白屏。
整个方案其实就是三层堆叠:① 本地缓存/离线包;② 鉴权/自定义 DNS;③ 网络兜底。各自职责清晰,你想要哪个功Neng就打开对应层,不想要就关掉,不影响其他层运行。
说实话,一开始我也以为只要写几行代码就Neng搞定代理,其实坑比山还高——MIME 错误、CORS 丢失、POST body 抓不到、流只Neng读一次……每踩一次坑,我dou忍不住笑出声来:哈哈,这玩意儿真是「技术细节」界的大坑啊!不过踩完以后你就拥有了一套几乎Ke以Zuo到秒开 H5 的神器方案,而且还Neng随时插拔 功Neng,简直爽翻天!你懂的~ 😎
"命中公共基础库 → 返回 Assets 中的 SDK 命中离线包 → 校验哈希后返回本地文件 命中图片缓存 → 返回 Glide 磁盘缓存 需要代理 → 注入鉴权头/自定义DNS后代发 全部未命中 → 返回 null,WebView 正常走网络"© 2026 咱们技术团队 | 保留所有权利 | 如有侵权请联系删除 .
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback