Hybrid App 應用 開發中 9 個必備知識點複習(WebView / 調試 等)

bg

前言

我們大前端團隊內部 📖每週一練 的知識複習計劃繼續加油,本篇文章是 《Hybrid APP 混合應用專題》 主題的第二期和第三期的合集。

這一期共整理了 10 個問題,和相應的參考答案,文字和圖片較多,建議大家可以收藏,根據文章目錄來閱讀

之前分享的每週內容,我都整理到掘金收藏集 📔《EFT每週一練》 上啦,歡迎點贊收藏咯💕💕。

內容回顧:

  1. 《EFT 每週分享 —— Hybrid App 應用開發中 5 個必備知識點複習》
  2. 《EFT 每週分享 —— HTTP 的15個常見知識點複習》
  3. 《EFT 每週分享 —— 數據結構與算法合集》

文章收錄:

本系列所有文章,都將收錄在 Github 上,歡迎點擊查閱

注:本文整理部分資料來源網絡,有些圖片/段落找不到原文出處,如有侵權,聯繫刪除。

一、iOS 平臺中 UIWebView 與 WKWebView 有什麼區別?

參考文章:《UIWebView與WKWebView》

UIWebView 是蘋果繼承於 UIView 封裝的一個加載 web 內容的類,它可以加載任何遠端的web數據展示在你的頁面上,你可以像瀏覽器一樣前進後退刷新等操作。不過蘋果在 iOS8 以後推出了 WKWebView 來加載 Web,並應用於 iOS 和 OSX 中,它取代了 UIWebViewWebView ,在兩個平臺上支持同一套 API。

它脫離於 UIWebView 的設計,將原本的設計拆分成14個類,和3個代理協議,雖然是這樣但是瞭解之後其實用法比較簡單,依照職責單一的原則,每個協議做的事情根據功能分類。

UIWebView

WKWebViewUIWebView 的區別:

  • WKWebView 的內存遠遠沒有 UIWebView 的開銷大,而且沒有緩存;
  • WKWebView 擁有高達 60FPS 滾動刷新率及內置手勢;
  • WKWebView 支持了更多的 HTML5 特性;
  • WKWebView 高效的 app 和 web 信息交換通道;
  • WKWebView 允許 JavaScriptNitro 庫加載並使用, 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 下用 UIWebViewiOS8 後用 WKWebView 。但是目前 IOS10 以下的系統以及很少了,

小結:

WKWebView 相較於 UIWebView 在整體上有較大的提升,滿足 iOS 上面使用同一套控件的功能,同時對整個內存的開銷以及滾動刷新率和 JS 交互做了優化的處理。

依據職責單一原則,拆分成了三個協議去實現 WebView 的響應,解耦了 JS 交互和加載進度的響應處理。

WKWebView 沒有做緩存處理,所以對網頁需要緩存的加載性能要求沒那麼高的還是可以考慮 UIWebView

二、WKWebView 有哪一些坑?

參考文章:《WKWebView 那些坑》

1. WKWebView 白屏問題

WKWebView 實際上是個多進程組件,這也是它加載速度更快,內存暫用更低的原因。

UIWebView 上當內存佔用太大的時候,App Process 會 crash;而在 WKWebView 上當總體的內存佔用比較大的時候,WebContent Process 會 crash,從而出現白屏現象。

解決辦法:

  1. 藉助 WKNavigtionDelegate

WKWebView 總體內存佔用過大,頁面即將白屏的時候,系統會調用上面的回調函數,我們在該函數裏執行[webView reload](這個時候 webView.URL 取值尚不爲 nil)解決白屏問題。在一些高內存消耗的頁面可能會頻繁刷新當前頁面,H5側也要做相應的適配操作。

  1. 檢測 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 的時候 HTTPBodyHTTPBodyStream 這兩個字段會被丟棄掉了。

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 出現( pushpresent )的動畫尚未結束,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 通常會經歷哪幾個階段,一般有這些:

  1. 交互無反饋;
  2. 到達新的頁面,頁面白屏;
  3. 頁面基本框架出現,但是沒有數據;頁面處於loading狀態;
  4. 出現所需的數據;

