跳转到内容
aswind7
GitHub
Blog

hybrid开发

WebView

App中嵌入H5页面,进行展示,这个叫WebView

常见的WebView种类:

  • Android WebView: 使用安卓系统内置的WebKit引擎或Chromium引擎来渲染网页(PS 这也是为什么很多手机型号不一样 有一些兼容性问题,因为引擎有一些不一样)
  • iOS WebView: UIWebView(已弃用)和WKWebView
  • 桌面端应用Electron: 基于Chromium的WebView

ios WebView例子

比如: 在IOS应用中主要通过webview来运行我们的网页, 他的引擎(内核)是Webkit: 分为UI渲染引擎 Web Core和js执行引擎JSCore;

在JSCore中又主要分为四部分: JSVM,JSContext,JSValue,JSExport。我们主要使用到JSContext和JSValue, JSContext提供环境和调用的接口,JSValue做类型映射与转换。 JSCore相关源码

示例:使用KVC( Key-Value Coding )技术操作jsContext来执行js代码:

// 假设你有一个 WebView 对象
UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
    
// 使用 KVC 获取 WebView 的 JSContext
JSContext *jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

// 现在你可以使用 jsContext 来操作 WebView 的 JavaScript 上下文
[jsContext evaluateScript:@"console.log('Hello, JavaScript!');"];

原理

双向通信

image-20230921162150515

  • JS发送消息给Native,调用 分享、标题、原生登录、拷贝文字到剪切板、修改顶部背景色等功能,并暴露callback,Native执行成功后会调用此callback
  • Native主动发消息给JS, 如登录态失效、APP 进入后台、APP 进入前台等事件

JavaScript调用Native

方案1: 拦截 URL SCHEME

这个方案主要是:Web 端通过iframe.src发送 URL Scheme 请求类似这样myapp://xxx/a/b, 然后 Native 拦截到请求并解析参数,这个方案比较古老并且有缺陷

  • URL长度限制
  • 因为发请求,耗时更长

方案2: 注入API

IOS 原生的WKWebView注入对象

// app注入: 在初始化webview的config的usercontroller中添加一个messageHandler(对应某个name)。
JSContext *context = [uiWebView [control addScriptMessageHandler:weakSelf name:XXWKWebViewJSBridgeMessageHandlerName];

// 前端调用
window.webkit.messageHandlers.nativeBridge.postMessage(message);

安卓原生注入对象

// app 注入:
webView.addJavascriptInterface(new NativeObject4Web(this, webView), "nativeObject");

// 前端调用
window.nativeBridge.postMessage(message);

Native 调用 JavaScript

IOS调用 JavaScript

// webview调用evaluateJavaScript 
[wkWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler];

安卓调用JavaScript

webView.loadUrl("javascript:window.myBridge.Core.callbackDispatcher('" + callbackId + "','" + json + "')");

如何实现

核心代码

(function () {
    let uniqueId = 0;
          // 存放js调用native的回调方法
    const actionCallbacks = {};
           // 存放native调用js的回调方法
    const registeredFunctions = {};

    window.MyAppBridge = {
        invoke(methodName, callback, payload) {
            const requestId = uniqueId++;
            actionCallbacks[requestId] = callback;
            nativeBridge.postMessage({
                methodName,
                payload: payload || {},
                requestId,
            });
        },
        receiveMessage(message) {
            const { methodName, payload = {}, requestId, responseId } = message;
          // 这里触发「js调用native」上面的回调
            if (requestId) {
                const callback = actionCallbacks[requestId];
                if (callback) {
                    callback(payload);
                }
            } else if (methodName) {
              // js调用native, 此时触发register注册好的函数
                const callbacks = registeredFunctions[methodName];
                if (callbacks) {
                    const response = {};
                    let hasCallback = false;
                    callbacks.forEach((callback) => {
                        callback(payload, (result) => {
                            hasCallback = true;
                            Object.assign(response, result);
                        });
                    });
                    if (hasCallback) {
                        nativeBridge.postMessage({
                            responseId,
                            response,
                        });
                    }
                }
            }
        },
        register(methodName, callback) {
            if (!registeredFunctions[methodName]) {
                registeredFunctions[methodName] = [];
            }
            registeredFunctions[methodName].push(callback);
        },
    };
})();

业务层调用

// 在 JavaScript 中主动调用 Native 函数并处理回调:
MyAppBridge.invoke("getUserInfo", (userInfo) => {
    console.log("Received user info from Native:", userInfo);
});

//  在 JavaScript 监听app端的主动事件并触发回调
MyAppBridge.register("onUserInfoReceived", (userInfo, callback) => {
    console.log("Received user info in JavaScript:", userInfo);
    // 执行逻辑并通过回调函数将结果发送回 Native
    const result = { success: true };
    callback(result);
});

扩展阅读

https://tech.meituan.com/2018/08/23/deep-understanding-of-jscore.html

/blog/javascript-runtime

https://www.superwen.cn/archives/hybrid-and-jsbridge#jsbridge-%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0