什么是H5混合开发? 新手指南

142 阅读7分钟

H5混合开发出现的背景

在移动端早期(2010年前后),App 市场被 iOS(苹果)和 Android(安卓)两分天下。

  • 原生开发的困境: 如果一家公司想做一个 App,必须雇两拨人。一波人用 Swift/OC 写 iOS 版,另一波人用 Java 写 Android 版。

  • 成本翻倍: 逻辑一模一样,但代码完全不通,开发成本、测试成本、维护成本全部翻倍。

  • 时间成本高: 原生 App 更新一次,需要重新打包、上传应用商店、经历数天的审核(尤其是 App Store)。

  • 无法即时触达: 如果 App 发现一个紧急 Bug,原生开发无法像网页一样“刷新即修复”,用户必须手动下载更新包

  • 痛点: 开发者在想,能不能写一套代码,在两个系统都能跑?

在这个背景之下,H5混合开发出现了,H5 混合开发(Hybrid App Development) ,本质上是:

  • 用 Web 技术(HTML / CSS / JS)开发页面,在“原生 App 壳”下运行,并且可以调用原生能力的一种开发模式。

这样一套逻辑,多端使用,提高了开发效率,减少了开发/测试/运维的成本。

H5混合的核心是WebView

什么是WebView

WebView 本质上是:

  • App 里内置的一个“浏览器内核组件”,用来加载和渲染网页(HTML / CSS / JS)

一句话理解: WebView = 没有地址栏的浏览器

这样使得前端开发的页面/逻辑,能在手机app页面中运行起来

在开发混合应用时,我们不一定要像浏览器那样去输入网址访问。我们可以直接把写好的 HTML、CSS、JS 文件打包进 App 的安装包里。当用户打开 App 时,WebView 会直接加载这些本地文件。

  • 感觉上: 它就是一个本地页面,加载速度极快,甚至没网也能打开。
  • 效果上: 用户根本察觉不到自己是在看网页

混合开发的底层逻辑

  • H5 (内容) :负责页面的样子和逻辑(像装修)。

  • WebView (容器) :负责把 H5 渲染出来(像房间)。

  • 原生外壳 (载体) :负责提供 WebView 运行的环境(像地基)。

H5 混合开发如何解决原生痛点?

通过 WebView 这个桥梁,混合开发模式精准地击碎了原生开发的枷锁:

1. 一套代码,到处运行

  • 原生做法: 雇两拨人,写两套代码(iOS + Android)。
  • 混合做法: 核心业务逻辑都用 H5 编写。由于 WebView 在不同系统上的渲染效果基本一致,开发者只需写一套 H5 代码,就可以同时跑在苹果和安卓手机上。
  • 结果: 研发人力成本直接降低了近 50%

2. 解决“审核慢”:跳过商店,实时生效(热更新)

  • 原生做法: 改个错别字都要重新打包、提交审核、等待数天。
  • 混合做法: H5 页面可以放在服务器上。当需要修改内容时,开发者只需在服务器更新文件,用户重新打开 App 时,WebView 就会加载最新的页面。
  • 结果: 实现了**“Bug 秒修复”“活动秒上线”**,完全不受应用商店审核周期的限制。

H5 和原生是怎么通信的?

H5 只是“住”在 WebView 里,它依然只是个普通的网页。要让它变成真正的 App,必须让它能和手机硬件(如相机、蓝牙)交流。

既然 H5 被包裹在原生系统的“外壳”里,它们之间就必须有一条沟通的管道。这个通信机制有一个专业术语叫:JSBridge(JS 桥)。

你可以把它想象成“房间里的对讲机”,让住在 WebView 里的 H5 能和外面的原生系统对话。

JSBridge 是一种 JS 实现的 Bridge,连接着桥两端的 Native 和 WebView。

协作链条

JS (发出指令)Bridge (格式转换)Native (执行动作)OS (调用硬件)

  • 这种结构的本质是:利用 Native 的权限,扩展 Web 的能力。

它在 APP 内方便地让 Native 调用 JSJS 调用 Native ,是双向通信的通道。

JSBridge-JS调用原生能力

1.拦截URL scheme

原理一句话

JS 构造一个特殊协议的 URLWebView 在加载 URL 时被原生拦截, 原生解析 URL → 执行对应 Native 方法。

