UniApp 与原生 App 混合开发:WebView 通信与页面跳转

在移动应用开发中,UniApp 凭借其跨平台能力和 Vue 语法优势,成为许多团队的首选框架。然而,当项目需要集成复杂的原生功能(如高性能图形渲染、特定硬件调用或已有原生模块)时,纯 UniApp 方案往往力不从心。这时,混合开发模式——将 UniApp 页面嵌入原生 App 的 WebView 中,或反过来在 UniApp 中嵌入原生页面——便成为一种务实的选择。

本文将聚焦混合开发中最核心的两个环节:WebView 通信页面跳转,结合实战场景给出可落地的方案。

一、混合开发的两种典型架构

在讨论通信与跳转之前,先明确两种常见架构:

  1. 原生为主,UniApp 为辅:原生 App 作为宿主,通过 WebView 加载 UniApp 打包后的 H5 页面。适合已有成熟原生 App,需要快速迭代部分业务模块的场景。
  2. UniApp 为主,原生为插件:UniApp 作为主框架,通过 plus.webview 或原生插件嵌入原生页面。适合以 UniApp 为主但需要调用特定原生能力的场景。

本文主要针对第一种架构展开,因为它的通信与跳转问题最为典型。

二、WebView 通信:双向数据传递

WebView 通信的本质是原生与 JavaScript 之间的消息传递。在 UniApp 混合开发中,通常涉及三个角色:原生 App、WebView 容器、UniApp H5 页面。

2.1 原生向 UniApp 发送消息

原生端可以通过 evaluateJavascript(Android)或 stringByEvaluatingJavaScriptFromString(iOS)直接调用 WebView 中的全局函数。

Android 示例:

webView.evaluateJavascript("javascript:onNativeMessage('" + jsonData + "')", null);

iOS 示例:

webView.evaluateJavaScript("onNativeMessage('\(jsonData)')") { result, error in }

在 UniApp 页面中,需要提前挂载全局函数:

// 在 UniApp 的 H5 页面中
window.onNativeMessage = function(data) {
  const message = JSON.parse(data);
  // 处理原生传来的数据
  console.log('收到原生消息:', message);
};

2.2 UniApp 向原生发送消息

UniApp 向原生发送消息有三种主流方式:

方式一:URL Scheme 拦截(通用性最强)

UniApp 通过修改 window.location.href 触发原生端的 URL 拦截:

// UniApp 端发送
const message = { type: 'login', token: 'abc123' };
window.location.href = `myapp://native?data=${encodeURIComponent(JSON.stringify(message))}`;

原生端拦截该 URL 并解析:

// Android WebViewClient
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("myapp://")) {
        // 解析 url 中的参数,执行原生逻辑
        return true;
    }
    return false;
}

方式二:注入对象(Android addJavascriptInterface)

Android 端通过 addJavascriptInterface 注入原生对象:

webView.addJavascriptInterface(new NativeBridge(), "NativeBridge");

class NativeBridge {
    @JavascriptInterface
    public void postMessage(String data) {
        // 处理来自 UniApp 的消息
    }
}

UniApp 端直接调用:

window.NativeBridge.postMessage(JSON.stringify({ type: 'pay', orderId: '123' }));

方式三:WKWebView 的 messageHandler(iOS 推荐)

iOS 端配置:

let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "nativeBridge")

UniApp 端调用:

window.webkit.messageHandlers.nativeBridge.postMessage({ type: 'share', content: '...' });

2.3 通信封装建议

为了降低耦合,建议在 UniApp 侧封装统一的通信模块:

// bridge.js
class NativeBridge {
  static send(type, data = {}) {
    const message = { type, data, timestamp: Date.now() };
    // 根据运行环境选择发送方式
    if (window.NativeBridge) {
      window.NativeBridge.postMessage(JSON.stringify(message));
    } else if (window.webkit?.messageHandlers?.nativeBridge) {
      window.webkit.messageHandlers.nativeBridge.postMessage(message);
    } else {
      // 降级为 URL Scheme
      window.location.href = `myapp://bridge?data=${encodeURIComponent(JSON.stringify(message))}`;
    }
  }

