在介绍我实现的Hybrid容器之前,建议先了解一下,常用的JavaScript和Native相互通信的方式到底有多少种?

建议阅读一下这篇文章: 从零收拾一个hybrid框架(一)-- 从选择JS通信方案开始

以下,假设你已对JS和Native通信方式有了基本的了解。

常用的三方库WebViewJavascriptBridge,为了兼容UIWebView,继续采用了假跳转拦截Request的方式。其实,你也可以不用拦截的方式,而是使用WKWebView自身提供的API。

1.先分析一下拦截Request的方式(如果你已经掌握,可以跳过)

WebViewJavascriptBridge源码分析。它加载了本地的ExampleApp.html文件。ExampleApp.html加载时,运行了一个js方法:setupWebViewJavascriptBridge(callback).

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 = 'https://__bridge_loaded__';document.documentElement.appendChild(WVJBIframe);setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)}setupWebViewJavascriptBridge(function(bridge) {var uniqueId = 1function 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)})}})

刚开始,window对象上的WebViewJavascriptBridgeWVJBCallbacks变量还没值;然后,定义了WVJBCallbacks数组,这是一个方法数组,存放了注册事件的操作。然后,声明了一个iframe,它的src是一个假地址(这也是为什么称呼它叫假跳转)。简单的说,html中的iframe就是打开一个网页。表现到webView上,就是跳转了一个新的链接。对于这个假链接,客户端当然不会做跳转,而是去注入了JS脚本文件。 JS文件主要用于处理web和native的通信,是用队列的方式去接收和发送事件。注入的JS文件做了什么?抛开一大堆的声明,可以看到如下的调用:

    messagingIframe = document.createElement('iframe');messagingIframe.style.display = 'none';messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;document.documentElement.appendChild(messagingIframe);registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);setTimeout(_callWVJBCallbacks, 0);function _callWVJBCallbacks() {var callbacks = window.WVJBCallbacks;delete window.WVJBCallbacks;for (var i=0; i<callbacks.length; i++) {callbacks[i](WebViewJavascriptBridge);}}

此处又创建了一个iframe,它的src是特定格式的:https://__wvjb_queue_message__。我们还看到,_callWVJBCallbacks方法中遍历了WVJBCallbacks数组,并且传递了WebViewJavascriptBridge。回到上文看数组中的方法,你会发现,这个时候:交互事件被注册。再回到客户端,这里的src也不是跳转链接,而是web和native的事件交互“触发器”。

总结:WebViewJavascriptBridge会触发2类假跳转,第一类用于客户端向webView注入js,第二类用于web和native事件交互。原理就是跳转的过程中拦截了请求,对请求做了特定的处理。

2.用WKWebView的API实现

本文的主题是不拦截Request,如果真的这么做,从上文的流程,我们可以看出2个问题。

  • 如何注入js文件?
  • 如何“传递交互”事件?

解决问题2: Apple提供了JavaScriptCore之后,JS在iOS中的使用如鱼得水。Webkit中有一个关键的类:WKUserContentController。看它的介绍:

A WKUserContentController object provides a way for JavaScript to post messages to a web view. The user content controller associated with a web view is specified by its web view configuration.

简单的说,我们可以通过注册事件的方式实现WebView的JS和Native交互。(代码示例我就不提供了,看API文档)

解决问题1: Webkit还提供了一个类:WKUserScript。它只提供一个公有方法,用于注入script文件。有2种可选时机,一种是页面加载完成,一种是页面开始加载。

其实到这里,这个方案的初步轮廓已经完成了。简单的说就是用WKWebView的API。但是,还有优化的地方。

优化

我们在WebViewJavascriptBridge的ExampleApp.html文件中可以看到,每一个交互的事件都需要单独的注册。能否用一个事件去处理呢?这是完全可以的。我们知道,事件交互时,会传递数据,我们可以:把需要调用的方法名作为参数传递给客户端,客户端用NSMethodSignature类生成函数签名,最后通过runtime去调用对应的方法。

例如:在注入的js文件中,我们可以这么做:

