Android WebView緩存機制和性能優化

前言

如今許多app都嵌入了H5頁面,H5具備開發週期短、靈活性好的特色。可是WebView的性能問題卻一直影響着用戶體驗。特別突出的就是加載速度慢消耗流量css

通常的H5加載流程

一般將html/js/css等靜態資源放到CDN上,而後頁面加載後,再經過CGI去拉取最新的數據,進行拼接展現。這種模式的加載流程以下: html

圖片源引騰訊VasSonic

  1. 加載開始前作初始化工做,包括Runtime初始化,建立WebView等;
  2. 完成初始化以後,WebView開始去請求資源加載H5頁面;
  3. 頁面發起CGI請求對應的數據(或者經過本地緩存獲取),拿到數據後對DOM進行操做更新。

從流程上看存在的直觀的問題:android

  1. 終端耗時:終端初始化階段的時間裏(網絡資料顯示耗時在1s以上),網絡徹底是處於空閒等待狀態的,浪費時間;
  2. 資源和數據動態拉取,獲取的速度受制於網絡速度;

存在的問題

H5頁面的渲染速度主要取決於三點,渲染速度資源加載速度WebView建立耗時渲染速度取決於兩個方面:git

  1. JS解析效率:若是JS文件較多、自己解析過程複雜就會致使解析速度不快;
  2. 硬件性能:手機硬件性能也是影響渲染速度的重要因素,Android機型碎片化嚴重,性能良莠不齊。

資源加載速度:通常加載一個H5頁面,都會產生多個的網絡請求,包括:github

  1. H5自身的主要URL請求;
  2. 圖片、css文件、H5持有的外部引用的JS文件等,都是一個獨立的HTTP請求。 這些文件都下載完成後才能完成渲染,並且每個請求都是串行的,致使H5加載緩慢。

WebView建立耗時:本地Webview初始化都要很多時間, 首次初始化webview與第二次初始化不一樣,首次會比第二次慢不少。 緣由猜想:webview初始化後,即便webview已經釋放,但一些webview共用的全局服務或資源對象仍沒有釋放,第二次初始化時不須要再初始化這些對象。 H5的流量耗費主要源於:每次使用H5頁面時,都須要從新加載H5頁面。每次加載都會產生較多的網絡請求。 問題總結以下圖: web

解決方案

針對上述問題,主流的解決方案有:數據庫

  1. WebView自帶的H5緩存機制
  2. 預加載
  3. 離線包

緩存機制

H5中有不少的特性,其中就包括離線存儲(也可稱爲緩存機制)這個重要的特性。加入了緩存機制,意味着web應用能夠進行緩存,在沒有網絡的狀況下進行離線訪問。緩存機制帶來的好處包括:segmentfault

  1. 離線訪問:用戶能夠在離線環境下使用
  2. 提升速度:緩存在本地的資源加載速度更快
  3. 減小服務器壓力:只需下載更新過的文件,無需每次加載頁面都進行一次完整的請求流程

到目前爲止,H5的緩存機制一共有六種,分別是:後端

  1. 瀏覽器緩存機制
  2. Dom Storgage(Web Storage)存儲機制
  3. Web SQL Database存儲機制
  4. Indexed Database(IndexedDB)
  5. Application Cache(AppCache)機制
  6. File System API

瀏覽器緩存機制

瀏覽器緩存機制主要是根據HTTP協議頭裏的Cache-ControlExpiresLast-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值的方法。一般,使用內容的散列,最後修改時間戳的哈希值,或簡單地使用版本號。ETagLast-Modified可根據需求使用一個或兩個同時使用。兩個同時使用時,只要知足基中一個條件,就認爲文件沒有更新。

常見用法是Cache-ControlLast-Modified一塊兒使用, ExpiresETag一塊兒使用。一個負責控制緩存的有效時間,一對負責用於緩存失效後判斷是否須要更新。還存在兩種特殊狀況:

  1. 手動刷新時(F5)瀏覽器會認爲緩存已通過期,在請求中加上字段Cache-Control:max-age=0,直接向服務器查詢是否有文件須要更新。
  2. 強制刷新頁面(Ctrl+F5)時瀏覽器會直接忽略本地的緩存,在請求中加上字段Cache-Control:no-cache,直接向服務器請求新的資源。

