[17年跨领域学习]从 WKWebview 再谈混合开发 | 掘金技术征文

做者:滴滴公共前端团队 - 小春

前言javascript

首先祝福各位同窗新年快乐,17 年咱们在大前端领域讨论点什么呢?前端

这个问题我相信不少公司的前端负责人都会思考。这里不做预言,只是带着前端同窗们实实在在地一块儿再来深刻地看看混合开发java

以前咱们会强调先后端交互的重要性,好比:ios

一、 POST 请求的 Content-Type 是 application/json 仍是 application/x-www-form-urlencoded,由于对应后端语言(PHP、Go、java 等) 获取数据的方式会有差别。git

二、再好比不一样后端处理跨域的方式。github

那相似的其实也有不少人也关注到前端和客户端交互的重要性,毕竟如今的 APP 内嵌开发频度要比传统的 Web 网站多。
web

咱们以 WKWebview 这个切入点来谈谈所谓的混合开发。json

注释后端

一、本文中会穿插大量的 iOS 代码和词汇,并且也会对 iOS 代码风格和规范进行标注,来方便前端同窗了解 iOS 的部分细节实现。跨域

二、本文更可能是从一个前端 iOS 客户端的双重角度去看待混合开发。

若有错误请指正。

正文

很简单的问题:咱们以往的前端代码都在什么环境(容器下)运行?

答案即是:浏览器(包含 PC 的各类浏览器以及手机上的浏览器)以及咱们先要介绍的 WebView

PC 浏览器内核的移植

对硬件原生 API 支持和 Webkit 特性都支持不够

因此不少混合开发解决方案的第一个点就是:

作一个加强版本的 WebView

还有人记得 PhoneGap 吗?

不少咱们早期的混合开发者基本都听过或者用过它,尤为被前端同窗喜欢:

由于开发都是用咱们擅长的前端技术开发 App

可是相比客户端开发同窗会发现它有不少诟病:

由于它不是原生和前端混合使用

在 iOS 中内部为 UIWebView

用于 iOS 网络视图加载网页

它有哪些能力?

一、指定一个在线网页地址,经过 NSURLRequest 类建立一个网络请求

配合 UIWebViewloadRequst 来进行网络视图的加载

代码实例

- (void) viewDidLoad {

[super viewDidLoad];

UIWebView *webview = [[UIWebView alloc] initWithFrame:self.view.frame];

NSURL *url = [NSURL URLWithString:@"https://zhuanlan.zhihu.com/ddfe-weekly"];

NSURLRequst *request = [NSURLRequest requestWithURL:url];

[webView loadRequest:request];

[self.view addSubview:webview];

}

给前端同窗的注释:

// 语法内容

- :方法里面的加号和减号,减号通常是对象方法

viewDidLoad :视图的生命周期方法,和咱们前端的 onLoad 或者 jq 里面的 ready 同样,初始化用到

NSURL - 相似咱们的 Location 对象,能解析 URL

// 代码风格规范

一、使用 4 个空格缩进,和前端开发规范同样

二、方法的书写:

* - 和 (void) 有一个空格,第一行结束的 { 在当前行的末尾

PS:听说有一些公司的 C 语言规范是第一个大括号独占一行

二、UIWebView 类也支持加载 HTML 文件来实现远程下载或者本地离线加载

经过 UIWebViewloadHTMLString

注释:HTML 字符串引号须要转义

三、代理(Delegate)

在哪定义当前视图状态呢?

就是:UIWebViewDelegate

通常咱们会定义 **ViewController 类

四、NSData 加载

通常针对图片资源加载。

给前端同窗的注释:

NSData 应用于文件读取,能够设置缓冲区

NSData 是不变缓冲区

NSMutableData 是可变缓冲区

五、咱们看看 UIWebView 源码里面都定义了哪些属性和方法:

注释:iOS 9.3 UIKit UIWebView.h

PS:

苹果内部对 WebView 有缓存机制,部分打开过的资源第二次访问的时候都会尝试本地读取,可是不太稳定,关掉以后,系统会清理它。

转折点来了,这个事情也使得不少一部分同窗认识到一个新词:

17年 1 月 6 号,微信团队在公众号发文:

微信 iOS 客户端将于 3 月 1 日逐步升级为 WKWebView 内核

WKWebView 又是什么?

苹果支持最新的 Webkit 功能

从 iOS 8 开始引入的网页浏览控件(组件)

-- 高性能的 Web View 解决方案

好像是救世主?

一、运行消耗的内存明显减少:App 启动更快、稳定性更高

二、最新的 Web 标准

三、高达 60 fps 的滚动刷新率,内置手势探测

等等

H5 和 APP 交互方式变了?

大部分的人都会提到 jsbridge 这个词,那真正的内涵是什么呢?

WebViewJavascriptBridge

一个解决 OC 和 Javascript 通信的 bridge 框架

An iOS/OSX bridge for sending messages between Obj-C and JavaScript in WKWebViews, UIWebViews & WebViews.

那原理究竟是什么?

一、OC 经过 WebView 的 stringByEvaluatingJavaScriptFromString 来调用 js

二、js 调用 OC:

* iOS7 引入了 JavaScriptCore,能够初始化一个 JSContext 对象,而后约定好一个方法名就行了。

* 通常也能够经过私有协议 Scheme,客户端会拦截指定的协议

* 还有人也提到轮询,但我的感受这种方式在通常业务场景并非不少,除了个别特定场景,并且客户端开销也大

注释:WebView 渲染是独立线程,因此 js 代码实际是异步的

说了这么多,咱们看看源码(pod 版本 6.0.2):

// WebViewJavascriptBridge/WebViewJavascriptBridge.m #103

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {

return [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];

}

// WebViewJavascriptBridge/WebViewJavascriptBridge.m #178

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

//...

}

