Native和JS交互方案选择

(一)通过URL传递函数名称和参数

这个方案是历史最悠久,至少目前也是使用最普遍的方案。这个方案的优点是技术最简单,通过scheme://host?query的形式由url来传递,iOS和Android都可以用最基础的方式来实现。
当然这里还是有一个比较有名的第三方库WebViewJavascriptBridge不过用这个库,需要JS、iOS、Android三方要协调好,最好都要用它。最后的使用接口还是简单清晰的。同时支持UIWebView和WKWebView,这种与时俱进的做法还是值得提倡。

Native调JS

  • UIWebView - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

  • WKWebView - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

  • WKWebView需要添加WebKit.framework,最低支持版本iOS8,Xib不能用,只能代码写界面。

  • 条件允许的情况下,建议用WKWebView,系统性能的提升是硬道理。

JS调Native

  • UIWebView - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

  • WKWebView - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

  • NSURLRequest中有NSURL参数,以格式scheme://host?query约定好,JS告诉Native想要达到的目的,Native根据参数完成相应的功能。

  • JS和iOS、Android的写法需要相互配合,才能完成相应的通讯过程。

JS调用Native大致步骤

  • Step1:Native通过截取的url,将scheme://host?key1=value1&key2=value2格式解析出来。一般根据scheme判断是否目标JS,决定是否处理;host部分可以约定为模块名或者类名或者函数名等;问号后面的key1=value1&key2=value2就是url的query部分,可以放参数名;当然也可以host部分不用,将所有信息都放入后面的参数中。

  • Step2: Native执行一段事先定义的固定的JS代码,告诉JS已经正确收到命令。比如:

[self.webView evaluateJavaScript:@"bridge.nativeCallComplete()" completionHandler:nil];
  • Step3:JS端收到命令响应之后等待或者做其他事情,Native端根据解析出来的参数调用本地模块完成响应的功能

  • Step4:Native执行完毕后,执行一段事先定义的固定的JS代码,告诉JS执行结果。
    比如:

[self.webView evaluateJavaScript:@"[self.webView evaluateJavaScript:@"javascript:bridge.invokeJs('result=3')" completionHandler:nil];" completionHandler:nil];
  • Step5:JS端根据收到的结果,决定下一步的响应。

参考文章

UIWebView与JavaScript(JS) 回调交互

WebViewJavascriptBridge简介

通过前面的介绍可以知道,Native调用JS和JS调用Native是很不一样的。并且在JS调用Native的过程中,有好几次用到Native调用JS的过程(告诉JS命令接收,告诉JS处理结果)。估计这也是让人感觉复杂的地方。
WebViewJavascriptBridge的设计比较巧妙,经过包装,JS和Native互相调用的写法完全一致,实现了“全双工通讯”。
marcuswestin/WebViewJavascriptBridge

通讯函数

typedef void (^WVJBResponseCallback)(id responseData);
typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

在一端用registerHandler进行注册,在另一端就可以用callHandler进行调用,调用结果通过Block以异步的方式告知。从调用形式上看,Native和JS完全一样,使用起来非常方便。

Native端注意点

+ (instancetype)bridgeForWebView:(WKWebView*)webView;
- (void)setWebViewDelegate:(id<WKNavigationDelegate>)webViewDelegate;
  • 第一个函数将Bridge和某个webView建立联系

  • 第二个函数设置webView的代理(外部代理),在函数内部,Bridge是真正的代理,进行截取url操作之后,Bridge会调用在这里设的这个代理,让外部代理起作用。代理模式中WebView的代理只有一个,就是Bridge,通过“二传手”模式,从外面看起来好像WebView有两个代理一样。

  • 如果是UIWebView,使用的类是WebViewJavascriptBridge

  • 如果是WKWebView,使用的类是WKWebViewJavascriptBridge

  • 两者的接口函数几乎是一样的,以上两个类基本上也是“二传手”,只是一个容器,真正的工作在类WebViewJavascriptBridgeBase中完成。

  • Android的代码也是有的,不过是另外的第三方库,是为了配合这个库而写的Android版本。这样JS、iOS、Android可以良好地配合起来。
    jesse01/WebViewJavascriptBridge

JS端注意点

  • JS端有固定的写法,里面有一个例子,“ExampleApp.html”这里面有现成的代码,自己只要往里面填自定义的函数就可以。
    function setupWebViewJavascriptBridge(callback) {
        if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
        if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
        window.WVJBCallbacks = [callback];
        var WVJBIframe = document.createElement('iframe');
        WVJBIframe.style.display = 'none';
        WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
        document.documentElement.appendChild(WVJBIframe);
        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
    }

    setupWebViewJavascriptBridge(function(bridge) {
        var uniqueId = 1
        function log(message, data) {
            var log = document.getElementById('log')
            var el = document.createElement('div')
            el.className = 'logLine'
            el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
            if (log.children.length) { log.insertBefore(el, log.children[0]) }
            else { log.appendChild(el) }
        }

        bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
            log('ObjC called testJavascriptHandler with', data)
            var responseData = { 'Javascript Says':'Right back atcha!' }
            log('JS responding with', responseData)
            responseCallback(responseData)
        })

        document.body.appendChild(document.createElement('br'))

        var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
        callbackButton.innerHTML = 'Fire testObjcCallback'
        callbackButton.onclick = function(e) {
            e.preventDefault()
            log('JS calling handler "testObjcCallback"')
            bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
                log('JS got response', response)
            })
        }
    })

(二)通过JavaScriptCore.framework

  • 需要引入系统库JavaScriptCore.framework

  • 最低支持iOS7

  • 有见到过用的场所,但是不多

  • 对于iOS和Android两个平台,JS端是否能够统一,存在不确定性

关于iOS7里的JavaScriptCore framework
JavaScriptCore框架

(三)通过WKWebView的WKUserContentController

  • 最低支持iOS8

  • 需要引入库WebKit.framework

  • 这是采用注入,代理监听,发消息等方式实现的

  • JS端无法统一iOS和Android两个平台的处理

WKWebView与Js实战(OC版)

如何选择

  • 采用WebViewJavascriptBridge目前来看应该是首选,iOS、Android、JS三个平台配合都考虑到了,通信方式也比较简单

  • 最低支持iOS8,WKWebView的注入方式是值得考虑的。只是目前还没有好的第三方库支持。JS需要对iOS和Android需要分别处理,因为两者的注入方式不同。不过这个方向是值得尝试的,功能强大。

  • 不采用WebViewJavascriptBridge,利用截取url的方式是目前大多数的方案,也是值得考虑的。JS、Android、iOS三者要彼此协调好。

  • JavaScriptCore在iOS8之前由于注入的方式还是有吸引力的。不过WKWebView也提供了注入的方式,目前还看不出有什么优势。当前,也是使用者比较少的一种方式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 前言:iOS 开发中,h5 和原生实现通信有多种方式, JSBridge 就是最常用的一种,各 JSBridge ...
    ShannonChenCHN阅读 8,411评论 11 63
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 今天在宿舍听歌,音乐可真不是个好东西。 想起来那年找各种借口跑到大连只为在谁的学校门口篮球场边上过一过,走的时候就...
    崔鹭涛阅读 218评论 0 0
  • 废弃玻璃瓶经过本菇凉的手绘改造,瞬间成了花瓶,种了一株小肉肉[害羞][害羞]心安宁,就是好生活[玫瑰][太阳][太阳]
    基础绘画社阅读 818评论 0 8