//  ...其他处理function _on(event, callback) {//...略_event_hook_map[event] = callback;
}function _handleMessageFromApp(message) {//...略switch(message) {case 'event': {//...}case 'init' : {var ret = _event_hook_map[xxx];}}
}function _setDefaultEventHandlers() {_on('sys:init',function(ses){if (window.RbJSBridge._hasInit) {console.log('hasInit, no need to init again');return;}else{console.log('init event');}window.RbJSBridge._hasInit = true;// bridge readyvar readyEvent = doc.createEvent('Events');readyEvent.initEvent('RbJSBridge');doc.dispatchEvent(readyEvent);});
}var doc = document;
_setDefaultEventHandlers();

1.webView加载时,注入js,js会调用_setDefaultEventHandlers();方法。_event_hook_map中存放了注册事件的方法,但是它要等待页面加载成功才能注册。

2.当客户端页面加载成功后,客户端在webView的didFinish代理方法中主动调用_handleMessageFromApp()方法,告诉web开始注册事件。

当客户端收到web的交互事件时,我们需要做的是把方法名“翻译”成函数。这里需要一套统一的规则。即我们规定(根据自身需要,两端人员制定):调用的所有客户端方法都只有2个参数,一是字典参数,而是block回调。然后,将其生成方法,如下:

+ (id)ur_performSelectorWithTargetName:(NSString *)targetName selector:(SEL)aSelector withObjects:(NSArray *)objects {URWebWidgetManager *manager = [URWebWidgetManager shareInstance];id realInstance = [manager.widgets objectForKey:targetName];if ([realInstance respondsToSelector:aSelector]) {NSMethodSignature *signature = [realInstance methodSignatureForSelector:aSelector];NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];[invocation setTarget:realInstance];[invocation setSelector:aSelector];NSUInteger i = 1;for (id object in objects) {id tempObject = object;[invocation setArgument:&tempObject atIndex:++i];}[invocation invoke];    //方法被执行if ([signature methodReturnLength]) {id data;[invocation getReturnValue:&data];return data;}}return nil;
}

这样,我们就不需要每次都在html和客户端中注册交互事件。而是前端规定好调用的客户端方法名,客户端提供对应的实现就好了。

最后

不拦截Request只是一种实现方式,我并没有去检测这么做会不会比拦截的性能更高。可是,我觉得提供唯一的事件注册,并把这个工作放在注入的js文件中去做,可以极大的减少两端开发人员要做的事情。web端调用客户端时,提供方法名和参数即可。客户端只要实现对应的方法名函数即可。 目前,基于这种方案实现的H5容器已经在我们公司的线上产品中使用。我还没有单独整理出demo,整理出来后第一时间更新。

不拦截Request!基于WKWebView的API实现Hybrid容器相关推荐

  1. postman如何测试php接口_基于Postman的API自动化测试

    基于Postman的API自动化测试 1.安装 两种安装方式,我热衷于以chrome插件形式安装 Chrome插件 Mac App 2.发送请求 Postman最基础的功能就是发送http请求,支持G ...

  2. .NET Core微服务之基于Ocelot实现API网关服务(续)

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.负载均衡与请求缓存 1.1 负载均衡 为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientServic ...

  3. 为支持两个语言版本,我基于谷歌翻译API写了一款自动翻译的 webpack 插件

    大家好,我是若川.持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列& ...

  4. NET Core微服务之路:基于Ocelot的API网关Relay实现--RPC篇

    前言 我们都知道,API网关是工作在应用层上网关程序,为何要这样设计呢,而不是将网关程序直接工作在传输层.或者网络层等等更底层的环境呢?让我们先来简单的了解一下TCP/IP的五层模型. (图片出自ht ...

  5. webapi输出炜json_.Net基于MVC4 Web Api输出Json格式实例

    本文实例讲述了.Net基于MVC4 Web Api输出Json格式的方法,分享给大家供大家参考.具体实现方法如下: 1.Global 中增加json输出 GlobalConfiguration.Con ...

  6. 基于Microsoft Translator API的vim翻译插件

    前些日子需要阅读某个使用日文注释的项目的代码,就想给vim装个翻译插件.网上简单找了下都是基于Google Translator API的,而Google的该服务已经可耻地不免费了. 本着" ...

  7. 基于.NET Socket API 通信的综合应用

    闲谈一下,最近和客户进行对接Scoket 本地的程序作为请求方以及接受方,对接Scoket 的难度实在比较大,因为涉及到响应方返回的报文的不一致性,对于返回的报文的格式我需要做反序列化的难度增大了不少 ...

  8. 基于百度地图API在AI Studio上的卫星地图块图像处理与分类

    基于百度地图API在AI Stduio上的瓦片地图块图像处理与分类 项目介绍 本项目基于百度地图API获取了不同的瓦片地图并进行合并等处理,可用于遥感和抽象地图的地图块的图像分类.分割.检测等数据的制 ...

  9. 基于SpringBoot的API网关实现

    目录 一.背景&目标 二.基于SpringBoot的API网关架构 2.1.概要架构图 2.2.架构说明 2.3.实现说明 2.3.2 高性能 2.3.3 高可用 2.3.4 安全性 三.总结 ...

  10. 基于Nuxt3的API接口服务网站

    原文链接: 基于Nuxt3的API接口服务网站 挺早之前就想写个 api 接口服务,封装下自己收集的一些 api 接口,以便调用,正好最近在接触 SSR 框架,所以就使用 Nuxt3 来编写该项目. ...

最新文章

  1. R语言使用ggpubr包的ggarrange函数组合多张结论图:使用ggpubr包将表格嵌套在可视化图像中
  2. pecl.php.net,WARNING: channel pecl.php.net has updated its protocols, use pecl channel-update pec...
  3. 配置Vim的显示样式
  4. 超图三维服务学习摘要1
  5. 51Nod - 2142身份证号排序
  6. layui父页面调用子页面的渲染_layui的iframe父子操作方法
  7. java日志之slf4j与logback简单使用
  8. 使用JDK 8将收藏转换为地图
  9. 1004. 成绩排名 (20)-PAT乙级真题
  10. Android 和 iOS 各有千秋
  11. getSupportFragmentManager要用在FragmentActivity及其子类中
  12. 银行金融管理系统java实现
  13. 《道德经》里的世界观(一种解读,仅供参考)
  14. DA14580蓝牙硬件系统总览(二)
  15. We never been grown up
  16. Mybatis-There is no getter for property named 'tj' in 'class
  17. 推荐适合胖mm大码 连衣裙 显瘦女装穿搭
  18. C. Candy Store(数学)
  19. PHP删除字符串中的空格和换行符终极方法
  20. 关于文案、营销、生活的15条思考!

热门文章

  1. 想转行做web前端工程师,必学这6大技能
  2. 123.static静态函数和函数模板
  3. 写, 读sdcard目录上的文件
  4. 获取网页中的验证码图片
  5. C# Web Service 不使用服务引用直接调用方法(转)
  6. SecureCRT连接虚拟机中的Linux系统
  7. getchar()不停止原因
  8. EasyDarwin添加自定义的服务模块EasyMyModule
  9. 网页制作初期,必须的东西
  10. snmp有android代理端吗,GitHub - wosika/SNMP4Android: 简易使用于安卓的SNMP工具类,基于snmp4j...