webview1

當 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 ,都會導致從 ApplicationContextActivityContext 的強制類型轉換錯誤,從而導致你應用崩潰。

這是因爲在加載 flash 的時候,系統會首先把你的 WebView 作爲父控件,然後在該控件上繪製 flash ,他想找一個 ActivityContext 來繪製他,但是你傳入的是 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 標記狀態的影響,如果希望僅 debuggabletrue 時才能使用 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/#devicesabout:inspect 訪問已啓用調試的 WebView 列表,需要翻牆

然後在 WebView 列表中選擇你要調試的頁面,點擊“ Inspect ”選項,跟調試 PC 網頁一樣,使用 Chrome 控制檯進行調試。

Chrome 控制檯

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,並設置默認的端口,如果沒有,你可以手動設置;
  • 點擊“連接”按鈕啓動各種客戶端的偵聽器;

DebugGap

2.2 在客戶端上配置:

在調試項目中要進行測試的 HTML 界面中引入 debuggap.js

<script src="debuggap.js" type="text/javascript"></script>

當調試項目的加載時,您的應用程序將會有一個藍色的地方,點擊會出現一個四葉三葉草的東西,點擊“配置”,顯示配置頁面。輸入與遠程 DebugGap 上的主機和端口相同的主機和端口,例如 192.168.1.4:11111,然後點擊“連接”按鈕。

DebugGap

1.4電腦端遠程 DebugGap 將檢測即將到來的客戶端,開發人員可以單擊每個客戶端進行調試。

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 -> 偏好設置… -> 高級 -> 勾選在菜單欄顯示“開發”菜單。

Safari

2. iPhone 開啓 Web檢查器

具體步驟爲:設置 -> Safari -> 高級 -> Web 檢查器。

Safari

3. 調試 APP 內的 WebView

參考文章:《前端 WEBVIEW 指南之 IOS 調試篇》

在 Safari-> 開發中,看到自己的設備以及 WebView 中網頁,點擊後即可開啓對應頁面的 Inspector ,可以用來進行斷點調試。

Safari

Safari

七、在內嵌版調試過程中,Fiddler 或 Charles 能起到什麼作用?

這兩者都是強大的抓包工具,原理是以web代理服務器的形式進行工作的,使用的代理地址是:127.0.0.1,端口默認爲8888,我們也可以通過設置進行修改。

代理就是在客戶端和服務器之間設置一道關卡,客戶端先將請求數據發送出去後,代理服務器會將數據包進行攔截,代理服務器再冒充客戶端發送數據到服務器;同理,服務器將響應數據返回,代理服務器也會將數據攔截,再返回給客戶端。

Fiddler 或 Charles 的主要作用有:

  • 可以代理請求,用來查看頁面發送的請求和接收的響應;
  • 可以修改請求的響應,用來模擬自己想要的數據;
  • 可以模擬網絡請求的速度;
  • 可以代理服務器的靜態資源請求,指向本地文件,省去頻繁發佈 H5 包,達到快速調試目的;
補充鏈接:《Fiddler工具使用介紹一》

八、調試企業微信、微信和釘釘版時,可以使用哪些工具?

1. 調試企業微信、微信

2. 調試釘釘

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 選項:

break on

  • Subtree modifications 選項,是指當節點內部子節點變化時斷點

break on

  • Attributes modifications 選項,是指當節點屬性發生變化時斷點

break on

  • node removal 選項,是指當節點被移除時斷點

break on

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; "語句,當代碼執行到該語句的時候就會自動斷點。

debugger

結語

對於初入混合應用開發的小夥伴,還有經常需要調試混合應用的小夥伴,相信會有幫助😁

大家加油~

關於我

本文首發在 pingan8787個人博客,如需轉載請保留個人介紹。
Author 王平安
E-mail [email protected]
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787...
ES小冊 js.pingan8787.com

微信公衆號

bg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章