使用javascriptcore实现供h5调用的native接口-阿里云开发者社区

开发者社区> 管振纬> 正文

使用javascriptcore实现供h5调用的native接口

简介: 在app开发中使用webview,经常需要从js端调用和原生相关的交互功能。那么这样一层bridge的开发工作具体采用什么方案来实现呢? JS call OC: 方案1: 最古老也是使用最广泛、且跨平台的方案是在页面内嵌入一个iframe,然后通过该iframe触发的webview相关事件来进行hook,从而达到通信的目的。
+关注继续查看

在app开发中使用webview,经常需要从js端调用和原生相关的交互功能。那么这样一层bridge的开发工作具体采用什么方案来实现呢?
JS call OC:
方案1:
最古老也是使用最广泛、且跨平台的方案是在页面内嵌入一个iframe,然后通过该iframe触发的webview相关事件来进行hook,从而达到通信的目的。
其中回调方法的传递是通过生成一个id并保存,来回传递id,在js端再通过id获取到对应的fuction实现回调。大名鼎鼎的cordova就是采用了这种方案实现了bridge。

方案2:
iOS7 苹果引入了javascriptcore引擎;而该引擎可以用作js 和原生代码交互的桥梁。 那具体到webview里面是怎样实现的呢?
javascriptcore的使用,离不开的是jscontext。
对于UIWebview,我们可以在webview的代理方法(比如webViewDidFinishLoad)中使用如下代码获取到jscontext并保存:

    // Undocumented access to UIWebView's JSContext
    // TODO: base64 of documentView.webView.mainFrame.javaScriptContext
    self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

但是对于wkwebview,因为其内部实现的原因,我们无法获取到jscontext,所以这里我们不展开(在文章结尾处我们会大概说一下wkwebview可以采用的方案)。

UIwebview下实现供h5调用的native接口有两种方式:
1. block
在webViewDidFinishLoad末尾插入如下代码(扫码示例):

    @weakify_self;
    self.context[@"scanQRCode"] = ^(JSValue *cb)
    {
        @strongify_self;
        self.scanQRCB = cb;

        OrderCapture *capture = [[OrderCapture alloc] init];
        capture.scanType = OrderCaptureScanTypeAll;
        capture.targetDelegate = self;
        [capture showDecodeView];
    };

这里的cb是js传递过来的回调函数,通过scanQRCB这个属性保存了起来,后面在扫码的delegate方法中可以通过它来调用回调函数:

//条形码返回结果
- (void)didFinishReader:(NSString *)value
{
    [self.scanQRCB callWithArguments:@[value]];
}

js调用的形式(注意:在window上直接调用):

<button onclick = "window.scanQRCode(callback)">点击我弹出原生的扫码!</button>
  1. 通过JSExport协议包装方法
    首先我们要为这些方法注册一个共同的命名空间了(这里叫wq):
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    // Undocumented access to UIWebView's JSContext
    // TODO: base64 of documentView.webView.mainFrame.javaScriptContext
    self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    // do the bridge below... here we use jsexport to do the bridge.

    self.context[@"wq"] = self;
}

实现原生的方法:

- (void)nativeAlert:(NSString *)title cb:(JSValue *)value
{
    self.alertCB = value;

    @weakify_self;
    dispatch_async(dispatch_get_main_queue(), ^{
        @strongify_self;
        self.alert = [[UIAlertView alloc] initWithTitle:title message:@""
                                               delegate:self
                                      cancelButtonTitle:@"取消"
                                      otherButtonTitles:nil, nil];
        [self.alert show];
    });

}

这里的参数应该是和js调用时的顺序对应,jsvalue可以对应js的function。

下面就是实现jsexport协议了,可以放在你的webview容器vc的.h最上面。

@protocol WqJSExport <JSExport>

JSExportAs
(openUrl,
 - (void)openUrlWithUrl:(NSString *)url title:(NSString *)title
 );

//- (void)nativeAlert:(NSString *)title;

JSExportAs
(nativeAlert,
 - (void)nativeAlert:(NSString *)title cb:(JSValue *)value
 );

@end

这里实现了3个方法,分别演示了多参数、单参数、带回调的export实现。
因js只支持单个参数,因此需要使用JSExportAs来对多参数的情况进行包装。
如果只有一个参数,不需要用jsexportAs来包装。
3个方法的调用示例:

<button onclick = "window.wq.openUrl(url, title)">通过原生打开页面!</button>

<button onclick = "window.wq.nativeAlert(biaoti, alertCallback)">点击我弹出原生的alert!</button>

<button onclick = "window.wq.nativeAlert(biaoti, alertCallback)">点击我弹出原生的alert!</button>

那么这样jscore在uiwebview上提供给js的bridge实现就讲完了,这种方法的好处是实现非常清晰,且没有额外的iframe开销;不失为一种优雅的bridge解决方案。

而对于wkwebview来说,需要采用另外的方式来实现(window.webkit.messageHandlers.xxxMethod.postMessage),和上面的方法完全不同,就不再展开了。

而如果使用iframe的方案,可以同时在wkwebview和uiwebview上起作用,考虑同时支持两种webview的情况下使用这种方案是比较合理的,无需做很多额外的处理;关于这套方案的具体实现,有时间再来细说一下(其实不复杂)。

补充:
对于wkwebview,不会自动弹出alert、prompt还有另外一个什么来着,而是可以通过代理方法,需要处理好相应的代理方法才可以完成交互(别以为是bug了,哈哈)。
例(对于alert):

webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Android Native禁止使用系统私有库详解
解读Android Native对于系统私有库的限制,老版本的黑科技代码在N版本之后都可能导致APP崩溃。
1776 0
Java基础-23总结多线程,线程实现Runnable接口,线程名字获取和设置,线程控制,线程安全,同步线程
你需要的是什么,直接评论留言。 获取更多资源加微信公众号“Java帮帮” (是公众号,不是微信好友哦) 还有“Java帮帮”今日头条号,技术文章与新闻,每日更新,欢迎阅读 学习交流请加Java帮帮交流QQ群553841695 分享是一种美德,分享更快乐! 1:多线程(理解) (1)多线程:一个应用程序有多条执行路径 进程:正
2784 0
怎么设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程
8428 0
java多线程 -- 创建线程的第三者方式 实现Callable接口
Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
579 0
JavaScript立即调用的函数表达式
主要参考知乎上这个问题:javascript 匿名函数有哪几种执行方式  长天之云的回答。 ~(function() {//todo})();!(function() {//todo})(); 对于不太熟悉JavaScript的同学来讲,可能会有两个疑问: 1、!(function() {})();前面为什么要加~、!这些符号? 后面的()是执行一个函数,而(function() {})是一个执行表达式,返回一个匿名函数的引用。
608 0
分布式RPC服务调用框架选型:使用Dubbo实现分布式服务调用
本文是一篇详细介绍分布式RPC调用框架Dubbo的文章,介绍了Dubbo服务治理和服务调用的实现。分析了Dubbo中的核心功能,包括Remoting,Cluster和RetRegistry的作用和功能。详细说明了Dubbo中几个角色以及各个角色之间的调用关系。通过这篇文章,可以快速了解Dubbo框架的基本面貌和重要原理,为以后更加深入细致的学习RPC调用框架做出准备。
77 0
+关注
管振纬
资深移动开发者
300
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载