前言
我們大前端團隊內部 📖每週一練 的知識複習計劃繼續加油,本篇文章是 《Hybrid APP 混合應用專題》 主題的第二期和第三期的合集。
這一期共整理了 10 個問題,和相應的參考答案,文字和圖片較多,建議大家可以收藏,根據文章目錄來閱讀。
之前分享的每週內容,我都整理到掘金收藏集 📔《EFT每週一練》 上啦,歡迎點贊收藏咯💕💕。
內容回顧:
文章收錄:
本系列所有文章,都將收錄在 Github 上,歡迎點擊查閱。
注:本文整理部分資料來源網絡,有些圖片/段落找不到原文出處,如有侵權,聯繫刪除。
一、iOS 平臺中 UIWebView 與 WKWebView 有什麼區別?
參考文章:《UIWebView與WKWebView》
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 那些坑》
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
中,但存儲時機有延遲,在 iOS 8
上,當頁面跳轉的時候,當前頁面的 Cookie
會寫入 NSHTTPCookieStorage
中,而在 iOS 10
上,JS 執行 document.cookie
或服務器 set-cookie
注入的 Cookie
會很快同步到 NSHTTPCookieStorage
中,FireFox 工程師曾建議通過 reset WKProcessPool
來觸發 Cookie
同步到 NSHTTPCookieStorage
中,實踐發現不起作用,並可能會引發當前頁面 session cookie
丟失等問題。
解決辦法1:
WKWebView loadRequest
前,在 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
後,網絡請求將從 Network Process
發送到 App Process
,這樣 NSURLProtocol
才能攔截網絡請求。在 webkit2
的設計裏使用 MessageQueue
進行進程之間的通信,Network Process 會將請求 encode
成一個 Message
,然後通過 IPC 發送給 App Process
。出於性能的原因,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接口獲取 base64 String
並轉換成 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
上調用 -[WKWebView goBack]
, 回退到上一個頁面後不會觸發window.onload()
函數、不會執行JS。
10. 頁面滾動速率
WKWebView
需要通過 scrollView delegate
調整滾動速率:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}
三、Crosswalk 是什麼,它有什麼作用?
參考網站: 《Crosswalk Github》
參考文章: 《Crosswalk入門》
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
完成後,纔開始加載。
而這段時間,由於WebView還不存在,所有後續的過程是完全阻塞的。因此由於這段過程發生在 native 的代碼中,單純靠前端代碼是無法優化的;大部分的方案都是前端和客戶端協作完成,以下是幾個業界採用過的方案:
1. 全局 WebView
在客戶端剛啓動時,就初始化一個全局的 WebView
待用,並隱藏,當用戶訪問了 WebView
時,直接使用這個 WebView
加載對應網頁,並展示。
這種方法可以比較有效的減少 WebView 在App中的首次打開時間。當用戶訪問頁面時,不需要初始化 WebView 的時間。
當然這也帶來了一些問題,包括:
- 額外的內存消耗。
- 頁面間跳轉需要清空上一個頁面的痕跡,更容易內存泄露。
2. WebView 動態加載
參考文章:《WebView常用優化方案》
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常用優化方案》
這個方法被運用於類似 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常用優化方案》
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》
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 沒有檢測到 Remote Target
中的頁面,可能需要安裝一下 Chrome 的 ADB 插件,也可以在 Chrome 翻牆訪問 https://chrome-devtools-frontend.appspot.com
;
(2)對於騰訊系的 APP,默認採用 X5內核 ,需要將 WebViewDebugHook
的 git 目錄下的 debug.conf
文件拷貝到 SD卡 的根目錄下即可。
2. 使用 DebugGap 調試
參考文章:《Android下的webview調試》
2.1 Windows 下載 DebugGap 並配置:
在電腦上面下載 Windows 版本的 DebugGap
軟件包(下載鏈接:DebugGap),下載完成後解壓下來。
安裝完成後,運行 DebugGap
,開始配置:
- 通常情況下,
DebugGap
可以自動獲取IP,並設置默認的端口,如果沒有,你可以手動設置; - 點擊“連接”按鈕啓動各種客戶端的偵聽器;
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頁面》
一般我們通過 Mac 的 Safari瀏覽器 來調試,但是要注意兩點:
- 如果調試的是 APP 中 WebView 的頁面,則需要這個 APP 的包支持調試,如果不能調試,需要讓 iOS 開發人員重簽名 APP(可能需要將我們 iOS 設備的 ID 寫入到可信任設備列表中,然後使用 iTunes 安裝客戶端提供的測試包即可)。
- 如果調試的是 H5 頁面,可以直接在手機的 Safari瀏覽器 打開直接調試。
下面開始說說在 Mac 上如何調試:
1. 開啓 Safari 開發菜單
先將 iPhone 連接到 Mac,在 Mac 的 Safari 偏好設置中,開啓開發菜單。具體步驟爲:Safari -> 偏好設置… -> 高級 -> 勾選在菜單欄顯示“開發”菜單。
2. iPhone 開啓 Web檢查器
具體步驟爲:設置 -> Safari -> 高級 -> Web 檢查器。
3. 調試 APP 內的 WebView
參考文章:《前端 WEBVIEW 指南之 IOS 調試篇》
在 Safari-> 開發中,看到自己的設備以及 WebView 中網頁,點擊後即可開啓對應頁面的 Inspector
,可以用來進行斷點調試。
七、在內嵌版調試過程中,Fiddler 或 Charles 能起到什麼作用?
這兩者都是強大的抓包工具,原理是以web代理服務器的形式進行工作的,使用的代理地址是:127.0.0.1
,端口默認爲8888
,我們也可以通過設置進行修改。
代理就是在客戶端和服務器之間設置一道關卡,客戶端先將請求數據發送出去後,代理服務器會將數據包進行攔截,代理服務器再冒充客戶端發送數據到服務器;同理,服務器將響應數據返回,代理服務器也會將數據攔截,再返回給客戶端。
Fiddler 或 Charles 的主要作用有:
- 可以代理請求,用來查看頁面發送的請求和接收的響應;
- 可以修改請求的響應,用來模擬自己想要的數據;
- 可以模擬網絡請求的速度;
- 可以代理服務器的靜態資源請求,指向本地文件,省去頻繁發佈 H5 包,達到快速調試目的;
補充鏈接:《Fiddler工具使用介紹一》
八、調試企業微信、微信和釘釘版時,可以使用哪些工具?
1. 調試企業微信、微信
- 微信開發者工具,可以用來調試頁面基本功能;
- 企業微信接口調試工具,可以用來調試企業微信的接口;
2. 調試釘釘
- 釘釘Android開發版,用來調試Android上的釘釘應用;
3. 通用
- Fiddler 或 Charles,可以攔截接口替換文件,來調試應用;
九、常見的調試技巧有哪些?
1. Chrome 控制檯調試
參考文章:《前端常見調試技巧篇總結(持續更新...)》
1.1 Source 面板斷點調試 JS
從左到右,各個圖標表示的功能分別爲:
-
Pause/Resume script execution
:暫停/恢復腳本執行(程序執行到下一斷點停止)。 -
Step over next function call
:執行到下一步的函數調用(跳到下一行)。 -
Step into next function call
:進入當前函數。 -
Step out of current function
:跳出當前執行函數。 -
Deactive/Active all breakpoints
:關閉/開啓所有斷點(不會取消)。 -
Pause on exceptions
:異常情況自動斷點設置。
1.2 Element 面板調試 DOM:
右擊元素,選擇 break on
選項:
-
Subtree modifications
選項,是指當節點內部子節點變化時斷點;
-
Attributes modifications
選項,是指當節點屬性發生變化時斷點;
-
node removal
選項,是指當節點被移除時斷點;
2. console 調試
參考文章:《Console調試常用用法》
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個人博客,如需轉載請保留個人介紹。
Author | 王平安 |
---|---|
[email protected] | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
ES小冊 | js.pingan8787.com |
微信公衆號