前言
我們大前端團隊內部 📖每周一練 的知識複習計劃繼續加油,本篇文章是 《Hybrid APP 混合應用專題》 主題的第二期和第三期的合集。
這一期共整理了 10 個問題,和相應的參考答案,文字和圖片較多,建議大家可以收藏,根據文章目錄來閱讀。
之前分享的每周內容,我都整理到掘金收藏集 [📔《EFT每周一練》] (https://juejin.im/collection/5cd11b0af265da0346227e24) 上啦,歡迎點讚收藏咯💕💕。
文章收錄:
本系列所有文章,都將收錄在 Github 上,[歡迎點擊查閱] (https://github.com/pingan8787/Leo-EveryWeek)。
註:本文整理部分資料來源網絡,有些圖片/段落找不到原文出處,如有侵權,聯繫刪除。
一、iOS 平臺中 UIWebView 與 WKWebView 有什麼區別?參考文章:[《UIWebView與WKWebView》] (http://www.cocoachina.com/articles/17337)
UIWebView 是蘋果繼承於 UIView 封裝的一個加載 web 內容的類,它可以加載任何遠端的web數據展示在你的頁面上,你可以像瀏覽器一樣前進後退刷新等操作。不過蘋果在 iOS8 以後推出了 WKWebView 來加載 Web,並應用於 iOS 和 OSX 中,它取代了 UIWebView 和 WebView ,在兩個平臺上支持同一套 API。
它脫離於 UIWebView 的設計,將原本的設計拆分成14個類,和3個代理協議,雖然是這樣但是了解之後其實用法比較簡單,依照職責單一的原則,每個協議做的事情根據功能分類。
WKWebView 與 UIWebView 的區別:
WKWebView 的內存遠遠沒有 UIWebView 的開銷大,而且沒有緩存;
WKWebView 擁有高達 60FPS 滾動刷新率及內置手勢;
WKWebView 支持了更多的 HTML5 特性;
WKWebView 高效的 app 和 web 信息交換通道;
WKWebView 允許 JavaScript 的 Nitro 庫加載並使用, UIWebView 中限制了;
WKWebView 目前缺少關於頁碼相關的 API;
WKWebView 提供加載網頁進度的屬性;
WKWebView 使用 Safari 相同的 JavaScript 引擎;
WKWebView 增加加載進度屬性: estimatedProgress ;
WKWebView 不支持頁面緩存,需要自己注入 cookie , 而 UIWebView 是自動注入 cookie ;
WKWebView 無法發送 POST 參數問題;
WKWebView 可以和js直接互調函數,不像 UIWebView 需要第三方庫 WebViewJavascriptBridge 來協助處理和 js 的交互;
注意:
大多數App需要支持 iOS7 以上的版本,而 WKWebView 只在 iOS8 後才能用,所以需要一個兼容性方案,既 iOS7 下用 UIWebView , iOS8 後用 WKWebView 。但是目前 IOS10 以下的系統以及很少了,
小結:
WKWebView 相較於 UIWebView 在整體上有較大的提升,滿足 iOS 上面使用同一套控制項的功能,同時對整個內存的開銷以及滾動刷新率和 JS 交互做了優化的處理。
依據職責單一原則,拆分成了三個協議去實現 WebView 的響應,解耦了 JS 交互和加載進度的響應處理。
WKWebView 沒有做緩存處理,所以對網頁需要緩存的加載性能要求沒那麼高的還是可以考慮 UIWebView 。
二、WKWebView 有哪一些坑?參考文章:[《WKWebView 那些坑》] (https://kknews.cc/tech/x2rzg3g.html)
1. WKWebView 白屏問題WKWebView 實際上是個多進程組件,這也是它加載速度更快,內存暫用更低的原因。
在 UIWebView 上當內存佔用太大的時候,App Process 會 crash;而在 WKWebView 上當總體的內存佔用比較大的時候,WebContent Process 會 crash,從而出現白屏現象。
解決辦法:
藉助 WKNavigtionDelegate
當 WKWebView 總體內存佔用過大,頁面即將白屏的時候,系統會調用上面的回調函數,我們在該函數裡執行 [webView reload](這個時候 webView.URL 取值尚不為 nil)解決白屏問題。在一些高內存消耗的頁面可能會頻繁刷新當前頁面,H5側也要做相應的適配操作。
檢測 webView.title 是否為空
並不是所有 H5 頁面白屏的時候都會調用上面的回調函數,比如,最近遇到在一個高內存消耗的 H5 頁面上 present 系統相機,拍照完畢後返回原來頁面的時候出現白屏現象(拍照過程消耗了大量內存,導致內存緊張,WebContent Process 被系統掛起),但上面的回調函數並沒有被調用。在 WKWebView 白屏的時候,另一種現象是 webView.titile 會被置空, 因此,可以在 viewWillAppear的時候檢測 webView.title 是否為空來 reload 頁面。
2. WKWebView Cookie 問題WKWebView Cookie 問題在於 WKWebView 發起的請求不會自動帶上存儲於 NSHTTPCookieStorage 容器中的 Cookie,而在 UIWebView 會自動帶上 Cookie。
原因是:
WKWebView 擁有自己的私有存儲,不會將 Cookie 存入到標準的 Cookie 容器 NSHTTPCookieStorage 中。
實踐發現 WKWebView 實例其實也會將 Cookie 存儲於 NSHTTPCookieStorage 中,但存儲時機有延遲,在 iOS8上,當頁面跳轉的時候,當前頁面的 Cookie 會寫入 NSHTTPCookieStorage 中,而在 iOS10 上,JS 執行 document.cookie 或伺服器 set-cookie 注入的 Cookie 會很快同步到 NSHTTPCookieStorage 中,FireFox 工程師曾建議通過 resetWKProcessPool 來觸發 Cookie 同步到 NSHTTPCookieStorage 中,實踐發現不起作用,並可能會引發當前頁面 session cookie丟失等問題。
解決辦法1:
WKWebViewloadRequest 前,在 request header 中設置 Cookie, 解決首個請求 Cookie 帶不上的問題;
解決辦法2:
通過 document.cookie 設置 Cookie 解決後續頁面(同域) Ajax``、iframe 請求的 Cookie 問題;(注意: document.cookie() 無法跨域設置 cookie)。
3. WKWebView loadRequest 問題在 WKWebView 上通過 loadRequest 發起的 post 請求 body 數據會丟失,同樣是由於進程間通信性能問題, HTTPBody 欄位被丟棄。
4. WKWebView NSURLProtocol問題WKWebView 在獨立於 app 進程之外的進程中執行網絡請求,請求數據不經過主進程,因此,在 WKWebView 上直接使用 NSURLProtocol 無法攔截請求。
解決辦法:
由於 WKWebView 在獨立進程裡執行網絡請求。一旦註冊 http(s)scheme 後,網絡請求將從 NetworkProcess 發送到 AppProcess,這樣 NSURLProtocol 才能攔截網絡請求。在 webkit2 的設計裡使用 MessageQueue 進行進程之間的通信,Network Process 會將請求 encode 成一個 Message,然後通過 IPC 發送給 AppProcess。出於性能的原因, encode 的時候 HTTPBody 和 HTTPBodyStream 這兩個欄位會被丟棄掉了。
5. WKWebView 頁面樣式問題在 WKWebView 適配過程中,我們發現部分 H5 頁面元素位置向下偏移或被拉伸變形,追蹤後發現主要是 H5 頁面高度值異常導致。
解決辦法:
調整 WKWebView 布局方式,避免調整 webView.scrollView.contentInset 。實際上,即便在UIWebView 上也不建議直接調整 webView.scrollView.contentInset 的值,這確實會帶來一些奇怪的問題。如果某些特殊情況下非得調整 contentInset 不可的話,可以通過下面方式讓H5頁面恢復正常顯示。
6. WKWebView 截屏問題WKWebView 下通過 -[CALayer renderInContext:]實現截屏的方式失效,需要通過以下方式實現截屏功能:
@implementation UIView (ImageSnapshot)
- (UIImage*)imageSnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES,self.contentScaleFactor);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end
然而這種方式依然解決不了 webGL 頁面的截屏問題,截屏結果不是空白就是純黑圖片。
解決辦法:
無奈之下,我們只能約定一個JS接口,讓遊戲開發商實現該接口,具體是通過 canvas getImageData()方法取得圖片數據後返回 base64 格式的數據,客戶端在需要截圖的時候,調用這個JS接口獲取 base64String並轉換成 UIImage。
7. WKWebView crash問題如果 WKWebView 退出的時候,JS剛好執行了 window.alert(), alert 框可能彈不出來, completionHandler 最後沒有被執行,導致 crash;
另一種情況是在 WKWebView 一打開,JS就執行 window.alert(),這個時候由於 WKWebView 所在的 UIViewController 出現( push 或 present )的動畫尚未結束,alert 框可能彈不出來, completionHandler最後沒有被執行,導致 crash。
8. 視頻自動播放WKWebView 需要通過 WKWebViewConfiguration.mediaPlaybackRequiresUserAction 設置是否允許自動播放,但一定要在 WKWebView 初始化之前設置,在 WKWebView 初始化之後設置無效。
9. goBack API問題WKWebView 上調用 -[WKWebViewgoBack], 回退到上一個頁面後不會觸發 window.onload() 函數、不會執行JS。
10. 頁面滾動速率WKWebView 需要通過 scrollViewdelegate 調整滾動速率:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}
三、Crosswalk 是什麼,它有什麼作用?參考網站:[《Crosswalk Github》] (https://Crosswalk-project.org/) 參考文章:[《Crosswalk入門》] (https://www.mobibrw.com/2015/1934)
Crosswalk 是一款開源的 web 引擎。目前 Crosswalk 正式支持的行動作業系統包括 Android 和 Tizen ,在 Android 4.0 及以上的系統中使用 Crosswalk 的 Web 應用程式在 HTML5 方面可以有一致的體驗,同時和系統的整合交互方面(比如啟動畫面、權限管理、應用切換、社交分享等等)可以做到類似原生應用。
現在 Crosswalk 已經成為眾多知名 HTML5 平臺和應用的推薦引擎,包括 Google Mobile Chrome App 、 Intel XDK 、 Famo.us 和 Construct2 等等,未來的 Cordova 4.0 也計劃集成 Crosswalk 。
四、常見的 WebView 性能優化方案有哪一些?0. 問題分析首先需要了解,對於一個普通用戶來講,打開一個 WebView 通常會經歷哪幾個階段,一般有這些:
交互無反饋;
到達新的頁面,頁面白屏;
頁面基本框架出現,但是沒有數據;頁面處於loading狀態;
出現所需的數據;
當 App 首次打開時,默認是並不初始化瀏覽器內核的;只有當創建 WebView 實例的時候,才會創建 WebView 的基礎框架。
所以與瀏覽器不同,App 中打開 WebView 的第一步並不是建立連接,而是啟動瀏覽器內核。
於是我們找到了「為什麼WebView總是很慢」的原因之一:
而這段時間,由於WebView還不存在,所有後續的過程是完全阻塞的。因此由於這段過程發生在 native 的代碼中,單純靠前端代碼是無法優化的;大部分的方案都是前端和客戶端協作完成,以下是幾個業界採用過的方案:
1. 全局 WebView在客戶端剛啟動時,就初始化一個全局的 WebView 待用,並隱藏,當用戶訪問了 WebView 時,直接使用這個 WebView 加載對應網頁,並展示。
這種方法可以比較有效的減少 WebView 在App中的首次打開時間。當用戶訪問頁面時,不需要初始化 WebView 的時間。
當然這也帶來了一些問題,包括:
2. WebView 動態加載參考文章:[《WebView常用優化方案》] (https://www.jianshu.com/p/f64e1b1c90d9)
WebView 動態加載。就是不在 xml 中寫 WebView ,寫一個 layout ,然後把 WebView add 進去。
WebView mWebView = new WebView(getApplicationgContext());
LinearLayout mll = findViewById(R.id.xxx);
mll.addView(mWebView);
protected void onDestroy() {
super.onDestroy();
mWebView.removeAllViews();
mWebView.destroy()
}
這裡用的 getApplicationContext() 也是防止內存溢出,這種方法有一個問題。如果你需要在 WebView 中打開連結或者你打開的頁面帶有 flash,獲得你的 WebView 想彈出一個 dialog ,都會導致從 ApplicationContext 到 ActivityContext 的強制類型轉換錯誤,從而導致你應用崩潰。
這是因為在加載 flash 的時候,系統會首先把你的 WebView 作為父控制項,然後在該控制項上繪製 flash ,他想找一個 Activity 的 Context 來繪製他,但是你傳入的是 ApplicationContext 。然後就崩潰了。
3. 獨立的web進程,與主進程隔開參考文章:[《WebView常用優化方案》] (https://www.jianshu.com/p/f64e1b1c90d9)
這個方法被運用於類似 qq ,微信這樣的超級 app 中,這也是解決任何 WebView 內存問題屢試不爽的方法 對於封裝的 webactivity ,在 manifest.xml 中設置。
<activity
android:name=".webview.WebViewActivity"
android:launchMode="singleTop"
android:process=":remote"
android:screenOrientation="unspecified"
/>
然後在關閉 webactivity 時銷毀進程:
@Overrideprotected void onDestroy() {
super.onDestroy();
System.exit(0);
}
關閉瀏覽器後便銷毀整個進程,這樣一般 95% 的情況下不會造成內存洩漏之類的問題,但這就涉及到 android 進程間通訊,比較不方便處理,優劣參半,也是可選的一個方案。
4. WebView 釋放參考文章:[《WebView常用優化方案》] (https://www.jianshu.com/p/f64e1b1c90d9)
public void destroy() {
if (mWebView != null) {
// 如果先調用destroy()方法,則會命中if (isDestroyed()) return;這一行代碼,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出時調用此方法,移除綁定的服務,否則某些特定系統會報錯
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
try {
mWebView.destroy();
} catch (Throwable ex) {
}
}
}
五、在 Android 平臺下如何調試 WebView?1. 在 Chrome 瀏覽器上調試參考文章:[《Android調試webview》] (https://www.jianshu.com/p/3591eebbe797)
1.1 條件:
在 Android 設備或模擬器運行 Android4.4 或更高版本,Android 設備上啟用 USB調試模式。
Chrome 30 或更高版本。更強大的 WebView 界面調試功能需要 Chrome31 或更高版本。
Android 應用程式中的 WebView 配置為可調試模式。
1.2 Android 代碼中配置 WebView 為可調試:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
注意 web 調測不受 app manifest 文件中 debuggable 標記狀態的影響,如果希望僅 debuggable為 true 時才能使用 web 調測,那麼運行時檢測此標記,如下:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if ( 0 != ( getApplcationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE ) ) {
WebView.setWebContentsDebuggingEnabled(true);
}
}
1.3 手機開啟 USB 調試選項,並用 USB 連接電腦:
開啟 Android 手機的開發者選項,一般在 系統設置 - Android版本 進行多次點擊會觸發開啟開發者選項,然後進入開發者選項頁面,開啟USB調試。
為了避免每次調試時看到此警告,勾選「總是允許從這臺計算機」,並單擊「確定」。
1.4 在 Chrome 中啟用設置「USB web debugging」(不會影響WebView):
在 Chrome 上訪問 chrome://inspect/#devices 或 about:inspect 訪問已啟用調試的 WebView 列表,需要翻牆。
然後在 WebView 列表中選擇你要調試的頁面,點擊「 Inspect 」選項,跟調試 PC 網頁一樣,使用 Chrome 控制臺進行調試。
1.5 小技巧:
(1)訪問 chrome://inspect/#devices 如果 chrome 沒有檢測到 RemoteTarget 中的頁面,可能需要安裝一下 Chrome 的 ADB 插件,也可以在 Chrome 翻牆訪問 https://chrome-devtools-frontend.appspot.com;
(2)對於騰訊系的 APP,默認採用 X5內核 ,我們可以在 APP 內部打開 https://debugx5.qq.com/ 來使用它的Vconsole調試工具進行調試。
2. 使用 DebugGap 調試參考文章:[《Android下的webview調試》] (https://segmentfault.com/a/1190000009240637)
2.1 Windows 下載 DebugGap 並配置:
在電腦上面下載 Windows 版本的 DebugGap 軟體包(下載連結:DebugGap),下載完成後解壓下來。
安裝完成後,運行 DebugGap ,開始配置:
2.2 在客戶端上配置:
在調試項目中要進行測試的 HTML 界面中引入 debuggap.js。
<script src="debuggap.js" type="text/javascript"></script>
當調試項目的加載時,您的應用程式將會有一個藍色的地方,點擊會出現一個四葉三葉草的東西,點擊「配置」,顯示配置頁面。輸入與遠程 DebugGap 上的主機和埠相同的主機和埠,例如 192.168.1.4:11111,然後點擊「連接」按鈕。
1.4電腦端遠程 DebugGap 將檢測即將到來的客戶端,開發人員可以單擊每個客戶端進行調試。
六、在 iOS 平臺下如何調試 WebView?參考文章:[《iOS之Safari調試webView/H5頁面》] (https://www.cnblogs.com/dianming/p/6902442.html)
一般我們通過 Mac 的 Safari瀏覽器 來調試,但是要注意兩點:
下面開始說說在 Mac 上如何調試:
1. 開啟 Safari 開發菜單先將 iPhone 連接到 Mac,在 Mac 的 Safari 偏好設置中,開啟開發菜單。具體步驟為:Safari -> 偏好設置… -> 高級 -> 勾選在菜單欄顯示「開發」菜單。
2. iPhone 開啟 Web檢查器具體步驟為:設置 -> Safari -> 高級 -> Web 檢查器。
3. 調試 APP 內的 WebView參考文章:[《前端 WEBVIEW 指南之 IOS 調試篇》] (https://imnerd.org/ios-webview-debug.html)
在 Safari-> 開發中,看到自己的設備以及 WebView 中網頁,點擊後即可開啟對應頁面的 Inspector,可以用來進行斷點調試。
七、在內嵌版調試過程中,Fiddler 或 Charles 能起到什麼作用?這兩者都是強大的抓包工具,原理是以web代理伺服器的形式進行工作的,使用的代理地址是: 127.0.0.1,埠默認為 8888,我們也可以通過設置進行修改。
代理就是在客戶端和伺服器之間設置一道關卡,客戶端先將請求數據發送出去後,代理伺服器會將數據包進行攔截,代理伺服器再冒充客戶端發送數據到伺服器;同理,伺服器將響應數據返回,代理伺服器也會將數據攔截,再返回給客戶端。
Fiddler 或 Charles 的主要作用有:
補充連結:[《Fiddler工具使用介紹一》] (https://www.cnblogs.com/miantest/p/7289694.html)
八、調試企業微信、微信和釘釘版時,可以使用哪些工具?1. 調試企業微信、微信2. 調試釘釘3. 通用九、常見的調試技巧有哪些?1. Chrome 控制臺調試參考文章:[《前端常見調試技巧篇總結(持續更新...)》] (https://www.cnblogs.com/chenbeibei520/p/9959555.html)
1.1 Source 面板斷點調試 JS
從左到右,各個圖標表示的功能分別為:
Pause/Resumescript execution:暫停/恢復腳本執行(程序執行到下一斷點停止)。
Stepovernextfunctioncall:執行到下一步的函數調用(跳到下一行)。
Stepintonextfunctioncall:進入當前函數。
Stepoutof currentfunction:跳出當前執行函數。
Deactive/Activeall breakpoints:關閉/開啟所有斷點(不會取消)。
Pauseon exceptions:異常情況自動斷點設置。
1.2 Element 面板調試 DOM:
右擊元素,選擇 breakon 選項:
Subtreemodifications 選項,是指當節點內部子節點變化時斷點;
Attributesmodifications 選項,是指當節點屬性發生變化時斷點;
node removal 選項,是指當節點被移除時斷點;
2. console 調試
參考文章:[《Console調試常用用法》] (https://blog.csdn.net/a0405221/article/details/85248433)
2.1 顯示信息的命令:
console.log("normal"); // 用於輸出普通信息
console.info("information"); // 用於輸出提示性信息
console.error("error"); // 用於輸出錯誤信息
console.warn("warn"); // 用於輸出警示信息
console.clear(); // 清空控制臺信息
2.2 計時功能:
console.time() 和 console.timeEnd() :
console.time("控制臺計時器");
for(var i = 0; i < 10000; i++){
for(var j = 0; j < 10000; j++){}
}
console.timeEnd("控制臺計時器")
2.3 信息分組:
console.group() 和 console.groupEnd():
console.group("第一組信息");
console.log("第一組第一條:我的博客");
console.log("第一組第二條:CSDN");
console.groupEnd();
console.group("第二組信息");
console.log("第二組第一條:程序愛好者QQ群");
console.log("第二組第二條:歡迎你加入");
console.groupEnd();
2.4 將對象以樹狀結構展現:
console.dir() 可以顯示一個對象所有的屬性和方法:
var info = {
name : "Alan",
age : "27",
grilFriend : "nothing",
getName : function(){
return this.name;
}
}
console.dir(info);
2.5 顯示某個節點的內容:
console.dirxml() 用來顯示網頁的某個節點( node) 所包含的 html/xml 代碼:
var node = document.getElementById("info");
node.innerHTML += "<p>追加的元素顯示嗎</p>";
console.dirxml(node);
2.5 統計代碼被執行的次數:
使用 console.count():
function myFunction(){
console.count("myFunction 被執行的次數");
}
myFunction(); //myFunction 被執行的次數: 1
myFunction(); //myFunction 被執行的次數: 2
myFunction(); //myFunction 被執行的次數: 3
2.6 輸出表格:
console.table(mytable);
3. 調試各種頁面尺寸雖然把各種各樣的手機都擺在桌子上看起來很酷,但卻很不現實。但是,瀏覽器內卻提供了你所需要的一切。進入檢查面板點擊「切換設備模式」按鈕。這樣,就可以在窗口內調整視窗的大小。
4. debugger 斷點具體的說就是通過在代碼中添加" debugger;"語句,當代碼執行到該語句的時候就會自動斷點。
結語對於初入混合應用開發的小夥伴,還有經常需要調試混合應用的小夥伴,相信會有幫助😁
大家加油~
關於我本文首發在 pingan8787個人博客,如需轉載請保留個人介紹。