相關閱讀:
吊炸天!74款APP完整源碼!
有了這些免費無限次的API 接口,再也不愁沒有伺服器開發不了APP了,也可以自己開發小程序了
github上萬顆star的21個JAVA(19個Android相關)開發框架,知道多少,用過多少(截止2016年12月17日)
原文連結:http://motalks.cn/2016/09/11/Android-WebView-JavaScript-3/
Mo說:大家通過前兩篇文章想必都能順利的 get 到 WebView 與 JavaScript 交互的技能了。現在 App 嵌入 H5 頁面已經是稀鬆平常的事情了,開發者要面對 WebView 也越來越多的爆發出來,比如頁面加載慢,內存洩露,不同 Android 系統版本採用了不同內核的兼容問題等等。 所以當我們使用了 WebView 這個組件的時候,性能優化的事情就不能不提上議程了。這篇文章我們就針對上述問題來總結下 Android WebView 性能優化的常見方法。
頁面加載速度優化影響頁面加載速度的因素有非常多,我們在對 WebView 加載一個網頁的過程進行調試發現,每次加載的過程中都會有較多的網絡請求,除了 web 頁面自身的 URL 請求,還會有 web 頁面外部引用的JS、CSS、字體、圖片等等都是個獨立的 http 請求。這些請求都是串行的,這些請求加上瀏覽器的解析、渲染時間就會導致 WebView 整體加載時間變長,消耗的流量也對應的真多。接下來我們就來說說幾種優化方案來是怎麼解決這個問題的。
選擇合適的 WebView 緩存WebView 緩存看似就是開啟幾個開關的問題,但是要弄懂這幾種緩存機制還是很有深度。下圖是騰訊某工程師總結六種 H5 常用的緩存機制的優勢及適用場景。
主要前端負責,Android 端不需要進行特別的配置。
Dom Storage(Web Storage)存儲機制:配合前端使用,使用時需要打開 DomStorage 開關。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDomStorageEnabled(true);
雖然已經不推薦使用了,但是為了兼容性,還是提供下 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)
Application Cache(簡稱 AppCache)似乎是為支持 Web App 離線使用而開發的緩存機制。它的緩存機制類似於瀏覽器的緩存(Cache-Control 和 Last-Modified)機制,都是以文件為單位進行緩存,且文件有一定更新機制。但 AppCache 是對瀏覽器緩存機制的補充,不是替代。
不過根據官方文檔,AppCache 已經不推薦使用了,標準也不會再支持。現在主流的瀏覽器都是還支持 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);
IndexedDB 也是一種資料庫的存儲機制,但不同於已經不再支持的 Web SQL Database。IndexedDB 不是傳統的關係資料庫,可歸為 NoSQL 資料庫。IndexedDB 又類似於 Dom Storage 的 key-value 的存儲方式,但功能更強大,且存儲空間更大。
Android 在4.4開始加入對 IndexedDB 的支持,只需打開允許 JS 執行的開關就好了。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
File System API 是 H5 新加入的存儲機制。它為 Web App 提供了一個虛擬的文件系統,就像 Native App 訪問本地文件系統一樣。由於安全性的考慮,這個虛擬文件系統有一定的限制。Web App 在虛擬的文件系統中,可以進行文件(夾)的創建、讀、寫、刪除、遍歷等操作。很可惜到目前,Android 系統的 WebView 還不支持 File System API。
簡單的介紹完了上面六種 H5 常用的緩存模式,想必大家能對 Android WebView 所支持的緩存模式有個粗略的了解。如果想和前端更好的配合使用 Android WebView 所支持的緩存,建議看下這篇文章《H5 緩存機制淺析 移動端 Web 加載性能優化》
常用資源預加載上面介紹的緩存技術,能優化二次啟動 WebView 的加載速度,那首次加載 H5 頁面的速度該怎麼優化呢?上面分析了一次加載過程會有許多外部依賴的 JS、CSS、圖片等資源需要下載,那我們能不能提前將這些資源下載好,等H5 加載時直接替換呢?
好在從 API 11(Android 3.0)開始,WebView 引入了 shouldInterceptRequest 函數,這個函數有兩種重載。
public WebResourceResponse shouldInterceptRequest(WebView webView, String url) 從 API 11 引入,API 21 廢棄
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) 從 API 21 開始引入
考慮到目前大多數 App 還要支持 API 14,所以還是使用 shouldInterceptRequest (WebView view, String url) 為例。
WebView mWebView = (WebView) findViewById(R.id.webview);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {
WebResourceResponse response = null;
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");
}
}
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 腳本本地化,直接打包放入 apk 中。比如 H5 頁面獲取用戶信息,設置標題等通用方法,就可以直接寫入一個 JS 文件,放入 asserts 文件夾,在 WebView 調用了onPageFinished() 方法後進行加載。需要注意的是,在該 JS 文件中需要寫入一個 JS 文件載入完畢的事件,這樣前端才能接受都愛 JS 文件已經種植完畢,可以調用 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)
})();
關於 JS 延遲加載
Android 的 OnPageFinished 事件會在 Javascript 腳本執行完成之後才會觸發。如果在頁面中使 用JQuery,會在處理完 DOM 對象,執行完 $(document).ready(function() {}); 事件自會後才會渲染並顯示頁面。而同樣的頁面在 iPhone 上卻是載入相當的快,因為 iPhone 是顯示完頁面才會觸髮腳本的執行。所以我們這邊的解決方案延遲 JS 腳本的載入,這個方面的問題是需要Web前端工程師幫忙優化的。
使用第三方 WebView 內核WebView 的兼容性一直也是困擾我們 Android 開發者的一個大問題,不說 Android 4.4 版本 Google 使用了Chromium 替代 Webkit 作為 WebView 內核,就看看國內眾多的第三方 ROM 都有可能會對原生的 WebView 做出修改,這時候如果出現兼容問題,是非常難定位到問題和解決的。
在一次使用微信瀏覽訂閱公眾號文章的過程中,發現微信的 H5 頁面有一行 『QQ 瀏覽器 X5 內核提供技術支持』。順著這個線索我就找到了騰訊瀏覽服務。發現騰訊已經把這個功能開放了,而且集成的 SDK 很小只有212 KB。這是很驚人的,通過介紹才發現這個 SDK 是可以共享微信和手機 QQ 的 X5 內核。這就很方便了,作為國內市場最不可或缺的兩個 App,我們能只需要集成一個很小的 SDK 就可以共享使用 X5 內核了,不得不說騰訊還是很有想法的。
簡單摘錄些功能亮點,想必能讓大家高潮一番。詳細內容大家可以直接到騰訊瀏覽服務看看,我相信不會讓你們失望的。
網頁瀏覽能力
Web頁面crash率降低75%
頁面打開速度提升35%
流量節省60%
閱讀模式
去除網頁中廣告等雜質
優化文章的閱讀體驗
文件打開能力
包括會話頁的互傳文件及郵件中附件
支持doc、ppt、xls、pdf等辦公格式
支持jpg、gif、png、bmp等圖片格式
支持zip、rar等壓縮文件
支持mp3、mp4、RMVB等音視頻格式
視頻菜單能力
支持屏幕調節等常規視頻菜單功能
靈活切換全屏&小窗功能
WebView 導致的內存洩露Android 中的 WebView 存在很大的兼容性問題,不僅僅是 Android 系統版本的不同對 WebView 產生很大的差異,另外不同的廠商出貨的 ROM 裡面 WebView 也存在著很大的差異。更嚴重的是標準的 WebView 存在內存洩露的問題,看這裡WebView causes memory leak - leaks the parent Activity。所以通常根治這個問題的辦法是為 WebView 開啟另外一個進程,通過 AIDL 與主進程進行通信,WebView 所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到內存的完整釋放。
這段話來自翻譯的 Google Android 內存優化之 OOM 。這裡提到的讓 WebView 獨立運行在一個進程裡,用完 WebView 後直接銷毀這個進程,即使內存洩露了,也不會影響到主進程。微信,手 Q 等 App 也採用了這個方案。但是這就涉及到了跨進程通訊,處理起來就比較麻煩。
另外個解決方案,就是使用自己封裝的 WebView,比如上面提到的 X5 內核,且使用 WebView 的時候,不在 XML 裡面聲明,而是在代碼中直接 new 出來,傳入 application context 來防止 activity 引用被濫用。
WebView webView = new WebView(getContext().getApplicationContext());
webFrameLayout.addView(webView, 0);
在使用了這個方式後,基本上 90% 的 WebView 內存洩漏的問題便得以解決。
上面兩個方案,大家可以結合自己的項目情況選擇。另外對 WebView 內存洩露原因感興趣的可以看看這篇文章。《Android中導致內存洩漏的竟然是它—-Dialog》
參考文章《H5 緩存機制淺析 移動端 Web 加載性能優化》
《android內存優化之webview》
相關閱讀《Android WebView:我是怎麼和 JavaScript 互撩的?》
《Android WebView:JavaScript 調用效率哪家強?》
到這裡,Android WebView 系列文章就告一段落了,大家還有什麼想了解的或者對文中什麼知識點有疑惑的,歡迎留言探討。
關於Java和Android大牛頻道
Java和Android大牛頻道是一個數萬人關注的探討Java和Android開發的公眾號,分享和原創最有價值的乾貨文章,讓你成為這方面的大牛!
我們探討android和Java開發最前沿的技術:android性能優化 ,插件化,跨平臺,動態化,加固和反破解等,也討論設計模式/軟體架構等。由一群來自BAT的工程師組成的團隊。
關注即送紅包,回覆:「百度」 、「阿里」、「騰訊」 有驚喜!!!關注後可用入微信群。群裡都是來自百度阿里騰訊的大牛。
歡迎關注我們,一起討論技術,掃描和長按下方的二維碼可快速關注我們。或搜索微信公眾號:JANiubility。
公眾號:JANiubility