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!');"];
原理
双向通信
- 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
https://www.superwen.cn/archives/hybrid-and-jsbridge#jsbridge-%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0
Tags: