如何通过预加载 WebView 实例提升页面打开速度?

解读

国内 App 普遍把 WebView 当成“半个原生页面”在用,首页弹窗、运营活动、广告落地页、小游戏、直播带货 H5 全靠它。WebView 第一次冷启动要:

  1. 加载 libwebviewchromium.so(32/64 位双 so 机型耗时 200-400 ms)
  2. 创建 AwBrowserProcess 主进程并初始化 GPU 线程、网络线程、渲染线程
  3. 下载或校验本地组件(国内无 GMS,系统 WebView 由厂商或微信内核提供,版本碎片化严重)

这三步在低端机上可达 600-800 ms,用户点击 H5 链接时如果“现场 new WebView()”,白屏时间直接爆炸。预加载的核心思路是:把 1、2 步提前到 App 启动或首页 idle 时段完成,并把成品 WebView 缓存到内存池,真正跳页时只做 3 步之后的“loadUrl”,把耗时从“串行”变成“并行+复用”。

知识点

  1. WebView 多进程模型:国内 ROM 把 WebView 放在 sWebView:xxx 独立进程,第一次创建会 fork 新进程,AMS 校验 PMS 耗时明显。
  2. 国内 ROM 差异:华为、小米、OPPO 部分机型对后台进程回收激进,预加载进程可能被杀;需配前台 Service 或 JobScheduler 保活。
  3. 内存泄漏根因:WebView 持有 Activity 引用,预加载时必须使用 ApplicationContext,并在独立进程或子线程中创建。
  4. 数据目录隔离:WebView 默认 /data/data/pkg/app_webview,多账号或多进程场景需 WebView.setDataDirectorySuffix 防止“多进程共用目录”导致 crash(Android P 强制要求)。
  5. 资源竞争:预加载池大小建议 1-2 个,过多会触发低内存 killer;需配合 onTrimMemory 及时释放。
  6. 国内合规:预加载不能在用户同意隐私政策前采集设备信息,否则应用市场审核会被判“违规收集”。
  7. 灰度指标:线下用“Systrace + WebView 标签”看 “Chromium.Main” 线程耗时;线上用埋点 “WebView.fullyDrawn” 减去 “Activity.onCreate” 作为真实白屏时间。

答案

  1. 时机选择

    • 在 Application.onCreate 中判断当前进程是主进程且非后台拉起,启动 IdleHandler 或 Looper.myQueue().addIdleHandler,在 CPU 空闲时预加载。
    • 首页首页渲染完成(onWindowFocusChanged=true)后,延迟 300 ms 再执行,避免与主线程抢 CPU。
  2. 独立进程预加载(推荐)

    • 在 manifest 声明 :webview 进程,android:process=":webview"。
    • 在 :webview 进程内新建 WebView(applicationContext),调用 loadUrl("about:blank") 完成 GPU 线程初始化,随后把 WebView 对象通过 AIDL 返回主进程(WebView 无法跨进程传递,只能返回“已预热”信号)。
    • 主进程收到信号后,把“空 WebView”缓存到对象池 LinkedBlockingQueue(1) 中,真正跳页时取出,setWebViewClient 并 loadUrl 业务地址。
  3. 主进程预加载(轻量方案)

    • 直接 new WebView(new MutableContextWrapper(app)),MutableContextWrapper 可动态替换 baseContext,解决“预加载阶段无 Activity”问题。
    • 预加载完放入弱引用池,跳页时把 baseContext 换成目标 Activity,避免内存泄漏。
  4. 生命周期管理

    • 在 Activity.onDestroy 把 WebView 从父容器移除并 stopLoading,随后放回对象池;若池已满则 destroy。
    • 监听 onTrimMemory(TRIM_MEMORY_RUNNING_LOW) 主动销毁池,防止后台被杀。
  5. 数据与缓存优化

    • 预加载同时预请求常用静态资源(字体、JS 框架)并放入 WebView 默认缓存目录,减少二次下载。
    • 对国内 CDN 域名做 HttpDns 预解析,把结果注入到 WebView 的 DnsPrefetch 白名单,降低 DNS 耗时 50-100 ms。
  6. 合规与灰度

    • 预加载前检查隐私弹窗是否已同意,未同意则延迟到同意后。
    • 线上灰度 10% 用户,对比“预加载组”与“对照组”的 H5 首屏 <1 s 率,提升 ≥8% 再全量。

通过以上步骤,可把 WebView 首次创建耗时从 600 ms 降到 100 ms 以内,页面首屏时间平均减少 25-35%,在 OPPO A5、红米 9A 等低端机效果最明显。

拓展思考

  1. 预加载与 TBS(腾讯 X5 内核)如何共存?
    TBS 自身已带“热启动”机制,首次创建 X5WebView 会检查本地内核版本并动态下发,耗时与系统 WebView 相当。若 App 同时集成系统 WebView 和 TBS,可在 Application 阶段根据 ROM 白名单动态选择预热对象,避免双份内存。

  2. 折叠屏/多窗口场景下预加载池是否失效?
    折叠屏展开后 Activity 会重建,但预加载 WebView 放在 Application 生命周期不受配置变更影响;需监听 onConfigurationChanged 重新计算 WebView 的 layoutParams,防止复用时宽高异常。

  3. 隐私沙盒(Android 13 限制 WebView 获取设备标识)对预加载的影响?
    预加载阶段禁止调用 WebSettings.setUserAgentString 拼接设备序列号,否则 targetSdk>=33 时在 Google Play 会被拒。可把 UA 拼接延后到真正 loadUrl 前,仅预热内核进程。

  4. 未来可替代方案

    • Chrome Custom Tabs:把 H5 托管到系统浏览器进程,Google Play 渠道可省预热,但国内无 GMS 基本不可行。
    • Jetpack Compose + WebView 包装器:Google Accompanist 已提供,可结合 rememberSaveable 把预热的 WebView 实例存到 ViewModelStore,生命周期与进程同生共死,适合单 Activity 架构。