如今許多app都嵌入了H5頁面,H5具備開發週期短、靈活性好
的特色。可是WebView的性能問題卻一直影響着用戶體驗。特別突出的就是加載速度慢
和消耗流量
。css
一般將html/js/css等靜態資源放到CDN上,而後頁面加載後,再經過CGI去拉取最新的數據,進行拼接展現。這種模式的加載流程以下: html
從流程上看存在的直觀的問題:android
H5頁面的渲染速度主要取決於三點,渲染速度
、資源加載速度
和WebView建立耗時
: 渲染速度
取決於兩個方面:git
資源加載速度
:通常加載一個H5頁面,都會產生多個的網絡請求,包括:github
WebView建立耗時
:本地Webview初始化都要很多時間, 首次初始化webview與第二次初始化不一樣,首次會比第二次慢不少。 緣由猜想:webview初始化後,即便webview已經釋放,但一些webview共用的全局服務或資源對象仍沒有釋放,第二次初始化時不須要再初始化這些對象。 H5的流量耗費主要源於:每次使用H5頁面時,都須要從新加載H5頁面。每次加載都會產生較多的網絡請求。 問題總結以下圖: web
針對上述問題,主流的解決方案有:數據庫
H5中有不少的特性,其中就包括離線存儲(也可稱爲緩存機制)這個重要的特性。加入了緩存機制,意味着web應用能夠進行緩存,在沒有網絡的狀況下進行離線訪問。緩存機制帶來的好處包括:segmentfault
到目前爲止,H5的緩存機制一共有六種,分別是:後端
瀏覽器緩存機制主要是根據HTTP協議頭裏的Cache-Control
、Expires
、Last-Modified
以及Etag
請求頭控制緩存:瀏覽器
Cache-Control
:用於控制資源在本地的緩存有效時長: Cache-Control的值就有十幾種,其中包含了請求首部可攜帶的和響應首部攜帶的。例如: max-age=<seconds>
設置緩存存儲的最大週期,超過這個時間緩存被認爲過時(單位秒),若是在時間期限內有再次請求這個資源,瀏覽器不會發出HTTP請求,而是直接使用本地緩存的文件。 no-cache
並不意味着不緩存,在發佈緩存副本以前,強制要求緩存把請求提交給原始服務器進行驗證。在每次請求中使用任何緩存響應以前都不會不會直接去拿緩存資源,而是會首先會經過服務器進行驗證,經過驗證後才使用緩存,不然直接使用服務器返回的新的資源。 no-store
是真正的不進行緩存,不會存儲有關客戶端請求或服務器響應的任何內容。本地和代理服務器都不緩衝,每次都從服務器獲取。請求和響應的信息都不會被存儲在對方的磁盤系統中。
Expires
:做用和Cache-Control
相似,響應頭包含日期/時間(格林尼治時間),表示在此時候以後,響應過時。 在Expires
設置的時間以前,瀏覽器不會再次請求獲取資源的。但若是本地時間和服務器時間不一樣步就會致使出現問題,所以纔會有了上面的Cache-Control
。須要注意的是,Cache-Control
的優先級比Expires
要高。若是在Cache-Control
響應頭設置了max-age
,那麼Expires
頭會被忽略。 示例:Expires: Wed, 21 Oct 2015 07:28:00 GMT
Last-Modified
:包含源頭服務器認定的資源作出修改的日期及時間,用於標識文件在服務器上的最新更新時間。判斷接收到的或者存儲的資源是否彼此一致,再次請求時,若是文件緩存過時,瀏覽器經過If-Modified-Since字段帶上這個時間,發送給服務器,由服務器比較時間戳來判斷文件是否有修改。若是請求的資源從接收的那時起未經修改,那麼返回一個不帶有消息主體的304響應告知瀏覽器使用緩存。不然就返回200,同時返回最新的資源。精確度比ETag
要低,是一個備用機制。
ETag
:做用和Last-Modified
相似,標識文件在服務器上的最新更新時間。不一樣的是,ETag的值是一個對文件進行標識的特徵字串。在向服務器查詢文件是否有更新時,瀏覽器經過If-None-Match字段把特徵字串發送給服務器,服務器將客戶端的ETag與其當前版本的資源的ETag進行比較,若是兩個值匹配(即資源未更改),服務器將返回不帶任何內容的304未修改狀態告知瀏覽器使用緩存。沒有明確指定生成ETag值的方法。一般,使用內容的散列,最後修改時間戳的哈希值,或簡單地使用版本號。ETag
和Last-Modified
可根據需求使用一個或兩個同時使用。兩個同時使用時,只要知足基中一個條件,就認爲文件沒有更新。
常見用法是Cache-Control
與Last-Modified
一塊兒使用, Expires
與ETag
一塊兒使用。一個負責控制緩存的有效時間,一對負責用於緩存失效後判斷是否須要更新。還存在兩種特殊狀況:
Cache-Control:max-age=0
,直接向服務器查詢是否有文件須要更新。Cache-Control:no-cache
,直接向服務器請求新的資源。瀏覽器緩存主要用於靜態資源文件的存儲,例如JS、CSS、字體、圖片等資源,Webview會將緩存的文件記錄及文件內容會存在當前app的data目錄中。WebView內置自動實現,使用默認的CacheMode就能夠實現。也可手動設置緩存模式:
WebSettings webSettings = webView.getSettings();
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
複製代碼
LOAD_CACHE_ONLY
:不使用網絡,只讀取本地緩存數據LOAD_DEFAULT
:根據cache-control決定是否從網絡上取數據LOAD_CACHE_NORMAL
:API level 17中已經廢棄,從API level 11開始做用同LOAD_DEFAULT模式LOAD_NO_CACHE
:不使用緩存,只從網絡獲取數據LOAD_CACHE_ELSE_NETWORK
:只要本地有,不管是否過時,或者no-cache,都使用緩存中的數據。本地沒有緩存時才從網絡上獲取。通常設置爲默認的緩存模式就能夠了。 瀏覽器緩存的優點在於支持Http協議層。不足之處有:
各個請求頭詳解:Cache-Control、Expires、Last-Modified、ETag
Dom Storage的官方描述爲:
DOM 存儲是一套在 Web Applications 1.0 規範中首次引入的與存儲相關的特性的總稱,如今已經分離出來,單獨發展成爲獨立的 W3C Web 存儲規範。 DOM存儲被設計爲用來提供一個更大存儲量、更安全、更便捷的存儲方法,從而能夠代替掉將一些不須要讓服務器知道的信息存儲到 cookies裏的這種傳統方法。 Dom Storage機制相似Cookies,但有一些優點。Dom Storage是經過存儲字符串的Key-Value對來提供的, Dom Storage存儲的數據在本地,不像Cookies,每次請求一次頁面,Cookies都會發送給服務器。 DOM Storage分爲sessionStorage和localStorage,兩者使用方法基本相同,區別在於做用範圍不一樣:前者具備臨時性,用來存儲與頁面相關的數據,它在頁面關閉後沒法使用,後者具有持久性,即保存的數據在頁面關閉後也可使用。
Dom Storage的優點在於:存儲空間(5M)大,遠遠大於Cookies(4KB),並且數據存儲在本地無需常常和服務器進行交互,存儲安全、便捷。可用於存儲臨時的簡單數據。做用機制相似於SharedPreference
。可是,若是要存儲結構化的數據,可能要藉助JSON了,將要存儲的對象轉爲JSON 串。不太適合存儲比較複雜或存儲空間要求比較大的數據,也不適合存儲靜態的文件。 使用方法以下:
webSettings.setDomStorageEnabled(true);
複製代碼
Web SQL Database基於SQL的數據庫存儲機制,用於存儲適合數據庫的結構化數據,充分利用數據庫的優點,存儲適合數據庫的結構化數據,Web SQL Database存儲機制提供了一組可方便對數據進行增長、刪除、修改、查詢。 Android系統也使用了大量的數據庫用來存儲數據,好比聯繫人、短消息等;數據庫的格式爲SQLite。Android也提供了API來操做SQLite。Web SQL Database存儲機制就是經過提供一組API,藉助瀏覽器的實現,將這種Native的功能提供給了Web。 實現方法爲:
String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
// 設置緩存路徑
webSettings.setDatabasePath(cacheDirPath);
webSettings.setDatabaseEnabled(true);
複製代碼
Web SQL Database存儲機制官方已再也不推薦使用,也已經中止了維護,取而代之的是IndexedDB緩存機制
IndexedDB也是一種數據庫的存儲機制,但不一樣於已經再也不支持 Web SQL Database緩存機制。IndexedDB不是傳統的關係數據庫,而是屬於NoSQL數據庫,經過存儲字符串的Key-Value對來提供存儲(相似於Dom Storage,但功能更強大,且存儲空間更大)。其中Key是必需的,且惟一的,Key能夠本身定義,也可由系統自動生成。Value也是必需的,但Value很是靈活,能夠是任何類型的對象。通常Value經過Key來存取的。 IndexedDB提供了一組異步的API,能夠進行數據存、取以及遍歷。IndexedDB有個很是強大的功能:index(索引),它可對Value對象中任何屬性生成索引,而後能夠基於索引進行Value對象的快速查詢。 IndexedDB集合了Dom Storage和Web SQL Database的優勢,用於存儲大塊或複雜結構的數據,提供更大的存儲空間,使用起來也比較簡單。能夠做爲 Web SQL Database的替代。可是不太適合靜態文件的緩存。 Android在4.4開始支持IndexedDB,開啓方法以下:
webSettings.setJavaScriptEnabled(true);
複製代碼
AppCache的緩存機制相似於瀏覽器的緩存(Cache-Control和Last-Modified)機制,都是以文件爲單位進行緩存,且文件有必定更新機制。但AppCache是對瀏覽器緩存機制的補充,不是替代。AppCache有兩個關鍵點:manifest屬性和manifest文件。在頭中經過manifest屬性引用manifest文件 瀏覽器在首次加載時,會解析manifest屬性,並讀取manifest文件,獲取Section:CACHE MANIFEST
下要緩存的文件列表,再對文件緩存。AppCache也有更新機制。被緩存的文件若是要更新,須要更新manifest文件。發現有修改,就會從新獲取manifest文件,manifest文件與緩存文件的檢查更新也遵照瀏覽器緩存機制。用於存儲靜態文件(如JS、CSS、字體文件)。AppCache已經不推薦使用了,標準也不會再支持。 使用方法:
String path = getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
//設置緩存路徑
webSettings.setAppCachePath(path);
//設置緩存大小
webSettings.setAppCacheMaxSize(10*1024*1024);
//開啓緩存
webSettings.setAppCacheEnabled(true);
複製代碼
File System是H5新加入的存儲機制。它爲Web App提供了一個運行在沙盒中的虛擬的文件系統。不一樣WebApp的虛擬文件系統是互相隔離的,虛擬文件系統與本地文件系統也是互相隔離的。Web App在虛擬的文件系統中,經過File System API提供的一組文件與文件夾的操做接口進行文件(夾)的建立、讀、寫、刪除、遍歷等操做。 瀏覽器給虛擬文件系統提供了兩種類型的存儲空間:臨時的和持久性的:
File System的優點在於:
遺憾的是:因爲File System是H5新加入的緩存機制,目前Android WebView暫時還不支持。
名稱 | 原理 | 優勢 | 適用對象 | 說明 |
---|---|---|---|---|
瀏覽器緩存 | 使用HTTP協議頭部字段進行緩存控制 | 支持HTTP協議層 | 存儲靜態資源 | Android默認實現 |
Dom Storage | 經過存儲鍵值對實現 | 存儲空間大,數據在本地,安全便捷 | 相似Cookies,存儲臨時的簡單數據 | 相似Android中的SP |
Web SQL DataBase | 基於SQL | 利用數據庫優點,增刪改查方便 | 存儲複雜、數據量大的結構化數據 | 不推薦使用,用IndexedDB替代 |
IndexedDB | 經過存儲鍵值對實現(NoSQL) | 存儲空間大、使用簡單靈活 | 存儲複雜、數據量大的結構化數據 | 集合Dom Storage和Web SQL DataBase的有點 |
AppCache | 相似瀏覽器緩存,以文件爲單位進行緩存 | 構建方便 | 離線緩存,存儲靜態資源 | 對瀏覽器緩存的補充 |
File System | 提供一個虛擬的文件系統 | 可存儲二進制數據、預加載資源和之間編輯文件 | 經過文件系統管理數據 | 目前Android不支持 |
預加載數據: 提早構建緩存,提前加載將需使用的H5頁面——在初始化WebView的同時,直接由native開始網絡請求數據, 當頁面初始化完成後,向native獲取其代理請求的數據, 數據請求和WebView初始化能夠並行進行,縮短整體的頁面加載時間。 實現思路:配置一個包含包含所需H5模塊的頁面和資源的預加載列表,在合適的時刻提早去請求, 客戶端接管全部請求的緩存,攔截WebViewClient的那兩個shouldInterceptRequest方法,無需進行webview默認緩存邏輯, 自行實現緩存機制。
預加載WebView: 首次使用WebView比後續使用初始化時間要漫長不少,webview初始化後,即便webview已經釋放,但一些webview共用的全局服務或資源對象仍沒有釋放,第二次初始化時不須要再初始化這些對象。並且屢次建立WebView對象耗費時間和資源。 實現思路:在Application裏初始化一個WebView對象;構建WebView複用池,重複使用,避免每次都建立。
可將更新頻率較低、經常使用的靜態資源文件(CSS、圖片等)等H5的頁面和資源進行打包後下發到客戶端,並由客戶端直接解壓到本地儲存中。在加載H5請求網絡資源以前攔截網絡請求並進行檢測,若是成功匹配到本地的靜態資源就直接從本地讀取進行替換,從而免除從服務端獲取。 實現方式:
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
boolean isMatch = ...//判斷是否匹配資源
if (isMatch) {
WebResourceResponse response = ...
if (response != null) {
return response;
}
}
return super.shouldInterceptRequest(view, url);
}
});
複製代碼
優勢是本地化,首屏加載速度快,用戶體驗更爲接近原生, 能夠不依賴網絡,離線運行。 缺點就是開發流程和更新機制複雜, 須要客戶端和服務端的共同協做。
CacheWebView:經過攔截shouldInterceptRequest方法使用Okhttp去下載資源, 同時給OkHttpClient配置了緩存攔截器
VasSonic:一個輕量級的高性能的Hybrid框架,專一於提高頁面首屏加載速度,完美支持靜態直出頁面和動態直出頁面,支持預加載兼容離線包等方案。優勢是性能好,速度快,大廠出品,缺點是配置複雜, 同時須要先後端接入。
本文雖然羅列分析了一些Android WebView的性能問題和解決方案,可是並未深刻分析也沒有寫出詳細的優化教程。具體使用要結合本身的項目進行優化。在此推薦一些文章供你們學習: