不拦截Request!基于WKWebView的API实现Hybrid容器
在介绍我实现的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
对象上的WebViewJavascriptBridge
和WVJBCallbacks
变量还没值;然后,定义了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容器相关推荐
- postman如何测试php接口_基于Postman的API自动化测试
基于Postman的API自动化测试 1.安装 两种安装方式,我热衷于以chrome插件形式安装 Chrome插件 Mac App 2.发送请求 Postman最基础的功能就是发送http请求,支持G ...
- .NET Core微服务之基于Ocelot实现API网关服务(续)
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.负载均衡与请求缓存 1.1 负载均衡 为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientServic ...
- 为支持两个语言版本,我基于谷歌翻译API写了一款自动翻译的 webpack 插件
大家好,我是若川.持续组织了6个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步.同时极力推荐订阅我写的<学习源码整体架构系列& ...
- NET Core微服务之路:基于Ocelot的API网关Relay实现--RPC篇
前言 我们都知道,API网关是工作在应用层上网关程序,为何要这样设计呢,而不是将网关程序直接工作在传输层.或者网络层等等更底层的环境呢?让我们先来简单的了解一下TCP/IP的五层模型. (图片出自ht ...
- webapi输出炜json_.Net基于MVC4 Web Api输出Json格式实例
本文实例讲述了.Net基于MVC4 Web Api输出Json格式的方法,分享给大家供大家参考.具体实现方法如下: 1.Global 中增加json输出 GlobalConfiguration.Con ...
- 基于Microsoft Translator API的vim翻译插件
前些日子需要阅读某个使用日文注释的项目的代码,就想给vim装个翻译插件.网上简单找了下都是基于Google Translator API的,而Google的该服务已经可耻地不免费了. 本着" ...
- 基于.NET Socket API 通信的综合应用
闲谈一下,最近和客户进行对接Scoket 本地的程序作为请求方以及接受方,对接Scoket 的难度实在比较大,因为涉及到响应方返回的报文的不一致性,对于返回的报文的格式我需要做反序列化的难度增大了不少 ...
- 基于百度地图API在AI Studio上的卫星地图块图像处理与分类
基于百度地图API在AI Stduio上的瓦片地图块图像处理与分类 项目介绍 本项目基于百度地图API获取了不同的瓦片地图并进行合并等处理,可用于遥感和抽象地图的地图块的图像分类.分割.检测等数据的制 ...
- 基于SpringBoot的API网关实现
目录 一.背景&目标 二.基于SpringBoot的API网关架构 2.1.概要架构图 2.2.架构说明 2.3.实现说明 2.3.2 高性能 2.3.3 高可用 2.3.4 安全性 三.总结 ...
- 基于Nuxt3的API接口服务网站
原文链接: 基于Nuxt3的API接口服务网站 挺早之前就想写个 api 接口服务,封装下自己收集的一些 api 接口,以便调用,正好最近在接触 SSR 框架,所以就使用 Nuxt3 来编写该项目. ...
最新文章
- R语言使用ggpubr包的ggarrange函数组合多张结论图:使用ggpubr包将表格嵌套在可视化图像中
- pecl.php.net,WARNING: channel pecl.php.net has updated its protocols, use pecl channel-update pec...
- 配置Vim的显示样式
- 超图三维服务学习摘要1
- 51Nod - 2142身份证号排序
- layui父页面调用子页面的渲染_layui的iframe父子操作方法
- java日志之slf4j与logback简单使用
- 使用JDK 8将收藏转换为地图
- 1004. 成绩排名 (20)-PAT乙级真题
- Android 和 iOS 各有千秋
- getSupportFragmentManager要用在FragmentActivity及其子类中
- 银行金融管理系统java实现
- 《道德经》里的世界观(一种解读,仅供参考)
- DA14580蓝牙硬件系统总览(二)
- We never been grown up
- Mybatis-There is no getter for property named 'tj' in 'class
- 推荐适合胖mm大码 连衣裙 显瘦女装穿搭
- C. Candy Store(数学)
- PHP删除字符串中的空格和换行符终极方法
- 关于文案、营销、生活的15条思考!
热门文章
- 想转行做web前端工程师,必学这6大技能
- 123.static静态函数和函数模板
- 写, 读sdcard目录上的文件
- 获取网页中的验证码图片
- C# Web Service 不使用服务引用直接调用方法(转)
- SecureCRT连接虚拟机中的Linux系统
- getchar()不停止原因
- EasyDarwin添加自定义的服务模块EasyMyModule
- 网页制作初期,必须的东西
- snmp有android代理端吗,GitHub - wosika/SNMP4Android: 简易使用于安卓的SNMP工具类,基于snmp4j...