前言

阿里内部使用的 windvane 容器原理剖析

抛出问题

  1. 端上设计缺陷问题处理(iOS首页白屏,Bridge 调用卡顿)
  2. 统一两端方案
  3. 安全加固
  4. 性能优化(加载速度过慢)
  5. 双端感知(H5 内部跳转(非原生跳转) 情况下 端上如何感知)
  6. H5 沉浸式页面(如全屏视频、游戏、直播)

解决!

时间线

---------------------------------------------------------
|                         WindVane架构演进               |
---------------------------------------------------------
|        年份/阶段         | 2013-2014 |    2015   |   now   |
---------------------------------------------------------
|         交互时           | Hybrid API|           |         |
|                          | 平台化    |           |         |
---------------------------------------------------------
|         下载时           |           | 预加载    |         |
|                          |           | HTTP-DNS  |         |
|                          |           | SPDY      |         |
---------------------------------------------------------
|         渲染时           |           |           | 渲染    |
---------------------------------------------------------

前期主要处理交互问题,后期开始处理下载问题以及渲染问题。

首页白屏问题

分两步

  1. app 第一次进入 wk 会白屏,解决方案:适当时机(比如进入骑手个人中心页面后)就启动一个空白 wkwebview。

统一两端方案

这个不用说,web 负责渲染 UI,逻辑部分通过 Javascript 各自端上实现。我们分开来看

  • web 页面渲染只需要 Html + CSS(当然,部分 UI 展示需要借助一定的 JS 逻辑)
  • Javascript 通过 Web 容器提供的通道来实现双向通信

安全加固

三个方案:

  1. 参数校验(Schema Validation)
    参数不对则拒绝
  2. 来源校验(Origin Check)
    不是 饿了么 host, 则拒绝
  3. 防重放攻击
    每次 invoke 带唯一 nonce + 时间戳;服务端校验防止重复提交。

性能优化(加载速度过慢)

离线包。静态资源(js、html、css)本地加载即可。

亮点

WindVane 采用 “预注入 + 动态增强” 双阶段注入策略,在页面加载前完成基础能力注入,运行时按需注入业务 SDK。

流程图:
windvane注入

阶段一:预注入 核心 js

时机:WKWebView 初始化后、loadRequest 之前
注入的内容:

  • window.WindVane
  • Promise 风格的 invoke(method, params) 方法
  • __callbacks 映射表
  • 基础事件监听(如 onPageReady)

注意

为什么必须在 loadRequest 前注入?
确保 H5 页面第一行 JS 就能调用 Bridge,避免“Bridge not ready”错误。

阶段 2:动态注入业务 SDK(按需)

  • 时机:
    收到 H5 的特定信号(如 <meta name="windvane-modules" content="login,tracker">)
  • 方式:

从本地缓存或 CDN 下载 login_sdk.js
通过 evaluateJavaScript 注入

  • 优势:
    减少首屏 JS 体积

按业务域隔离能力(防滥用)

数据:淘宝早期因 Bridge 未就绪导致的 JS 错误率 >15%,注入前置后降至 <0.1%。

保证跨平台一致性:前端无需写 if (isIOS) ... else if (isAndroid) ...。

双端感知

监听 History API:

WindVane 会注入 JS 代码,劫持 pushState / replaceState:

const originalPush = history.pushState;
history.pushState = function() {
  originalPush.apply(history, arguments);
  // 通知原生:页面已跳转
  windvane.notifyPageChange(location.href);
};

H5 沉浸式页面

什么是“沉浸式”?

  • 页面需要 隐藏状态栏、导航栏
  • 可能要求 横屏锁定
  • 用户手势(如下滑)可能触发 退出全屏 而非返回上一页
  • 原生 UI(如 ActionBar)需动态隐藏/显示

WindVane 如何处理?

扩展 JSBridge API

提供类似:

// H5 主动请求沉浸式
windvane.setImmersiveMode({
  immersive: true,
  orientation: 'landscape',
  hideNavBar: true
});

原生容器收到后,动态调整 Activity 的 UI 和屏幕方向。

手势代理机制

WindVane 监听 WebView 的 touch 事件
若 H5 声明“当前处于沉浸式”,则拦截系统返回手势,交由 H5 处理(如退出全屏)
通过 shouldOverrideKeyEvent 或自定义 GestureDetector 实现

生命周期透传

当 App 进入后台(onPause),WindVane 主动调用 H5 注册的 onAppBackground() 回调
H5 可在此暂停视频、释放资源

权限预声明

在离线包配置中声明“该页面需要沉浸式权限”
WindVane 加载前检查,避免运行时失败

虚拟页面栈管理

WindVane 维护一个 H5 内部页面栈(独立于原生 Activity 栈)
每次 notifyPageChange 就 push 一个新“虚拟页面”
按返回键时,先 pop 虚拟栈;栈空才关闭 WebView

动态埋点上报

收到 notifyPageChange 后,自动触发新页面的 PV 上报
性能指标(FCP、LCP)按虚拟页面重置

总结

+---------------------+--------------------------------------+
| 问题                | WindVane 方案                        |
+---------------------+--------------------------------------+
| 首屏白屏            | 预注入 + 离线包 → H5秒开             |
| Bridge 调用卡顿     | 异步队列 + 批量合并消息              |
| 内存泄漏            | 自动清理 callback + WebView 复用池   |
| JS 执行阻塞 UI      | 所有 evaluateJavaScript 放子线程调度 |
+---------------------+--------------------------------------+

具体的阿里内部有在大会分享过:活动家(https://www.huodongjia.com/)