android 原生使用WebView嵌入H5頁面 Hybrid開發
一、性能問題
- android webview 里H5加載速度慢
- 網絡流量大
1、H5頁面加載速度慢
-
渲染速度慢
js解析效率
js本身的解析過程復雜、解析速度不快,前端頁面設計較多的js代碼文件
手機硬件設備的性能
機型多,硬件性能不一
-
資源加載慢
H5頁面的資源多
網絡請求數量多
? H5頁面所有資源都需要從網絡請求
二、解決方案
- webView組件本身的緩存機制
- 資源預加載
- 資源攔截
webView組件本身的緩存機制
-
WebView自帶的緩存機制有5種:瀏覽器 緩存機制
-
Application Cache 緩存機制
-
Dom Storage 緩存機制
-
Web SQL Database 緩存機制
-
Indexed Database 緩存機制
-
File System 緩存機制(H5頁面新加入的緩存機制,雖然Android WebView暫時不支持,但會進行簡單介紹)
(1) Cache-Control: 用于控制文件在本地緩存的有效時長
? eg:
Cache-Control:max-age=600
,則表示文件在本地應該緩存,且有效時長是600秒(從發出請求算起)。在接下來600秒內,如果有請求這個資源,瀏覽器不會發出 HTTP 請求,而是直接使用本地緩存的文件。(2)Expires: 與Cache-Control 功能相同,即控制緩存的有效時間
? cache-Control 優先級高
(3) Last-Modified: 標識文件在服務器上的最新更新時間
? 下次請求時,如果文件緩存過期,瀏覽器通過 If-Modified-Since 字段帶上這個時間,發送給服務器,由服務器比較時間戳來判斷文件是否有修改。如果沒有修改,服務器返回304告訴瀏覽器繼續使用緩存;如果有修改,則返回200,同時返回最新的文件。
(4) Etag:功能同Last-Modified, 即標識文件在服務器上的最新更新時間
優點:支持Http協議層
缺點:緩存文件需要首次加載后才會產生;瀏覽器緩存的存儲空間有限,緩存有被清楚的可能,緩存的文件沒有校驗
可以緩存講臺文件資源,如JS,CSS、文本、圖片等,webView 會將緩存的文件記錄及文件內容會存在當前appde data目錄中,
Application Cache 緩存機制
以文件為單位進行緩存,且文件有一定更新機制(類似于瀏覽器緩存機制)
AppCache : manifest 屬性和manifest文件
<!DOCTYPE html>
<html manifest="demo_html.appcache">
// HTML 在頭中通過 manifest 屬性引用 manifest 文件
// manifest 文件:就是上面以 appcache 結尾的文件,是一個普通文件文件,列出了需要緩存的文件
// 瀏覽器在首次加載 HTML 文件時,會解析 manifest 屬性,并讀取 manifest 文件,獲取 Section:CACHE MANIFEST 下要緩存的文件列表,再對文件緩存
<body>
...
</body>
</html>
// 原理說明如下:
AppCache 在首次加載生成后,也有更新機制。被緩存的文件如果要更新,需要更新 manifest 文件
因為瀏覽器在下次加載時,除了會默認使用緩存外,還會在后臺檢查 manifest 文件有沒有修改(byte by byte)
發現有修改,就會重新獲取 manifest 文件,對 Section:CACHE MANIFEST 下文件列表檢查更新
manifest 文件與緩存文件的檢查更新也遵守瀏覽器緩存機制
如用戶手動清了 AppCache 緩存,下次加載時,瀏覽器會重新生成緩存,也可算是一種緩存的更新
AppCache 的緩存文件,與瀏覽器的緩存文件分開存儲的,因為 AppCache 在本地有 5MB(分 HOST)的空間限制
// 通過設置WebView的settings來實現WebSettings settings = getSettings();String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";settings.setAppCachePath(cacheDirPath);// 1. 設置緩存路徑settings.setAppCacheMaxSize(20*1024*1024);// 2. 設置緩存大小settings.setAppCacheEnabled(true);// 3. 開啟Application Cache存儲機制
Application 只調用一次 WebSettings.setAppCachePath() 和
WebSettings.setAppCacheMaxSize()
Dom Storage 緩存機制
通過存儲字符串的Key-Value 對來提供
sessionStorage
:具備臨時性,即存儲與頁面相關的數據,它在頁面關閉后無法使用
localStorage
:具備持久性,即保存的數據在頁面關閉后也可以使用。
特點:
- 存儲空間大(5MB): 存儲空間對于不同瀏覽器不同,如Cookies才4KB
- 存儲安全、便捷:Dom Storage 存儲的數據在本地,不需要經常和服務器進行交互。
應用:
? 存儲臨時、簡單的數據
? 類似于sharedPreference機制
// 通過設置 `WebView`的`Settings`類實現WebSettings settings = getSettings();settings.setDomStorageEnabled(true);// 開啟DOM storage
Web SQL Database 緩存機制
? 基于SQL的數據庫存儲機制
? 充分利用數據庫的優勢,可方便對數據進行增加、刪除、修改、查詢。
應用:
? 存儲適合數據庫的結構化數據
// 通過設置WebView的settings實現WebSettings settings = getSettings();String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";settings.setDatabasePath(cacheDirPath);// 設置緩存路徑settings.setDatabaseEnabled(true);// 開啟 數據庫存儲機制
官方說明,Web SQL Database 存儲機制不在推薦使用(不在維護),取而代之的是IndexedDB緩存機制。
IndexedDB緩存機制
屬于NoSQL數據庫,通過存儲字符串的Key-Value對來提供,類似于Dom Storage 存儲機制的key-value存儲方式
優點:
-
功能強大、使用簡單
通過數據庫的事務(tranction)機制進行數據操作
可對對象任何屬性生成索引,方便查詢
-
存儲空間大
較大的存儲空間,默認推薦250MB(分HOST)
-
使用靈活
以key-value的方式存存取對象,可以是任何類型值或對象,包括二級制
異步的API調用,避免造成等待而影響體驗
存儲 復雜、數據量大的結構化數據
// 通過設置WebView的settings實現
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);// 只需設置支持JS就自動打開IndexedDB存儲機制// Android 在4.4開始加入對 IndexedDB 的支持,只需打開允許 JS 執行的開關就好了。
資源預加載
? 提前加載將使用的H5頁面,即提前構建緩存
? 預加載webView 、預加載H5資源
預加載WebView對象
? 提前初始化一個webView對象,后續復用這個webView對象
預加載H5資源
- 在應用啟動、初始化第一個
WebView
對象時,直接開始網絡請求加載H5
頁面 - 后續需打開這些H5頁面時就直接從該本地對象中獲取
自身構建緩存
為了有效解決 Android WebView
的性能問題,除了使用 Android WebView
自身的緩存機制,還可以自己針對某一需求場景構建緩存機制。
實現方法:
- 事先將更新頻率較低、常用 & 固定的
H5
靜態資源 文件(如JS
、CSS
文件、圖片等) 放到本地 - 攔截
H5
頁面的資源網絡請求 并進行檢測 - 如果檢測到本地具有相同的靜態資源 就 直接從本地讀取進行替換 而 不發送該資源的網絡請求 到 服務器獲取
實現方法:
假設現在需要攔截一個圖片的資源并用本地資源進行替代
mWebview.setWebViewClient(new WebViewClient() {// 重寫 WebViewClient 的 shouldInterceptRequest ()// API 21 以下用shouldInterceptRequest(WebView view, String url)// API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)// 下面會詳細說明// API 21 以下用shouldInterceptRequest(WebView view, String url)@Overridepublic WebResourceResponse shouldInterceptRequest(WebView view, String url) {// 步驟1:判斷攔截資源的條件,即判斷url里的圖片資源的文件名if (url.contains("logo.gif")) {// 假設網頁里該圖片資源的地址為:http://abc.com/imgage/logo.gif// 圖片的資源文件名為:logo.gifInputStream is = null;// 步驟2:創建一個輸入流try {is =getApplicationContext().getAssets().open("images/abc.png");// 步驟3:獲得需要替換的資源(存放在assets文件夾里)// a. 先在app/src/main下創建一個assets文件夾// b. 在assets文件夾里再創建一個images文件夾// c. 在images文件夾放上需要替換的資源(此處替換的是abc.png圖片)} catch (IOException e) {e.printStackTrace();}// 步驟4:替換資源WebResourceResponse response = new WebResourceResponse("image/png","utf-8", is);// 參數1:http請求里該圖片的Content-Type,此處圖片為image/png// 參數2:編碼類型// 參數3:存放著替換資源的輸入流(上面創建的那個)return response;}return super.shouldInterceptRequest(view, url);}
?
// API 21 以上用shouldInterceptRequest(WebView view, WebResourceRequest request)@TargetApi(Build.VERSION_CODES.LOLLIPOP)@Overridepublic WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {// 步驟1:判斷攔截資源的條件,即判斷url里的圖片資源的文件名if (request.getUrl().toString().contains("logo.gif")) {// 假設網頁里該圖片資源的地址為:http://abc.com/imgage/logo.gif// 圖片的資源文件名為:logo.gifInputStream is = null;// 步驟2:創建一個輸入流try {is = getApplicationContext().getAssets().open("images/abc.png");// 步驟3:獲得需要替換的資源(存放在assets文件夾里)// a. 先在app/src/main下創建一個assets文件夾// b. 在assets文件夾里再創建一個images文件夾// c. 在images文件夾放上需要替換的資源(此處替換的是abc.png圖片} catch (IOException e) {e.printStackTrace();}// 步驟4:替換資源WebResourceResponse response = new WebResourceResponse("image/png","utf-8", is);// 參數1:http請求里該圖片的Content-Type,此處圖片為image/png// 參數2:編碼類型// 參數3:存放著替換資源的輸入流(上面創建的那個)return response;}return super.shouldInterceptRequest(view, request);}
});
}