根据网上的文章和最近使用的经历,做了下面一篇关于iOS与HTML5交互方法的分析,不过多描述具体代码。
大概有以下几种方式:
1. 利用WKWebView进行交互(系统API) -ios 8以后
2. 利用UIWebView进行交互(系统API) -ios 7前常用
3. 苹果的javascriptcore.framework框架; -ios 7以后
4. 跨平台cordova框架;
5. 第三方WebViewJavascriptBridge(作者N久没有更新,有提交issuse未解决,用的人最多)
6. 第三方DSBridge等
先说下最后的实现方案是:WKWebView+JSBridge
历史
iOS7之前兼容 -URL拦截
在iOS7之前,实现js与oc的交互,方式为向UIWebView发送stringByEvaluatingJavaScriptFromString:消息来执行一段JavaScript的脚本。
而且如果想用JavaScript来调用Objective-C的方法,必须打开一个自定义的URL(例如:Nanshanyi://),然后在UIWebView的delegate方法webView:shouldStartLoadWithRequest:navigationType中进行处理 实质就是上述的利用UIWebView进行交互(系统API)
这种JS调用OC的方法的缺点十分明显,需要繁琐地解释字符串得到相应的方法名和传值,且调用的方法也不能传递返回值;
优点是:不需要等待页面加载完才触发,当相应的代码被运行就能调用OC的方法(相比 JavaScriptCore而言)
发展
ios7以后-JavaScriptCore
JavaScriptCore这个框架是iOS7之后苹果推出的,方便了开发者的使用,让web页面和iOS本地原生应用交互起来更加简单。
JavaScriptCore是webkit的一个重要组成部分,主要是对JS进行解析和提供执行环境。
代码是开源的:https://github.com/phoboslab/JavaScriptCore-iOS
JavaScriptCore这里有个坑,就是oc调用js,必须是html加载完成之后才可以。
UIWebVIew支持,WKWebView不能够利用javaScriptCore交互。
使用方法百度很多,不具体描述。
代码示例:
Objective-C 调用 JavaScript
self.context = [[JSContext alloc] init];
NSString *js = @"function add(a,b) {return a+b}";
[self.context evaluateScript:js];
JSValue *n = [self.context[@"add"] callWithArguments:@[@2, @3]];
步骤很简单,创建一个JSContext对象,然后将JS代码加载到context里面,最后取到这个函数对象,调用callWithArguments这个方法进行参数传值。(JS里面函数也是对象)
JavaScript 调用 Objective-C
self.context = [[JSContext alloc] init];
self.context[@"add"] = ^(NSInteger a, NSInteger b) {
NSLog(@"---%@", @(a + b));
};
[self.context evaluateScript:@"add(2,3)"];
1.WKWebView(系统API)
题外话:WKWebView优点:
- 更多的支持HTML5的特性
- 官方宣称的高达60fps的滚动刷新率以及内置手势
- Safari相同的JavaScript引擎
- 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议(官方文档说明)
- 另外用的比较多的,增加加载进度属性:estimatedProgress
缺点:
WKWebView 不支持二级页面缓存 和 NSURLProtocol 拦截了
因为在js发送消息给native的时候,有时候需要通过回调来获取相应的信息,仅仅靠上面两个方法是没有办法满足的,也可能会有小伙伴说,先通过上面方法1发送消息个native然后,再使用方法2发送消息给js不就好了么,不行的,这样的话,js调用native方法时,和native发送消息时候并没有时间先后的约定,不能保证,js获取相关返回值的时候,一定能拿到值。
-iOS端添加
[_webView configuration].userContentController = [[WKUserContentController alloc]init];
//添加监听
[[_webView configuration].userContentController addScriptMessageHandler:self name:@"sendNativeAction"];
//WKScriptMessageHandler协议方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"sendNativeAction"]){
[self anlyicesResp:message.body];
}
}
JS端添加
1.代码需要判断安卓还是iOS移动端
1. $(function(){
2. var u = navigator.userAgent, app = navigator.appVersion;
3. var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android终端或者uc浏览器
4. var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
5. alert('是否是Android:'+isAndroid);
6. alert('是否是iOS:'+isiOS);
7. if(isAndroid){
8. }
9. });
10. window.webkit.messageHandlers.xxx.postMessage(JSON.stringify(json))
WebViewJavascriptBridge与JS交互
WebViewJavascriptBridge同时支持UIWeView和WKWebView,无论是JS调用OC还是OC调用JS,都可以正常传值和返回值;而且在页面加载时只要JS代码被运行就可以进行交互,上面遇到的缺点和坑在这里都被掩埋的,所以是现在处理交互的主流做法。
就是在OC环境和Javascript环境各自保存一个相互调用的bridge对象,每一个调用之间都有id和callbackid来找到两个环境对应的处理
WebViewJavascriptBridge是网上很火的一个交互库,使用的人较多,但是对于js基础较弱的小伙伴来说,底层不是太好理解。底层和easy-js一样都是通过创新一个隐藏的iframe通过截取url来进行交互。WebViewJavascriptBridge本身是有bug的,作者本身也停止了更新,网上有许多框架基于它的基础上做了优化。
//设置能够进行桥接
[WebViewJavascriptBridge enableLogging];
//注册handler在Object-C,如果有self.webView.delegate = self。应注释掉,否则注册方法不执行
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];
//如果想执行UIWebView的代理方法,需设置
[self.bridge setWebViewDelegate:self];
JS调用OC
JS调用原生方法getUsername,可以使用如下方式注册。JS需要返回值的可以用responseCallback将返回值传过去。
[self.bridge registerHandler:@"getUsername" handler:^(id data,WVJBResponseCallback responseCallback) {
NSLog(@"%@",data);
responseCallback([self getUsername]);
}];
OC调用JS
OC调用JS可以使用如下方法实现,如果需要传参,可以写到参数data:里,如果没有参数就传nil
//OC调JS的方法
[self.bridge callHandler:@"testJavascriptHandler" data:nil responseCallback:^(id responseData) {
NSLog(@"ObjC received response: %@", responseData);
}];
JSBridge
JSBridge是偶然看到的一个Native和JS的通信框架,因为已经用了WebViewJavascriptBridge,这个框架作为了解,Native只通过一个固定的桥对象调用JS,JS也只通过固定的桥对象调用Native,基本原理是:
H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调。
github地址:https://github.com/lzyzsd/JsBridge
- android4.2以下,addJavascriptInterface方式有安全漏掉
- iOS7以下,JS无法调用Native
JsBridge的核心
JsBridge之所以能实现Native与Js相互调用的功能,其核心实现其实就是:
拦截Url
load url("javascript:js_method()");
先说第二点,Native调用Js,通过加载以javascript:开头的url即可实现调用Js的方法。这个很好理解,在web中也是通过这种方式调用Js的方法的。
然后细说下第一点的实现:
向body中添加一个不可见的iframe元素。通过拦截url的方法来执行相应的操作,但是页面本身不能跳转,所以改变一个不可见的iframe的src就可以让webview拦截到url,而用户是无感知的。
拦截url。通过shouldOverrideUrlLoading来拦截约定规则的Url,再做具体操作。
Ps: 添加iframe是H5自身可实现的,但是如果H5来实现的话,需要每个页面实现,且耦合较高;因此放在库里,通过加载完成注入的方式,则会降低耦合