瀏覽器緩存主要用於靜態資源文件的存儲,例如JS、CSS、字體、圖片等資源,Webview會將緩存的文件記錄及文件內容會存在當前app的data目錄中。WebView內置自動實現,使用默認的CacheMode就能夠實現。也可手動設置緩存模式:

WebSettings webSettings = webView.getSettings();
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
複製代碼
  1. LOAD_CACHE_ONLY:不使用網絡,只讀取本地緩存數據
  2. LOAD_DEFAULT:根據cache-control決定是否從網絡上取數據
  3. LOAD_CACHE_NORMAL:API level 17中已經廢棄,從API level 11開始做用同LOAD_DEFAULT模式
  4. LOAD_NO_CACHE:不使用緩存,只從網絡獲取數據
  5. LOAD_CACHE_ELSE_NETWORK:只要本地有,不管是否過時,或者no-cache,都使用緩存中的數據。本地沒有緩存時才從網絡上獲取。

通常設置爲默認的緩存模式就能夠了。 瀏覽器緩存的優點在於支持Http協議層。不足之處有:

  1. 須要首次加載以後才能產生緩存文件;
  2. 終端設備緩存的空間有限,緩存有可能會被清除;
  3. 緩存使用缺少校驗,有可能被篡改;

各個請求頭詳解:Cache-ControlExpiresLast-ModifiedETag

Dom Storage存儲機制

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,兩者使用方法基本相同,區別在於做用範圍不一樣:前者具備臨時性,用來存儲與頁面相關的數據,它在頁面關閉後沒法使用,後者具有持久性,即保存的數據在頁面關閉後也可使用。

  • sessionStorage 是個全局對象,它維護着在頁面會話(page session)期間有效的存儲空間。只要瀏覽器開着,頁面會話週期就會一直持續。當頁面從新載入(reload)或者被恢復(restores)時,頁面會話也是一直存在的。每在新標籤或者新窗口中打開一個新頁面,都會初始化一個新的會話。
  • localStorage保存的數據是持久性的。當前PAGE關閉(Page Session結束後),保存的數據依然存在。從新打開PAGE,上次保存的數據能夠獲取到。另外,Local Storage 是全局性的,同時打開兩個 PAGE 會共享一份存數據,在一個PAGE中修改數據,另外一個 PAGE 中是能夠感知到的。

Dom Storage的優點在於:存儲空間(5M)大,遠遠大於Cookies(4KB),並且數據存儲在本地無需常常和服務器進行交互,存儲安全、便捷。可用於存儲臨時的簡單數據。做用機制相似於SharedPreference。可是,若是要存儲結構化的數據,可能要藉助JSON了,將要存儲的對象轉爲JSON 串。不太適合存儲比較複雜或存儲空間要求比較大的數據,也不適合存儲靜態的文件。 使用方法以下:

webSettings.setDomStorageEnabled(true);
複製代碼

Web SQL Database存儲機制

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緩存機制

Indexed Database緩存機制

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);
複製代碼

Application Cache緩存機制(AppCache)

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

File System是H5新加入的存儲機制。它爲Web App提供了一個運行在沙盒中的虛擬的文件系統。不一樣WebApp的虛擬文件系統是互相隔離的,虛擬文件系統與本地文件系統也是互相隔離的。Web App在虛擬的文件系統中,經過File System API提供的一組文件與文件夾的操做接口進行文件(夾)的建立、讀、寫、刪除、遍歷等操做。 瀏覽器給虛擬文件系統提供了兩種類型的存儲空間:臨時的和持久性的:

  • 臨時的存儲空間是由瀏覽器自動分配的,但可能被瀏覽器回收;
  • 持久性的存儲空間須要顯示的申請,申請時瀏覽器會給用戶一提示,須要用戶進行確認。持久性的存儲空間是 WebApp 本身管理,瀏覽器不會回收,也不會清除內容。存儲空間大小經過配額管理,首次申請時會一個初始的配額,配額用完須要再次申請。

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的性能問題和解決方案,可是並未深刻分析也沒有寫出詳細的優化教程。具體使用要結合本身的項目進行優化。在此推薦一些文章供你們學習:

相關文章
相關標籤/搜索