1.重構(gòu)項(xiàng)目 背景
cordVoa
WKWebView 替換UIWebView?
溝通的重要性
1.歷時(shí)8天的解決bug的坑逼日程
1.接到一個(gè)任務(wù),說(shuō)在WKWebView替換UIwebview過(guò)程中,decidePolicyForNavigationAction攔截url時(shí),URL有些內(nèi)容會(huì)丟失。比如abs:XX//a=1&b=2,正常情況是abs是可以攔截到的,但是WK加載相同的界面,a bs有時(shí)侯會(huì)加載出來(lái),有時(shí)候加載不出來(lái)。帶著問(wèn)題找答案,針對(duì)此類問(wèn)題百度一搜一大堆??戳藘扇?,基本上都是那個(gè)幾個(gè)問(wèn)題,畢竟大家都被坑的不欠你。比如說(shuō)一下這個(gè)問(wèn)題和我遇到的問(wèn)題相似:
(1)cookie問(wèn)題?
說(shuō)什么cookie 保存不下來(lái)之類的,我一想cookie是放在request.httpHeader里面的,是不是一起掉了,當(dāng)時(shí)有過(guò)一段懷疑,但是很快就排除掉了,怎么說(shuō)呢,cookie保存問(wèn)題,是個(gè)很普遍的問(wèn)題,如果我遇到的問(wèn)題和cookie一樣平常,那么我遇到的問(wèn)題簡(jiǎn)書(shū)里面應(yīng)該有體現(xiàn),但是我發(fā)現(xiàn)我遇到的問(wèn)題,簡(jiǎn)書(shū)里面我找不到,可能是我姿勢(shì)不正確把,沒(méi)找到問(wèn)題所在。
(2)WKWebView NSURLProtocol問(wèn)題
(1)正式從這個(gè)問(wèn)題,我走向了一條錯(cuò)誤的道路,簡(jiǎn)單來(lái)說(shuō)就是自定義Protocol,我們的協(xié)議不是在客戶端做的,而是H5根據(jù)UserAgent判斷加上的。
所以根本就沒(méi)有牽扯到NSURLProtocol 這個(gè)問(wèn)題。
WKWebView 在獨(dú)立于 app 進(jìn)程之外的進(jìn)程中執(zhí)行網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求數(shù)據(jù)不經(jīng)過(guò)主進(jìn)程,因此,在 WKWebView 上直接使用 NSURLProtocol 無(wú)法攔截請(qǐng)求。蘋果開(kāi)源的 webKit2 源碼暴露了私有API:
[WKBrowsingContextController registerSchemeForCustomProtocol:]
通過(guò)注冊(cè) http(s) scheme 后 WKWebView 將可以使用 NSURLProtocol 攔截 http(s) 請(qǐng)求:
Class cls = NSClassFromString(@"WKBrowsingContextController”);
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
// 注冊(cè)http(s) scheme, 把 http和https請(qǐng)求交給 NSURLProtocol處理
[(id)cls performSelector:sel withObject:@"http"];
[(id)cls performSelector:sel withObject:@"https"];
}
但是這種方案目前存在兩個(gè)嚴(yán)重缺陷:
a、post 請(qǐng)求 body 數(shù)據(jù)被清空
由于 WKWebView 在獨(dú)立進(jìn)程里執(zhí)行網(wǎng)絡(luò)請(qǐng)求。一旦注冊(cè) http(s) scheme 后,網(wǎng)絡(luò)請(qǐng)求將從 Network Process 發(fā)送到 App Process,這樣 NSURLProtocol 才能攔截網(wǎng)絡(luò)請(qǐng)求。在 webkit2 的設(shè)計(jì)里使用 MessageQueue 進(jìn)行進(jìn)程之間的通信,Network Process 會(huì)將請(qǐng)求 encode 成一個(gè) Message,然后通過(guò) IPC 發(fā)送給 App Process。出于性能的原因,encode 的時(shí)候 HTTPBody 和 HTTPBodyStream 這兩個(gè)字段被丟棄掉了
參考蘋果源碼:
https://github.com/WebKit/webkit/blob/fe39539b83d28751e86077b173abd5b7872ce3f9/Source/WebKit2/Shared/mac/WebCoreArgumentCodersMac.mm#L61-L88(復(fù)制鏈接到瀏覽器中打開(kāi))
及bug report:
https://bugs.webkit.org/show_bug.cgi?id=138169(復(fù)制鏈接到瀏覽器中打開(kāi))
因此,如果通過(guò) registerSchemeForCustomProtocol 注冊(cè)了 http(s) scheme, 那么由 WKWebView 發(fā)起的所有 http(s)請(qǐng)求都會(huì)通過(guò) IPC 傳給主進(jìn)程 NSURLProtocol 處理,導(dǎo)致 post 請(qǐng)求 body 被清空;
b、對(duì)ATS支持不足
測(cè)試發(fā)現(xiàn)一旦打開(kāi)ATS開(kāi)關(guān):Allow Arbitrary Loads 選項(xiàng)設(shè)置為NO,同時(shí)通過(guò) registerSchemeForCustomProtocol 注冊(cè)了 http(s) scheme,WKWebView 發(fā)起的所有 http 網(wǎng)絡(luò)請(qǐng)求將被阻塞(即便將Allow Arbitrary Loads in Web Content 選項(xiàng)設(shè)置為YES);
WKWebView 可以注冊(cè) customScheme, 比如 dynamic://, 因此希望使用離線功能又不使用 post 方式的請(qǐng)求可以通過(guò) customScheme 發(fā)起請(qǐng)求,比如dynamic://www.dynamicalbumlocalimage.com/,然后在 app 進(jìn)程 NSURLProtocol 攔截這個(gè)請(qǐng)求并加載離線數(shù)據(jù)。不足:使用 post 方式的請(qǐng)求該方案依然不適用,同時(shí)需要 H5 側(cè)修改請(qǐng)求 scheme 以及 CSP 規(guī)則;
(3)在 WKWebView 上通過(guò) loadRequest 發(fā)起的 post 請(qǐng)求 body 數(shù)據(jù)會(huì)丟失
因?yàn)槲覀冞@個(gè)是GET,請(qǐng)求也不會(huì)出現(xiàn)問(wèn)題
(4)到這里,基本上研究完了,但是根據(jù)log日志的分析,確定了問(wèn)題大概在 前端加 XX前綴上和WKWebview加載請(qǐng)求之前這個(gè)階段,然后就順著safras 瀏覽器的調(diào)試功能,確實(shí)發(fā)現(xiàn)前段判斷沒(méi)加上,是因?yàn)槲覀兛蛻舳艘粋€(gè)添加agent的方法沒(méi)加上,至此才發(fā)現(xiàn)了問(wèn)題所在。
坑:(1)自定義協(xié)議去了
? ? ? (2)4.1.1.2 攔截 URL SCHEME
先解釋一下 URL SCHEME:URL SCHEME是一種類似于url的鏈接,是為了方便app直接互相調(diào)用設(shè)計(jì)的,形式和普通的 url 近似,主要區(qū)別是 protocol 和 host 一般是自定義的,例如: qunarhy://hy/url?url=ymfe.tech,protocol 是 qunarhy,host 則是 hy。
攔截 URL SCHEME 的主要流程是:Web 端通過(guò)某種方式(例如 iframe.src)發(fā)送 URL Scheme 請(qǐng)求,之后 Native 攔截到請(qǐng)求并根據(jù) URL SCHEME(包括所帶的參數(shù))進(jìn)行相關(guān)操作。
在時(shí)間過(guò)程中,這種方式有一定的?缺陷:
使用 iframe.src 發(fā)送 URL SCHEME 會(huì)有 url 長(zhǎng)度的隱患。
創(chuàng)建請(qǐng)求,需要一定的耗時(shí),比注入 API 的方式調(diào)用同樣的功能,耗時(shí)會(huì)較長(zhǎng)。
但是之前為什么很多方案使用這種方式呢?因?yàn)樗?支持 iOS6。而現(xiàn)在的大環(huán)境下,iOS6 占比很小,基本上可以忽略,所以并不推薦為了 iOS6 使用這種?并不優(yōu)雅?的方式。
【注】:有些方案為了規(guī)避 url 長(zhǎng)度隱患的缺陷,在 iOS 上采用了使用 Ajax 發(fā)送同域請(qǐng)求的方式,并將參數(shù)放到 head 或 body 里。這樣,雖然規(guī)避了 url 長(zhǎng)度的隱患,但是 WKWebView 并不支持這樣的方式。
【注2】:為什么選擇 iframe.src 不選擇 locaiton.href ?因?yàn)槿绻ㄟ^(guò) location.href 連續(xù)調(diào)用 Native,很容易丟失一些調(diào)用。
這部分內(nèi)容的誤導(dǎo),以為傳輸過(guò)程中丟失了,想用另外一個(gè)方式解決。(這種想法基本上不可能,要改的代碼太多了)
http://www.cocoachina.com/articles/28999?
這片文章給了啟示,也給了誤導(dǎo),里面給了好多技術(shù)方案。
https://juejin.im/entry/5975916e518825594d23d777
這片文章,給了好多技術(shù)原理方面的知識(shí)。
(3) 方法總結(jié) 1:階段性調(diào)試 (屏蔽原則) 2.log分析 3.問(wèn)題定位 前后端連調(diào)。