前言:
首先祝福各位同學新年快樂,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 類建立一個網路請求
配合 UIWebView 的 loadRequst 來進行網路檢視的載入
程式碼例項:
- (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
// 程式碼風格規範
1、使用 4 個空格縮排,和前端開發規範一樣
2、方法的書寫:
* - 和 (void) 有一個空格,第一行結束的 { 在當前行的末尾
PS:據說有一些公司的 C 語言規範是第一個大括號獨佔一行
2、UIWebView 類也支援載入 HTML 檔案來實現遠端下載或者本地離線載入
通過 UIWebView 的 loadHTMLString
註釋:HTML 字串引號需要轉義
3、代理(Delegate)
在哪定義當前檢視狀態呢?
就是:UIWebViewDelegate
一般我們會定義 **ViewController 類
4、NSData 載入
一般針對圖片資源載入。
給前端同學的註釋:
NSData 應用於檔案讀取,可以設定緩衝區
NSData 是不變緩衝區
NSMutableData 是可變緩衝區
5、我們看看 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):
// 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 customNSURLProtocol
s 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 代理方法(這個前面也提到了)
那很多人肯定會問:切換到這個新的有什麼風險嗎?
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.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.
那如何評估一個混合應用的好壞呢,一般幾個維度:
快:
1、開發效率高
其實大家發現後面的混合方案基本的初衷都是利用前端一些優秀的地方:模組化,元件化,工程化。
當然客戶端和前端在協調開發的時候也有一些效率工具:比如客戶端載入一個前端的 demo 頁面,同時給前端打一個模擬器安裝包,以及類似 RN 這種 debugger 除錯。
2、快取帶來的載入快,資原始檔可以本地化,而且我們可以靈活配置化的管理快取
穩定:
1、js 錯誤可以通過 WebView 來捕獲,然後通過 App 日誌傳送服務端來展示
2、WebView 的 Crash 也可以採用 fabric 這些來收集
總結:
17 年我們會放出更多跨領域的內容,來提升前端同學的事業,在互相協作的技術解決方案實施過程中知其所以然。
加油 & 再次新年快樂。
同時感謝:滴滴 iOS 高工文傑老師對 iOS 程式碼的指導
擴充套件閱讀:
歡迎關注DDFE
GITHUB:github.com/DDFE
微信公眾號:微信搜尋公眾號“DDFE”或掃描下面的二維碼