固然细心的同窗会发现,它也支持 WKWebViews,咱们在源码包里面也看到了文件:

咱们也都知道在 H5 里面能够经过私有协议来唤起 App 以及细化到某个页面。

对于 WKWebView 呢?

最先在 stackoverflow 上有一篇 Q:

《WKWebView and NSURLProtocol not working》

里面也提到:

When using the old UIWebView you could catch the requests by implementing a custom NSURLProtocol. I use this to handle requests that requires authentication.

I tried the same code and it doesn't work with the new WKWebView but my protocol class isn't called at all.

咱们看看回答:

WKWebView makes requests and renders content out-of-process, meaning your app does not hear the requests they make.

If you are missing a functionality, now is the time to open a bug report and/or an enhancement request with Apple.

As of iOS 10.3 SDK, WKWebView is still unable to make use of custom NSURLProtocols using public APIs.

固然后面也贴了:

Enterprising developers have found an interesting method: +[WKBrowsingContextController registerSchemeForCustomProtocol:]

It supposedly adds the provided scheme to a list of custom protocol handled schemes and should then work with NSURLProtocol.

因此大部分的混合方案都是从入口 URL 拦截

这里有几个区别

WKWebView 拦截 decidePolicyForNavigationAction 方法

咱们能够在上面提到的 WebViewJavascriptBridge 的 WKWebViewJavascriptBridge.m 源码文件能够看到:

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

//...

}

UIWebView拦截 shouldStartLoadWithRequest 代理方法(这个前面也提到了)

那不少人确定会问:切换到这个新的有什么风险吗?

一、看看是否页面有适配问题

二、看看 jsbridge 是否有错误

三、看看是否有崩溃

我的以为这些风险都是可控的,毕竟也会经历一个灰度的过程。部分同窗也看过腾讯 Bugly 以前 1 月份发的《WKWebView 那些坑》的文章,里面也提到了几点和前端有关系的:

  • WKWebView 是多进程组件:Network Loading 和 UI Rendering 在其余进程执行。在一些 WebGL 渲染的复杂页面整体内存占用也不低,过大的彷佛也会 crash 致使白屏

  • 页面适配问题:好比调用 window.innerHeight 致使页面被拉伸

  • 视频自动播放的设置

  • goBack 回退上一页不触发 onload,也不会执行 js

  • Cookie 存储的问题:WKWebView 发起的请求不会自动带上存储在 NSHTTPCookieStorage 容器的 Cookie;并且存储时机会有延时。

这个就致使咱们以前的前端统一登陆组件换了一种方案去 hot fix,更多客源查看文末的原文连接。

咱们提一下豆瓣混合开发框架:Rexxar (以前也邀请来滴滴分享过一次)

它主要分 3 个部分:

  • Rexxar Web

  • Rexxar IOS

  • Rexxar Android

本文咱们重点看一下 iOS 的 Rexxar Container

容器 -- 其实就是一个内置的 WebView

可是增长了原生的一些功能支持:图片缓存、Native UI 的调用等

说了这么多,咱们看看源码:

//rexxar-ios/Rexxar/Core/RXRViewController.h

/**

* 内置的 WebView。

*/

@property (nonatomic, strong, readonly) UIWebView *webView;

那不少专业的人要吐槽了:

Rexxar 采用了原生的 WebView,是对 App 体积没影响

可是以前不少个 WebView 带来的内存问题也一样存在

-- 这个是滴

一样的 Rexxar Container 和 Web 如何交互呢?

前面咱们介绍了 iOS 通常采用 WebViewJavascriptBridge,但它这里不是,采用发送 HTTP 请求(套路基本你们都是采用 iframe 加载特殊约定的 URL),而后 Container 来拦截。其实相似 Proxy,Web 发出的请求都会被 Proxy 处理一下。

那这里好像没有用到 WKWebView,为何呢?

这个咱们以前也在分享的时候请教过豆瓣的同窗,他们也尝试过,毕竟咱们前面介绍了那么多 WKWebView 的好处,大体的结论:

和他们的设计冲突

NSURLProtocol 没法截获 WKWebView 中的请求

固然以前也听过美团大众点评的 Hybrid 方案分享,他们的流程基本也相似:

一个 URL 请求在客户端发起,有一个 Router 来查询本地的路由配置表(这个配置表是 App 从后台路由配置服务拉取的),根据对应的规则去跳转到 H5 仍是 Native.

那如何评估一个混合应用的好坏呢,通常几个维度:

一、开发效率高

其实你们发现后面的混合方案基本的初衷都是利用前端一些优秀的地方:模块化,组件化,工程化。

固然客户端和前端在协调开发的时候也有一些效率工具:好比客户端加载一个前端的 demo 页面,同时给前端打一个模拟器安装包,以及相似 RN 这种 debugger 调试。

二、缓存带来的加载快,资源文件能够本地化,并且咱们能够灵活配置化的管理缓存

  • 稳定

一、js 错误能够经过 WebView 来捕获,而后经过 App 日志发送服务端来展现

二、WebView 的 Crash 也能够采用 fabric 这些来收集

总结

17 年咱们会放出更多跨领域的内容,来提高前端同窗的事业,在互相协做的技术解决方案实施过程当中知其因此然。

加油 & 再次新年快乐。

同时感谢:滴滴 iOS 高工文杰老师对 iOS 代码的指导

扩展阅读:

github.com/marcuswesti…

www.infoq.com/cn/news/201…

mp.weixin.qq.com/s/rhYKLIbXO…

征文活动:

掘金年度征文 | 2016 与个人技术之路


欢迎关注DDFE
GITHUB:github.com/DDFE
微信公众号:微信搜索公众号“DDFE”或扫描下面的二维码

相关文章
相关标签/搜索