  static receive(callback) {
    window.onNativeMessage = (data) => {
      try {
        callback(JSON.parse(data));
      } catch (e) {
        console.error('消息解析失败', e);
      }
    };
  }
}

三、页面跳转:原生与 UniApp 的导航协同

页面跳转比通信更复杂,因为它涉及两个独立的路由系统:原生的 Activity/ViewController 栈和 UniApp 的页面栈。核心原则是:谁主导跳转,谁负责维护栈的一致性

3.1 原生跳转到 UniApp 页面

原生端直接加载对应的 UniApp H5 页面 URL,并可通过参数传递初始数据:

// Android
String url = "https://your-h5-domain.com/#/pages/order/detail?id=123";
webView.loadUrl(url);

UniApp 页面在 onLoad 中接收参数:

onLoad(options) {
  const orderId = options.id;
  // 根据 orderId 加载数据
}

注意:如果 UniApp 使用 hash 路由,参数应放在 # 之后;如果使用 history 模式,则按普通 URL 参数处理。

3.2 UniApp 跳转到原生页面

UniApp 无法直接操作原生页面栈,必须通过通信机制请求原生执行跳转:

// UniApp 端
NativeBridge.send('navigate', {
  page: 'NativeOrderDetail',
  params: { orderId: '123' }
});

原生端收到消息后执行:

// Android
Intent intent = new Intent(context, OrderDetailActivity.class);
intent.putExtra("orderId", orderId);
startActivity(intent);

3.3 跳转中的栈管理策略

混合开发中最容易出问题的是返回逻辑。建议遵循以下策略:

  • 原生 → UniApp → 原生:UniApp 页面应作为原生栈中的一个“黑盒”,返回时由原生统一处理。
  • UniApp 内部跳转:使用 uni.navigateTo 等 API,不要触发原生跳转,除非确实需要离开 WebView。
  • 返回拦截:在 UniApp 中监听 onBackPress,必要时通过通信通知原生执行特定返回逻辑。
// UniApp 中拦截返回
onBackPress(options) {
  if (this.hasUnsavedChanges) {
    NativeBridge.send('confirmBack', { message: '有未保存的内容,确定返回?' });
    return true; // 阻止默认返回
  }
  return false;
}

四、实战注意事项

  1. URL Scheme 冲突:自定义 Scheme 要确保全局唯一,避免与其他 App 冲突。
  2. 消息序列化:复杂对象必须 JSON 序列化,注意循环引用和特殊字符转义。
  3. 线程安全:Android 的 addJavascriptInterface 回调运行在非 UI 线程,操作 UI 需切回主线程。
  4. iOS WKWebView 内存:大量消息传递可能导致内存上涨,及时移除不再使用的 messageHandler。
  5. 调试技巧:Android 用 chrome://inspect,iOS 用 Safari 开发者工具,可大幅提升排查效率。

五、总结

UniApp 与原生 App 的混合开发,通信是血脉,跳转是骨架。通信方案的选择取决于团队技术栈和兼容性要求:URL Scheme 最通用,注入对象最直接,messageHandler 最规范。页面跳转则需明确“谁主导、谁维护栈”的原则,避免出现返回逻辑混乱。

在实际项目中,建议将通信和跳转逻辑封装为独立的 Bridge 模块,统一管理消息格式和错误处理。这样既能降低维护成本,也能在后续扩展新功能时保持架构清晰。混合开发不是妥协,而是一种务实的工程选择——用对地方,它能发挥出 1+1>2 的效果。

未经允许不得转载:任鹏个人博客 » UniApp 与原生 App 混合开发:WebView 通信与页面跳转

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