Android WebView 性能优化
原文出處:http://motalks.cn/2016/09/11/Android-WebView-JavaScript-3/
WebView相關(guān)閱讀
- Android WebView 和 javaScript的互相調(diào)用(一)
- Android WebView 和 javaScript的互相調(diào)用(二)
- Android WebView 和 javaScript的互相調(diào)用(三)
- Android WebView與js交互通信
- Android 4.4 中 WebView 使用注意事項(xiàng)
- Android WebView開發(fā)問題匯總
- Android WebView 性能優(yōu)化
Mo說:大家通過前兩篇文章想必都能順利的 get 到 WebView 與 JavaScript 交互的技能了。現(xiàn)在 App 嵌入 H5 頁面已經(jīng)是稀松平常的事情了,開發(fā)者要面對(duì) WebView 也越來越多的爆發(fā)出來,比如頁面加載慢,內(nèi)存泄露,不同 Android 系統(tǒng)版本采用了不同內(nèi)核的兼容問題等等。 所以當(dāng)我們使用了 WebView 這個(gè)組件的時(shí)候,性能優(yōu)化的事情就不能不提上議程了。這篇文章我們就針對(duì)上述問題來總結(jié)下 Android WebView 性能優(yōu)化的常見方法。
一、頁面加載速度優(yōu)化
影響頁面加載速度的因素有非常多,我們?cè)趯?duì) WebView 加載一個(gè)網(wǎng)頁的過程進(jìn)行調(diào)試發(fā)現(xiàn),每次加載的過程中都會(huì)有較多的網(wǎng)絡(luò)請(qǐng)求,除了 web 頁面自身的 URL 請(qǐng)求,還會(huì)有 web 頁面外部引用的JS、CSS、字體、圖片等等都是個(gè)獨(dú)立的 http 請(qǐng)求。這些請(qǐng)求都是串行的,這些請(qǐng)求加上瀏覽器的解析、渲染時(shí)間就會(huì)導(dǎo)致 WebView 整體加載時(shí)間變長(zhǎng),消耗的流量也對(duì)應(yīng)的真多。接下來我們就來說說幾種優(yōu)化方案來是怎么解決這個(gè)問題的。
二、選擇合適的 WebView 緩存
WebView 緩存看似就是開啟幾個(gè)開關(guān)的問題,但是要弄懂這幾種緩存機(jī)制還是很有深度。下圖是騰訊某工程師總結(jié)六種 H5 常用的緩存機(jī)制的優(yōu)勢(shì)及適用場(chǎng)景。
1、瀏覽器緩存機(jī)制:
主要前端負(fù)責(zé),Android 端不需要進(jìn)行特別的配置。
2、Dom Storage(Web Storage)存儲(chǔ)機(jī)制:
配合前端使用,使用時(shí)需要打開 DomStorage 開關(guān)。
WebView myWebView = (WebView) findViewById(R.id.webview); WebSettings webSettings = myWebView.getSettings(); webSettings.setDomStorageEnabled(true);3、Web SQL Database 存儲(chǔ)機(jī)制:
雖然已經(jīng)不推薦使用了,但是為了兼容性,還是提供下 Android 端使用的方法
WebView myWebView = (WebView) findViewById(R.id.webview); WebSettings webSettings = myWebView.getSettings(); webSettings.setDatabaseEnabled(true); final String dbPath = getApplicationContext().getDir("db",Context.MODE_PRIVATE).getPath(); webSettings.setDatabasePath(dbPath)4、Application Cache 存儲(chǔ)機(jī)制
Application Cache(簡(jiǎn)稱 AppCache)似乎是為支持 Web App 離線使用而開發(fā)的緩存機(jī)制。它的緩存機(jī)制類似于瀏覽器的緩存(Cache-Control 和 Last-Modified)機(jī)制,都是以文件為單位進(jìn)行緩存,且文件有一定更新機(jī)制。但 AppCache 是對(duì)瀏覽器緩存機(jī)制的補(bǔ)充,不是替代。
不過根據(jù)官方文檔,AppCache 已經(jīng)不推薦使用了,標(biāo)準(zhǔn)也不會(huì)再支持。現(xiàn)在主流的瀏覽器都是還支持 AppCache的,以后就不太確定了。同樣給出 Android 端啟用 AppCache 的代碼。
WebView myWebView = (WebView) findViewById(R.id.webview); WebSettings webSettings = myWebView.getSettings(); webSettings.setAppCacheEnabled(true); final String cachePath = getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath(); webSettings.setAppCachePath(cachePath); webSettings.setAppCacheMaxSize(5*1024*1024);5、Indexed Database 存儲(chǔ)機(jī)制
IndexedDB 也是一種數(shù)據(jù)庫的存儲(chǔ)機(jī)制,但不同于已經(jīng)不再支持的 Web SQL Database。IndexedDB 不是傳統(tǒng)的關(guān)系數(shù)據(jù)庫,可歸為 NoSQL 數(shù)據(jù)庫。IndexedDB 又類似于 Dom Storage 的 key-value 的存儲(chǔ)方式,但功能更強(qiáng)大,且存儲(chǔ)空間更大。
Android 在4.4開始加入對(duì) IndexedDB 的支持,只需打開允許 JS 執(zhí)行的開關(guān)就好了。
WebView myWebView = (WebView) findViewById(R.id.webview); WebSettings webSettings = myWebView.getSettings(); webSettings.setJavaScriptEnabled(true);6、File System API
File System API 是 H5 新加入的存儲(chǔ)機(jī)制。它為 Web App 提供了一個(gè)虛擬的文件系統(tǒng),就像 Native App 訪問本地文件系統(tǒng)一樣。由于安全性的考慮,這個(gè)虛擬文件系統(tǒng)有一定的限制。Web App 在虛擬的文件系統(tǒng)中,可以進(jìn)行文件(夾)的創(chuàng)建、讀、寫、刪除、遍歷等操作。很可惜到目前,Android 系統(tǒng)的 WebView 還不支持 File System API。
簡(jiǎn)單的介紹完了上面六種 H5 常用的緩存模式,想必大家能對(duì) Android WebView 所支持的緩存模式有個(gè)粗略的了解。如果想和前端更好的配合使用 Android WebView 所支持的緩存,建議看下這篇文章《H5 緩存機(jī)制淺析 移動(dòng)端 Web 加載性能優(yōu)化》
三、常用資源預(yù)加載
上面介紹的緩存技術(shù),能優(yōu)化二次啟動(dòng) WebView 的加載速度,那首次加載 H5 頁面的速度該怎么優(yōu)化呢?上面分析了一次加載過程會(huì)有許多外部依賴的 JS、CSS、圖片等資源需要下載,那我們能不能提前將這些資源下載好,等H5 加載時(shí)直接替換呢?
好在從 API 11(Android 3.0)開始,WebView 引入了 shouldInterceptRequest 函數(shù),這個(gè)函數(shù)有兩種重載。
public WebResourceResponse shouldInterceptRequest(WebView webView, String url) //從 API 11 引入,API 21 廢棄 public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) //從 API 21 開始引入考慮到目前大多數(shù) App 還要支持 API 14,所以還是使用 shouldInterceptRequest (WebView view, String url) 為例。
WebView mWebView = (WebView) findViewById(R.id.webview); mWebView.setWebViewClient(new WebViewClient() {@Overridepublic WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {WebResourceResponse response = null;// 檢查該資源是否已經(jīng)提前下載完成。我采用的策略是在應(yīng)用啟動(dòng)時(shí),用戶在 wifi 的網(wǎng)絡(luò)環(huán)境下 // 提前下載 H5 頁面需要的資源。boolean resDown = JSHelper.isURLDownValid(url);if (resDown) {jsStr = JsjjJSHelper.getResInputStream(url);if (url.endsWith(".png")) {response = getWebResourceResponse(url, "image/png", ".png");} else if (url.endsWith(".gif")) {response = getWebResourceResponse(url, "image/gif", ".gif");} else if (url.endsWith(".jpg")) {response = getWebResourceResponse(url, "image/jepg", ".jpg");} else if (url.endsWith(".jepg")) {response = getWebResourceResponse(url, "image/jepg", ".jepg");} else if (url.endsWith(".js") && jsStr != null) {response = getWebResourceResponse("text/javascript", "UTF-8", ".js");} else if (url.endsWith(".css") && jsStr != null) {response = getWebResourceResponse("text/css", "UTF-8", ".css");} else if (url.endsWith(".html") && jsStr != null) {response = getWebResourceResponse("text/html", "UTF-8", ".html");}}// 若 response 返回為 null , WebView 會(huì)自行請(qǐng)求網(wǎng)絡(luò)加載資源。 return response;}});private WebResourceResponse getWebResourceResponse(String url, String mime, String style) {WebResourceResponse response = null;try {response = new WebResourceResponse(mime, "UTF-8", new FileInputStream(new File(getJSPath() + TPMD5.md5String(url) + style)));} catch (FileNotFoundException e) {e.printStackTrace();}return response;}public String getJsjjJSPath() {String splashTargetPath = JarEnv.sApplicationContext.getFilesDir().getPath() + "/JS";if (!TPFileSysUtil.isDirFileExist(splashTargetPath)) {TPFileSysUtil.createDir(splashTargetPath);}return splashTargetPath + "/";}四、常用 JS 本地化及延遲加載
比預(yù)加載更粗暴的優(yōu)化方法是直接將常用的 JS 腳本本地化,直接打包放入 apk 中。比如 H5 頁面獲取用戶信息,設(shè)置標(biāo)題等通用方法,就可以直接寫入一個(gè) JS 文件,放入 asserts 文件夾,在 WebView 調(diào)用了onPageFinished() 方法后進(jìn)行加載。需要注意的是,在該 JS 文件中需要寫入一個(gè) JS 文件載入完畢的事件,這樣前端才能接受都愛 JS 文件已經(jīng)種植完畢,可以調(diào)用 JS 中的方法了。 附上一段本地化的 JS 代碼。
javascript: ; (function() {try{window.JSBridge = {'invoke': function(name) {var args = [].slice.call(arguments, 1),callback = args.pop(),params, obj = this[name];if (typeof callback !== 'function') {params = callback;callback = function() {}} else {params = args[0]} if (typeof obj !== 'object' || typeof obj.func !== 'function') {callback({'err_msg': 'system:function_not_exist'});return}obj.callback = callback;obj.params = params;obj.func(params)},'on': function(event, callback) {var obj = this['on' + event];if (typeof obj !== 'object') {callback({'err_msg': 'system:function_not_exist'});retrun}if (typeof callback !== 'undefined') obj.callback = callback},'login': {'func': function(params) {prompt("login", JSON.stringify(params))},'params': {},'callback': function(res) {}},'settitle': {'func': function(params) {prompt("settitle",JSON.stringify(params))},'params': {},'callback': function(res) {}},}catch(e){alert('demo.js error:'+e);}var readyEvent = document.createEvent('Events');readyEvent.initEvent('JSBridgeReady', true, true);document.dispatchEvent(readyEvent) })();六、關(guān)于 JS 延遲加載
Android 的 OnPageFinished 事件會(huì)在 Javascript 腳本執(zhí)行完成之后才會(huì)觸發(fā)。如果在頁面中使 用JQuery,會(huì)在處理完 DOM 對(duì)象,執(zhí)行完 $(document).ready(function() {}); 事件自會(huì)后才會(huì)渲染并顯示頁面。而同樣的頁面在 iPhone 上卻是載入相當(dāng)?shù)目?#xff0c;因?yàn)?iPhone 是顯示完頁面才會(huì)觸發(fā)腳本的執(zhí)行。所以我們這邊的解決方案延遲 JS 腳本的載入,這個(gè)方面的問題是需要Web前端工程師幫忙優(yōu)化的。
七、使用第三方 WebView 內(nèi)核
WebView 的兼容性一直也是困擾我們 Android 開發(fā)者的一個(gè)大問題,不說 Android 4.4 版本 Google 使用了Chromium 替代 Webkit 作為 WebView 內(nèi)核,就看看國(guó)內(nèi)眾多的第三方 ROM 都有可能會(huì)對(duì)原生的 WebView 做出修改,這時(shí)候如果出現(xiàn)兼容問題,是非常難定位到問題和解決的。
在一次使用微信瀏覽訂閱公眾號(hào)文章的過程中,發(fā)現(xiàn)微信的 H5 頁面有一行 『QQ 瀏覽器 X5 內(nèi)核提供技術(shù)支持』。順著這個(gè)線索我就找到了騰訊瀏覽服務(wù)。發(fā)現(xiàn)騰訊已經(jīng)把這個(gè)功能開放了,而且集成的 SDK 很小只有212 KB。這是很驚人的,通過介紹才發(fā)現(xiàn)這個(gè) SDK 是可以共享微信和手機(jī) QQ 的 X5 內(nèi)核。這就很方便了,作為國(guó)內(nèi)市場(chǎng)最不可或缺的兩個(gè) App,我們能只需要集成一個(gè)很小的 SDK 就可以共享使用 X5 內(nèi)核了,不得不說騰訊還是很有想法的。
簡(jiǎn)單摘錄些功能亮點(diǎn),想必能讓大家高潮一番。詳細(xì)內(nèi)容大家可以直接到騰訊瀏覽服務(wù)看看,我相信不會(huì)讓你們失望的。
網(wǎng)頁瀏覽能力
- Web頁面crash率降低75%
- 頁面打開速度提升35%
- 流量節(jié)省60%
閱讀模式
- 去除網(wǎng)頁中廣告等雜質(zhì)
- 優(yōu)化文章的閱讀體驗(yàn)
文件打開能力
- 包括會(huì)話頁的互傳文件及郵件中附件
- 支持doc、ppt、xls、pdf等辦公格式
- 支持jpg、gif、png、bmp等圖片格式
- 支持zip、rar等壓縮文件
- 支持mp3、mp4、RMVB等音視頻格式
視頻菜單能力
- 支持屏幕調(diào)節(jié)等常規(guī)視頻菜單功能
- 靈活切換全屏&小窗功能
八、WebView 導(dǎo)致的內(nèi)存泄露
Android 中的 WebView 存在很大的兼容性問題,不僅僅是 Android 系統(tǒng)版本的不同對(duì) WebView 產(chǎn)生很大的差異,另外不同的廠商出貨的 ROM 里面 WebView 也存在著很大的差異。更嚴(yán)重的是標(biāo)準(zhǔn)的 WebView 存在內(nèi)存泄露的問題,看這里WebView causes memory leak - leaks the parent Activity。所以通常根治這個(gè)問題的辦法是為 WebView 開啟另外一個(gè)進(jìn)程,通過 AIDL 與主進(jìn)程進(jìn)行通信,WebView 所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放。
這段話來自胡凱翻譯的 Google Android 內(nèi)存優(yōu)化之 OOM 。這里提到的讓 WebView 獨(dú)立運(yùn)行在一個(gè)進(jìn)程里,用完 WebView 后直接銷毀這個(gè)進(jìn)程,即使內(nèi)存泄露了,也不會(huì)影響到主進(jìn)程。微信,手 Q 等 App 也采用了這個(gè)方案。但是這就涉及到了跨進(jìn)程通訊,處理起來就比較麻煩。
另外個(gè)解決方案,就是使用自己封裝的 WebView,比如上面提到的 X5 內(nèi)核,且使用 WebView 的時(shí)候,不在 XML 里面聲明,而是在代碼中直接 new 出來,傳入 application context 來防止 activity 引用被濫用。
WebView webView = new WebView(getContext().getApplicationContext()); webFrameLayout.addView(webView, 0);在使用了這個(gè)方式后,基本上 90% 的 WebView 內(nèi)存泄漏的問題便得以解決。
上面兩個(gè)方案,大家可以結(jié)合自己的項(xiàng)目情況選擇。另外對(duì) WebView 內(nèi)存泄露原因感興趣的可以看看這篇文章。
《Android中導(dǎo)致內(nèi)存泄漏的竟然是它—-Dialog》
參考文章
《H5 緩存機(jī)制淺析 移動(dòng)端 Web 加載性能優(yōu)化》
《android內(nèi)存優(yōu)化之webview》
總結(jié)
以上是生活随笔為你收集整理的Android WebView 性能优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android WebView 和 ja
- 下一篇: 使用Palette类提取图片的颜色信息