[17年跨領域學習]從 WKWebview 再談混合開發

作者:滴滴公共前端——小春

前言

首先祝福各位同學新年快樂,17 年我們在大前端領域討論點什麼呢?

這個問題我相信很多公司的前端負責人都會思考。這裡不作預言,只是帶著前端同學們實實在在地一起再來深入地看看混合開發

之前我們會強調前後端交互的重要性,比如:

  1. POST 請求的 Content-Type 是 application/json 還是 application/x-www-form-urlencoded,因為對應後端語言(PHP、Go、java 等) 獲取數據的方式會有差異。

  2. 再比如不同後端處理跨域的方式。

那類似的其實也有很多人也關注到前端和客戶端交互的重要性,畢竟現在的 APP 內嵌開發頻度要比傳統的 Web 網站多。

我們以 WKWebview 這個切入點來談談所謂的混合開發。

注釋

  1. 本文中會穿插大量的 iOS 代碼和辭彙,而且也會對 iOS 代碼風格和規範進行標註,來方便前端同學了解 iOS 的部分細節實現。

  2. 本文更多是從一個前端 iOS 客戶端的雙重角度去看待混合開發。

如有錯誤請指正。

正文

很簡單的問題:我們以往的前端代碼都在什麼環境(容器下)運行?

答案便是:瀏覽器(包含 PC 的各種瀏覽器以及手機上的瀏覽器)以及我們先要介紹的 WebView

PC 瀏覽器內核的移植

對硬體原生 API 支持和 Webkit 特性都支持不夠

所以很多混合開發解決方案的第一個點就是:

做一個增強版本的 WebView

還有人記得 PhoneGap 嗎?

很多我們早期的混合開發者基本都聽過或者用過它,尤其被前端同學喜歡:

因為開發都是用我們擅長的前端技術開發 App

但是相比客戶端開發同學會發現它有很多詬病:

因為它不是原生和前端混合使用

在 iOS 中內部為 UIWebView

用於 iOS 網路視圖載入網頁

它有哪些能力?

  1. 指定一個在線網頁地址,通過 NSURLRequest 類創建一個網路請求

配合 UIWebViewloadRequst 來進行網路視圖的載入

代碼實例

- (void) viewDidLoad { n [super viewDidLoad]; n UIWebView *webview = [[UIWebViewalloc]initWithFrame:self.view.frame]; n NSURL *url = [NSURL URLWithString:@"https://zhuanlan.zhihu.com/ddfe-weekly"]; n NSURLRequst *request = [NSURLRequest requestWithURL:url]; n [webView loadRequest:request]; n [self.view addSubview:webview];n}n

給前端同學的注釋:

// 語法內容

*- :方法裡面的加號和減號,減號一般是對象方法

viewDidLoad :視圖的生命周期方法,和我們前端的 onLoad 或者 jq 裡面的 ready 一樣,初始化用到

NSURL - 類似我們的 Location 對象,能解析 URL

// 代碼風格規範

1、使用 4 個空格縮進,和前端開發規範一樣

2、方法的書寫:

