1.重构项目 背景
cordVoa
WKWebView 替换UIWebView
沟通的重要性
1.历时8天的解决bug的坑逼日程
1.接到一个任务,说在WKWebView替换UIwebview过程中,decidePolicyForNavigationAction拦截url时,URL有些内容会丢失。比如abs:XX//a=1&b=2,正常情况是abs是可以拦截到的,但是WK加载相同的界面,a bs有时侯会加载出来,有时候加载不出来。带着问题找答案,针对此类问题百度一搜一大堆。看了两三天,基本上都是那个几个问题,毕竟大家都被坑的不欠你。比如说一下这个问题和我遇到的问题相似:
(1)cookie问题
说什么cookie 保存不下来之类的,我一想cookie是放在request.httpHeader里面的,是不是一起掉了,当时有过一段怀疑,但是很快就排除掉了,怎么说呢,cookie保存问题,是个很普遍的问题,如果我遇到的问题和cookie一样平常,那么我遇到的问题简书里面应该有体现,但是我发现我遇到的问题,简书里面我找不到,可能是我姿势不正确把,没找到问题所在。
(2)WKWebView NSURLProtocol问题
(1)正式从这个问题,我走向了一条错误的道路,简单来说就是自定义Protocol,我们的协议不是在客户端做的,而是H5根据UserAgent判断加上的。
所以根本就没有牵扯到NSURLProtocol 这个问题。
WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在 WKWebView 上直接使用 NSURLProtocol 无法拦截请求。苹果开源的 webKit2 源码暴露了私有API:
[WKBrowsingContextController registerSchemeForCustomProtocol:]
通过注册 http(s) scheme 后 WKWebView 将可以使用 NSURLProtocol 拦截 http(s) 请求:
Class cls = NSClassFromString(@"WKBrowsingContextController”);
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
// 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理
[(id)cls performSelector:sel withObject:@"http"];
[(id)cls performSelector:sel withObject:@"https"];
}
但是这种方案目前存在两个严重缺陷:
a、post 请求 body 数据被清空
由于 WKWebView 在独立进程里执行网络请求。一旦注册 http(s) scheme 后,网络请求将从 Network Process 发送到 App Process,这样 NSURLProtocol 才能拦截网络请求。在 webkit2 的设计里使用 MessageQueue 进行进程之间的通信,Network Process 会将请求 encode 成一个 Message,然后通过 IPC 发送给 App Process。出于性能的原因,encode 的时候 HTTPBody 和 HTTPBodyStream 这两个字段被丢弃掉了
参考苹果源码:
及bug report:
https://bugs.webkit.org/show_bug.cgi?id=138169(复制链接到浏览器中打开)
因此,如果通过 registerSchemeForCustomProtocol 注册了 http(s) scheme, 那么由 WKWebView 发起的所有 http(s)请求都会通过 IPC 传给主进程 NSURLProtocol 处理,导致 post 请求 body 被清空;
b、对ATS支持不足
测试发现一旦打开ATS开关:Allow Arbitrary Loads 选项设置为NO,同时通过 registerSchemeForCustomProtocol 注册了 http(s) scheme,WKWebView 发起的所有 http 网络请求将被阻塞(即便将Allow Arbitrary Loads in Web Content 选项设置为YES);
WKWebView 可以注册 customScheme, 比如 dynamic://, 因此希望使用离线功能又不使用 post 方式的请求可以通过 customScheme 发起请求,比如dynamic://www.dynamicalbumlocalimage.com/,然后在 app 进程 NSURLProtocol 拦截这个请求并加载离线数据。不足:使用 post 方式的请求该方案依然不适用,同时需要 H5 侧修改请求 scheme 以及 CSP 规则;
(3)在 WKWebView 上通过 loadRequest 发起的 post 请求 body 数据会丢失
因为我们这个是GET,请求也不会出现问题
(4)到这里,基本上研究完了,但是根据log日志的分析,确定了问题大概在 前端加 XX前缀上和WKWebview加载请求之前这个阶段,然后就顺着safras 浏览器的调试功能,确实发现前段判断没加上,是因为我们客户端一个添加agent的方法没加上,至此才发现了问题所在。
坑:(1)自定义协议去了
(2)4.1.1.2 拦截 URL SCHEME
先解释一下 URL SCHEME:URL SCHEME是一种类似于url的链接,是为了方便app直接互相调用设计的,形式和普通的 url 近似,主要区别是 protocol 和 host 一般是自定义的,例如: qunarhy://hy/url?url=ymfe.tech,protocol 是 qunarhy,host 则是 hy。
拦截 URL SCHEME 的主要流程是:Web 端通过某种方式(例如 iframe.src)发送 URL Scheme 请求,之后 Native 拦截到请求并根据 URL SCHEME(包括所带的参数)进行相关操作。
在时间过程中,这种方式有一定的 缺陷:
使用 iframe.src 发送 URL SCHEME 会有 url 长度的隐患。
创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长。
但是之前为什么很多方案使用这种方式呢?因为它 支持 iOS6。而现在的大环境下,iOS6 占比很小,基本上可以忽略,所以并不推荐为了 iOS6 使用这种 并不优雅 的方式。
【注】:有些方案为了规避 url 长度隐患的缺陷,在 iOS 上采用了使用 Ajax 发送同域请求的方式,并将参数放到 head 或 body 里。这样,虽然规避了 url 长度的隐患,但是 WKWebView 并不支持这样的方式。
【注2】:为什么选择 iframe.src 不选择 locaiton.href ?因为如果通过 location.href 连续调用 Native,很容易丢失一些调用。
这部分内容的误导,以为传输过程中丢失了,想用另外一个方式解决。(这种想法基本上不可能,要改的代码太多了)
http://www.cocoachina.com/articles/28999
这片文章给了启示,也给了误导,里面给了好多技术方案。
https://juejin.im/entry/5975916e518825594d23d777
这片文章,给了好多技术原理方面的知识。
(3) 方法总结 1:阶段性调试 (屏蔽原则) 2.log分析 3.问题定位 前后端连调。