它是如何工作的?(以调用相机为例)

  1. 定暗号: 原生开发和前端开发约定好一个特殊的“暗号”(协议),比如:myapp://
  2. 发指令: 网页(H5)里不需要真的跳转页面,它只需要执行一行代码: window.location.href = "myapp://utils/openCamera?id=123"; 这看起来像是在跳网址,但其实是发出了一个“调用相机”的指令。
  3. 拦截并执行: WebView 像一个过滤器,它发现这个请求不是 httphttps 开头,而是约定的暗号 myapp,于是立刻拦下这个请求,不让它真的跳转,而是解析出后面的指令(openCamera),然后直接在后台打开手机摄像头。

JS侧代码示例

/**
 * 1. 定义发送指令的函数
 * @param {String} action  要执行的动作,如 'openCamera'
 * @param {Object} params  传递给原生的参数
 */
function callNative(action, params) {
    // 构造约定的特殊协议头(暗号)
    const scheme = "myapp://utils/";
    
    // 将参数转为字符串(如:id=123)
    const query = new URLSearchParams(params).toString();
    
    // 拼接成完整的伪 URL:myapp://utils/openCamera?id=123
    const url = `${scheme}${action}?${query}`;

    // 发送暗号:通过创建一个不可见的 iframe 来触发请求(比直接改 location 更安全,不会导致页面刷新)
    const iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = url;
    document.body.appendChild(iframe);
    
    // 记得移除,保持页面整洁
    setTimeout(() => {
        document.body.removeChild(iframe);
    }, 200);
}

// 2. 实际调用:点击按钮打开相机
document.getElementById('cameraBtn').onclick = function() {
    console.log("准备调用原生相机...");
    callNative('openCamera', { id: 123, quality: 80 });
};

/**
 * 3. 结果回调:原生处理完后(拍完照),会主动调用这个 JS 函数把图片传回来
 */
window.onCameraSuccess = function(photoData) {
    const img = document.getElementById('preview');
    img.src = "data:image/jpeg;base64," + photoData;
    alert("原生相机调用成功!照片已显示。");
};
  • 构造 URL: 我们把想做的事情(openCamera)和参数(id=123)像写网址一样拼起来。

  • 触发拦截: 当这个 iframe 尝试加载这个 myapp:// 开头的地址时,WebView 内部的拦截器会瞬间抓取到这个请求。

  • 识别指令: 原生代码会看到这个地址,它会想:“这不是普通的网页,这是我们约定的暗号!”于是它解析出 openCamera,立刻调起系统相机。

  • 异步回调: 因为拍照需要时间,所以原生处理完后,会反向执行一段 JS 代码(比如 window.onCameraSuccess(...)),把结果“喂”回给网页。

2. 注入 API(Context Injection)

原理一句话

原生系统直接在 WebView 的 JavaScript 环境中注入一个全局对象,JS 像调用普通本地函数一样调用原生功能。

比喻理解

  • URL Scheme(拦截): 网页在屋里大喊一声暗号,门外的原生系统听到了去办事(像对讲机)。
  • API 注入: 原生系统直接送给网页一个“万能遥控器”(全局对象),网页想干嘛,直接按遥控器上的按钮就行了。

JS侧代码示例

这种方式下,JS 不需要构造复杂的 URL,代码非常自然

/**
 * 现代主流方式:直接通过原生注入的对象调用
 */
document.getElementById('cameraBtn').onclick = function() {
    // 这里的 'NativeBridge' 是原生端提前注入到 window 对象上的名
    // 就像 window.alert 一样,不需要定义,直接使用
    if (window.NativeBridge && window.NativeBridge.openCamera) {
        
        // 直接调用,像写普通的 JS 函数一样
        // 甚至可以像原生一样直接传复杂的 JSON 对象
        window.NativeBridge.openCamera({
            id: 123,
            quality: 80,
            callback: "onCameraSuccess" // 告诉原生:处理完后调用我的哪个函数
        });
        
    } else {
        console.error("当前环境不支持原生调用");
    }
};

/**
 * 回调函数(保持不变)
 */
window.onCameraSuccess = function(photoData) {
    document.getElementById('preview').src = "data:image/jpeg;base64," + photoData;
};

API 注入的优势

  • 性能更高: 不需要创建 iframe 或修改 location,没有解析 URL 字符串的开销,通信几乎是瞬时的。

  • 数据传输量大: URL Scheme 受限于 URL 的长度限制(通常 2KB 左右),而 API 注入可以传递非常大的 JSON 数据。

  • 代码更优雅: 前端开发者不需要关心“暗号”怎么拼,只需要像调用普通的 Library 一样开发即可。

API注入是现在目前主流的JSbridge通信方式