* - 和 (void) 有一個空格,第一行結束的 { 在當前行的末尾

PS:據說有一些公司的 C 語言規範是第一個大括弧獨佔一行

  1. UIWebView 類也支持載入 HTML 文件來實現遠程下載或者本地離線載入

通過 UIWebViewloadHTMLString

注釋:HTML 字元串引號需要轉義

  1. 代理(Delegate)

在哪定義當前視圖狀態呢?

就是:UIWebViewDelegate

一般我們會定義 ViewController

  1. NSData 載入

一般針對圖片資源載入。

給前端同學的注釋:

NSData 應用於文件讀取,可以設置緩衝區

NSData 是不變緩衝區

NSMutableData 是可變緩衝區

  1. 我們看看 UIWebView 源碼裡面都定義了哪些屬性和方法:

注釋:iOS 9.3 UIKit UIWebView.h

PS:

蘋果內部對 WebView 有緩存機制,部分打開過的資源第二次訪問的時候都會嘗試本地讀取,但是不太穩定,關掉之後,系統會清理它。

轉折點來了,這個事情也使得很多一部分同學認識到一個新詞:

17年 1 月 6 號,微信團隊在公眾號發文:

微信 iOS 客戶端將於 3 月 1 日逐步升級為 WKWebView 內核

WKWebView 又是什麼?

蘋果支持最新的 Webkit 功能

從 iOS 8 開始引入的網頁瀏覽控制項(組件)

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

好像是救世主?

  1. 運行消耗的內存明顯減小:App 啟動更快、穩定性更高

  2. 最新的 Web 標準

  3. 高達 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.

那原理到底是什麼?

  1. OC 通過 WebView 的 stringByEvaluatingJavaScriptFromString 來調用 js

  2. js 調用 OC:

  • iOS7 引入了 JavaScriptCore,可以初始化一個 JSContext 對象,然後約定好一個方法名就好了。

  • 一般也可以通過私有協議 Scheme,客戶端會攔截指定的協議

  • 還有人也提到輪詢,但個人感覺這種方式在一般業務場景並不是很多,除了個別特定場景,而且客戶端開銷也大

注釋:WebView 渲染是獨立線程,所以 js 代碼實際是非同步的

說了這麼多,我們看看源碼(pod 版本 6.0.2):

n// WebViewJavascriptBridge/WebViewJavascriptBridge.m #103n- (NSString) _evaluateJavascript:(NSString)javascriptCommand {nreturnn[_webViewstringByEvaluatingJavaScriptFromString:javascriptCommand];n}nn// WebViewJavascriptBridge/WebViewJavascriptBridge.m #178n- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)nnavigationType { n//... n}n

當然細心的同學會發現,它也支持 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 doesnt work with the new WKWebView but my protocol class isnt 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 源碼文件可以看到:

n- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {n //...n}n

UIWebView攔截 shouldStartLoadWithRequest 代理方法(這個前面也提到了)

那很多人肯定會問:切換到這個新的有什麼風險嗎?

  1. 看看是否頁面有適配問題

  2. 看看 jsbridge 是否有錯誤

  3. 看看是否有崩潰

個人覺得這些風險都是可控的,畢竟也會經歷一個灰度的過程。部分同學也看過騰訊 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.hn/**n- 內置的 WebView。 n*/nn@property (nonatomic, strong, readonly) UIWebView *webView;n

那很多專業的人要吐槽了:

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.

那如何評估一個混合應用的好壞呢,一般幾個維度:

  1. 開發效率高

其實大家發現後面的混合方案基本的初衷都是利用前端一些優秀的地方:模塊化,組件化,工程化。

當然客戶端和前端在協調開發的時候也有一些效率工具:比如客戶端載入一個前端的 demo 頁面,同時給前端打一個模擬器安裝包,以及類似 RN 這種 debugger 調試。

  1. 緩存帶來的載入快,資源文件可以本地化,而且我們可以靈活配置化的管理緩存

  • 穩定

  1. js 錯誤可以通過 WebView 來捕獲,然後通過 App 日誌發送服務端來展示

  2. WebView 的 Crash 也可以採用 fabric 這些來收集

總結

17 年我們會放出更多跨領域的內容,來提升前端同學的視野,在互相協作的技術解決方案實施過程中知其所以然。

加油 & 再次新年快樂。

同時感謝:滴滴 iOS 高工文傑老師對 iOS 代碼的指導

擴展閱讀:

marcuswestin/WebViewJavascriptBridge

Rexxar:豆瓣對混合開發的思考

WKWebView 那些坑


推薦閱讀:

iOS 推送全解析,你不可不知的所有 Tips!
如何優雅地使用 KVO
已無力吐槽!iOS 11確實問題很多,尤其是這個不能忍
假如 PC 有一個類似 iOS 的通知系統,不用開某樣軟體也能收到通知,優缺點有哪些?

TAG:iOS | 前端开发 | 移动应用 |