大家好,又见面了,我是你们的朋友全栈君。
Web 页面中的 JS 与 iOS Native 如何交互?JS 和 iOS Native 就好比两块没有交集的大陆,如果想要使它们相互通信就必须要建立一座“桥梁”。
WebViewJavascriptBridge 是盛名已久的 JSBridge 库,它仅使用了少量代码就实现了对于 Mac OS X 的 WebView 以及 iOS 平台的 UIWebView 和 WKWebView 三种组件的完美支持。
WebViewJavascriptBridge 主要是作为 Mac OS X 和 iOS 端(Native 端)与 JS 端相互通信,互相调用的桥梁。对于 Mac OS X 和 iOS 两种平台包含的三种 WebView 功能组件而言,WebViewJavascriptBridge 做了隐性适配,即仅用一套代码即可绑定不同平台的 WebView 组件实现同样功能的 JS 通信功能。 WebViewJavascriptBridge 对于 JS 端和 Native 端设计了对等的接口,不论是 JS 端还是 Native 端,注册本端的响应处理都是用 registerHandler
接口,调用另一端(给另一端发消息)都是用 callHandler
接口。
- UIWebView 使用 javaScriptCore.
- WKWebView 使用 WKUserContentController.
UIWebView 原生的交互原理 通过一个 JSContext 获取 UIWebView 的 JS 执行上下文。 然后通过这个上下文,进行 OC & JS 的双端交互。
代码语言:javascript复制 _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
_jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"%@",@"获取 WebView JS 执行环境失败了!");
};
WKWebView 原生交互原理
通过 userContentController 把需要观察的 JS 执行函数注册起来。 然后通过一个协议方法,将所有注册过的 JS 函数执行的参数传递到此协议方法中。
注册 需要 观察的 JS 执行函数
代码语言:javascript复制 [webView.configuration.userContentController addScriptMessageHandler:self name:@"jsFunc"];
在 JS 中调用这个函数并传递参数数据
代码语言:javascript复制window.webkit.messageHandlers.jsFunc.postMessage({name : "李四",age : 22});
OC 中遵守 WKScriptMessageHandler
协议。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
此协议方法里的 WKScriptMessage 有 name & body 两个属性。 name 可以用来判断是哪个 JSFunc 调用了。body 则是 JSFunc 传递到 OC 的参数。
WebViewJavaScriptBridge
WebViewJavaScriptBridge 用于 WKWebView & UIWebView 中 OC 和 JS 交互。 它的基本原理是:
把 OC 的方法注册到桥梁中,让 JS 去调用。 把 JS 的方法注册在桥梁中,让 OC 去调用。
WebViewJavaScriptBridge 使用的基本步骤
- 首先在项目中导入
WebViewJavaScriptBridge 框架
。
pod ‘WebViewJavascriptBridge’
- 导入头文件
#import <WebViewJavascriptBridge.h>
。 - 建立 WebViewJavaScriptBridge 和 WebView 之间的关系。
_jsBridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
- 在HTML 文件中,复制粘贴这两段 JS 函数。
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(window.WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback] // 创建一个 WVJBCallbacks 全局属性数组,并将 callback 插入到数组中。
var WVJBIframe = document.createElement('iframe') // 创建一个 iframe 元素
WVJBIframe.style.display = 'none' // 不显示
WVJBIframe.src = 'https://__bridge_loaded__' // 设置 iframe 的 src 属性
document.documentElement.appendChild(WVJBIframe) // 把 iframe 添加到当前文导航上。
setTimeout(function() {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
// 这里主要是注册 OC 将要调用的 JS 方法。
setupWebViewJavascriptBridge(function(bridge){
});
到此为止,基本的准备工作就做完了。现在需要往桥梁中注入 OC 方法 和 JS 函数了。
往桥梁中注入 OC 方法 和 JS 函数
1、往桥梁中注入 OC 方法。
代码语言:javascript复制 [_jsBridge registerHandler:@"scanClick" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"dataFrom JS : %@",data[@"data"]);
responseCallback(@"扫描结果 : www.baidu.com");
}];
这段代码的意思:
- scanClick 是 OC block 的一个别名。
- block 本身,是 JS 通过某种方式调用到 scanClick 的时候,执行的代码块。
- data ,由于 OC 这端由 JS 调用,所以 data 是 JS 端传递过来的数据。
- responseCallback OC 端的 block 执行完毕之后,往 JS 端传递的数据。
2、往桥梁中注入 JS 函数. 在 JS 的方法如何注入到桥梁呢?需要在第二段 JS 代码中,注入 JS 的函数。
代码语言:javascript复制// 这里主要是注册 OC 将要调用的 JS 方法。
setupWebViewJavascriptBridge(function(bridge){
// 声明 OC 需要调用的 JS 方法。
bridge.registerHanlder('testJavaScriptFunction',function(data,responseCallback){
// data 是 OC 传递过来的数据.
// responseCallback 是 JS 调用完毕之后传递给 OC 的数据
alert("JS 被 OC 调用了.");
responseCallback({data: "js 的数据",from : "JS"});
})
});
这段代码的意思:
- testJavaScriptFunction 是注入到桥梁中 JS 函数的别名。以供 OC 端调用。
- 回调函数的 data。 既然 JS 函数由 OC 调用,所以 data 是 OC 端传递过来的数据。
- responseCallback 。 JS 调用在被 OC 调用完毕之后,向 OC 端传递的数据。
基本就是:
OC 端注册 OC 的方法,OC 端调用 JS 的函数。 JS 端注册 JS 的函数,JS 端调用 OC 的方法。
场景
JS -> OC 的交互
在 HTML 中,有个按钮,点击这个按钮,修改 NavigationBar 的颜色。
- 在 OC 端,往桥梁注入一个修改 NavigationBar 颜色的 block.
- 在 JS 端,调用这个 block,来间接的达到修改颜色的目的。
首先,在 OC 中,通过 WebViewJavascriptBridge 注册一个修改 navigationBar 颜色的 Block。
代码语言:javascript复制[_jsBridge registerHandler:@"colorClick" handler:^(id data, WVJBResponseCallback responseCallback) {
self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];
responseCallback(@"颜色修改完毕!");
}];
然后再 JS 中,通过某种方式去调用这个 OC 的 block。
代码语言:javascript复制WebViewJavascriptBridge.callHandler('colorClick',function(dataFromOC) {
alert("JS 调用了 OC 注册的 colorClick 方法");
document.getElementById("returnValue").value = dataFromOC;
})
这里通过某种方式就是使用 WebViewJavascriptBridge.callHandler(‘OC 中block 别名’,callback)的方式来调用。
OC -> JS 的交互
OC 上有一个UIButton,点击这儿按钮,把 HTML body 的颜色修改成橙色。
首先,往桥梁中,注入一个修改 HTML body 颜色的 JSFunction。
代码语言:javascript复制// 在这里声明 OC 需要主动调用 JS 的方法。
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('changeBGColor',function(data,responseCallback){
// alert('aaaaaa');
document.body.style.backgroundColor = "orange";
document.getElementById("returnValue").value = data;
});
});
然后在 OC 端通过桥梁调用这个 changeBGColor
。
[_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];
执行效果:
补充
OC 调用 JS 的三种情况。
代码语言:javascript复制 // 单纯的调用 JSFunction,不往 JS 传递参数,也不需要 JSFunction 的返回值。
[_jsBridge callHandler:@"changeBGColor"];
// 调用 JSFunction,并向 JS 传递参数,但不需要 JSFunciton 的返回值。
[_jsBridge callHandler:@"changeBGColor" data:@"把 HTML 的背景颜色改成橙色!!!!"];
// 调用 JSFunction ,并向 JS 传递参数,也需要 JSFunction 的返回值。
[_jsBridge callHandler:@"changeBGColor" data:@"传递给 JS 的参数" responseCallback:^(id responseData) {
NSLog(@"JS 的返回值: %@",responseData);
}];
JS 调用 OC 的三种情况。
代码语言:javascript复制// JS 单纯的调用 OC 的 block
WebViewJavascriptBridge.callHandler('scanClick');
// JS 调用 OC 的 block,并传递 JS 参数
WebViewJavascriptBridge.callHandler('scanClick',"JS 参数");
// JS 调用 OC 的 block,传递 JS 参数,并接受 OC 的返回值。
WebViewJavascriptBridge.callHandler('scanClick',{data : "这是 JS 传递到 OC 的扫描数据"},function(dataFromOC){
alert("JS 调用了 OC 的扫描方法!");
document.getElementById("returnValue").value = dataFromOC;
});
可以根据实际情况,选择合适的方法。
关于在 OC 中,往桥梁中注入 block 的注意点。
在当前控制器消失的时候,要记得把注入到桥梁中的 OC block,从桥梁中删除。 否则,可能会出现控制器无法释放的情况。
代码语言:javascript复制- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_jsBridge removeHandler:@"scanClick"];
[_jsBridge removeHandler:@"colorClick"];
[_jsBridge removeHandler:@"locationClick"];
[_jsBridge removeHandler:@"shareClick"];
[_jsBridge removeHandler:@"payClick"];
[_jsBridge removeHandler:@"goBackClick"];
}
Android
以上说的都是ios交互,安卓有一点点不同。
代码语言:javascript复制function connectWebViewJavascriptBridge (callback) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady'
, function() {
callback(WebViewJavascriptBridge)
},
false
);
}
}
//和ios一样
connectWebViewJavascriptBridge (function(bridge) {
//注册一个方法(方法名是“JS Echo”),客户端进行调用(方法名也是“JS Echo”),responseCallback是回调函数
bridge.registerHandler('JS Echo', function(data, responseCallback) {
console.log("JS Echo called with:", data)
responseCallback(data)
})
//客户端已经注册好一个名为“ObjC Echo”的方法,H5直接进行调用(方法名也为“ObjC Echo”)就行,调用的时候可以传客户端需要的参数
bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
console.log("JS received response:", responseData)
})
})
封装:
代码语言:javascript复制/* eslint-disable */
function setAndroid () {
var bridge = {
default: this,
callHandler: function(b, a, c) {
var e = ''
'function' == typeof a && ((c = a), (a = {}))
a = { data: void 0 === a ? null : a }
if ('function' == typeof c) {
var g = 'dscb' window.dscb
window[g] = c
a._dscbstub = g
}
a = JSON.stringify(a)
if (window._dsbridge) e = _dsbridge.callHandler(b, a)
else if (
window._dswk ||
-1 != navigator.userAgent.indexOf('_dsbridge')
)
e = prompt('_dsbridge=' b, a)
return JSON.parse(e || '{}').data
},
register: function(b, a, c) {
c = c ? window._dsaf : window._dsf
window._dsInit ||
((window._dsInit = !0),
setTimeout(function() {
bridge.callHandler('_dsb.dsinit')
}, 0))
'object' == typeof a ? (c._obs[b] = a) : (c[b] = a)
},
registerAsyn: function(b, a) {
this.register(b, a, !0)
},
hasNativeMethod: function(b, a) {
return this.callHandler('_dsb.hasNativeMethod', {
name: b,
type: a || 'all'
})
},
disableJavascriptDialogBlock: function(b) {
this.call('_dsb.disableJavascriptDialogBlock', {
disable: !1 !== b
})
}
}
!(function() {
if (!window._dsf) {
var b = {
_dsf: { _obs: {} },
_dsaf: { _obs: {} },
dscb: 0,
WebViewJavascriptBridge: bridge,
close: function() {
bridge.callHandler('_dsb.closePage')
},
_handleMessageFromNative: function(a) {
var e = JSON.parse(a.data),
b = { id: a.callbackId, complete: !0 },
c = this._dsf[a.method],
d = this._dsaf[a.method],
h = function(a, c) {
b.data = a.apply(c, e)
bridge.call('_dsb.returnValue', b)
},
k = function(a, c) {
e.push(function(a, c) {
b.data = a
b.complete = !1 !== c
bridge.callHandler('_dsb.returnValue', b)
})
a.apply(c, e)
}
if (c) h(c, this._dsf)
else if (d) k(d, this._dsaf)
else if (((c = a.method.split('.')), !(2 > c.length))) {
a = c.pop()
var c = c.join('.'),
d = this._dsf._obs,
d = d[c] || {},
f = d[a]
f && 'function' == typeof f
? h(f, d)
: ((d = this._dsaf._obs),
(d = d[c] || {}),
(f = d[a]) &&
'function' == typeof f &&
k(f, d))
}
}
},
a
for (a in b) window[a] = b[a]
bridge.register('_hasJavascriptMethod', function(a, b) {
b = a.split('.')
if (2 > b.length) return !(!_dsf[b] && !_dsaf[b])
a = b.pop()
b = b.join('.')
return (b = _dsf._obs[b] || _dsaf._obs[b]) && !!b[a]
})
}
})();
return bridge
}
const jsBridge = {
init: function() {
if (/(Android)/i.test(navigator.userAgent.toLowerCase())) {
// setAndroid()
this.register = setAndroid().register
}
},
connectJsBridge: function(callback) {
if (window.WebViewJavascriptBridge) {
return callback(window.WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
var WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'https://__bridge_loaded__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function() {
document.documentElement.removeChild(WVJBIframe)
}, 0)
},
addBridgeProperty: function(bridge) {
const dependencies = ['getUserInfo', 'login', 'getLocation', 'share',..... ];
try {
dependencies.forEach(dependency => {
// if (!this[dependency]) {
Object.defineProperty(this, dependency, {
configurable: true, // 注:允许重复定义属性(移除会造成重新定义报错)
get: () => {
return bridge.callHandler.bind(bridge, dependency)
}
})
// }
})
} catch (error) {
console.error(error)
}
// if (this.openWebPage) {
this.openWebPage = url => {
window.location.href = `app内嵌的域名?url=${encodeURIComponent(
url
)}`
}
// }
return this
},
ready: function(callback) {
this.connectJsBridge(bridge => {
callback(this.addBridgeProperty(bridge))
})
},
register: function(...args) {
window.WebViewJavascriptBridge.registerHandler(...args)
},
}
jsBridge.init()
export default jsBridge
//使用:
import JSBridge from './base/jsBridge'
JSBridge.ready((bridge: any) => {
bridge.getUserInfo((json: string | object) => {
console.log(json)
})
})
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/198352.html原文链接:https://javaforall.cn