WebView詳解
WebView使用
WebSettings常用方法:
方法 | 說明 |
---|---|
setAllowFileAccess | 啓用或禁用WebView訪問文件數據 |
setBlockNetworkImage | 是否顯示網絡圖像 |
setBuiltInZoomControls | 設置是否支持縮放 |
setCacheMode | 設置緩衝的模式 |
setDefaultFontSize | 設置默認的字體大小 |
setDefaultTextEncodingName | 設置在解碼時時候用的默認編碼 |
setFixedFontFamily | 設置固定使用的字體 |
setJavaScriptEnabled | 設置是否支持JavaScript |
setLayoutAlgorithm | 設置佈局方式 |
setLightTouchEnabled | 設置用鼠標激活被選項 |
setSupportZoom | 設置是否支持變焦 |
設置將接收各種通知和請求的WebViewClient
方法 | 說明 |
---|---|
doUpdateVisitedHistory | 更新歷史記錄 |
onFormResubmission | 應用程序重新請求網頁數據 |
onLoadResource | 加載指定地址提供的資源 |
onPageFinished | 網頁加載完畢 |
onPageStarted | 網頁開始加載 |
onReceivedError | 報告錯誤信息 |
onScaleChanged | WebView發生改變 |
shouldOverrideUrlLoading | 控制新的連接在當前WebView中打開 |
WebChromeClient常用方法:
方法 | 說明 |
---|---|
onCloseWindow | 關閉WebView |
onCreateWindow | 創建WebView |
onJsAlert | 處理Javascript中的Alert對話框 |
onJsConfirm | 處理Javascript中的Confirm對話框 |
onJsPrompt | 處理Javascript中的Prompt對話框 |
onProgressChanged | 加載進度條改變 |
onReceivedlcon | 網頁圖標更改 |
onReceivedTitle | 網頁Title更改 |
onRequestFocus WebView | 顯示焦點 |
自定義WebView
- 創建和設置WebChromeClient子類。當一些可能影響瀏覽器的用戶界面發生了,例如,進度更新和JavaScript警報送到這裏(見調試任務)調用這個類。
- 創建和設置WebViewClient子類。當影響內容呈現的事情發生是調用這個類,例如,錯誤或表單提交。您也可以攔截的URL加載到這裏(通過
shouldOverrideUrlLoading()
)。 - 修改WebSettings,如以
setJavaScriptEnabled(boolean arg)
方式啓用JavaScript。 - 將Java對象通過
addJavascriptInterface(Object,String)
方法注射到WebView。 這方法允許您將Java對象注入到一個頁面的JavaScript上下文,這樣他們可以通過JavaScript訪問的頁面。
Cookie和窗口管理
可以有自己的緩存和cookie存儲;
addJavascriptInterface(Object,String)
該方法可以讓web頁面調用Android,所以這是危險的。也就是傳說中的webview的注入bug。
頁面導航
當用戶單擊在WebView上的鏈接時,默認行爲是啓動一個處理URL的Android應用。通常默認網頁瀏覽器打開和裝在目的URL。但是你可以爲WebView覆蓋這個行爲,以便在你的WebView上打開鏈接。然後,您可以允許用戶前後瀏覽通過的由您的WebView保留的網頁歷史記錄。
要打開用戶點擊鏈接,只是提供一個WebViewClient爲您的WebView,使用setWebViewClient()。
mWebView.setWebViewClient(new WebViewClient());
WebView 漏洞
UXSS漏洞
可以越過同源策略,獲得任意網頁的Cookie等信息,Android4.4以下都有此問題。
addJavaScriptInterface() 方法漏洞
通過反射機制,js可以直接獲取Runtime,從而執行命令。Android4.2以上,可以通過聲明 @JavascriptInterface
保證安全性,4.2以下不能調用addJavascriptInterface()
方法,需要另謀他法。
- 如果使用https,應進行證書校驗防止訪問的頁面被篡改掛馬;
- 如果使用http,應進行白名單過濾、完整性校驗等防止訪問的頁面被篡改;
- 如果加載本地Html,應將html文件內置在APK中,以及進行對html頁面完整性的校驗;
WebView 優化
頁面加載速度優化
除了 web 頁面自身的 URL 請求,還會有 web 頁面外部引用的JS、CSS、字體、圖片等等都是個獨立的 http 請求。這些請求都是串行的,這些請求加上瀏覽器的解析、渲染時間就會導致 WebView 整體加載時間變長,消耗的流量也對應的真多。
選擇合適的WebView緩存
緩存機制 | 優勢 | 適用場景 |
---|---|---|
瀏覽器緩存機制 | HTTP協議層支持 | 靜態文件的緩存 |
Dom Storage | 較大存儲空間 | 臨時、簡單數據的緩存,Cookies的擴展 |
Web SQL Database | 存儲、管理複雜結構數據 | 用IndexedDB替代,不推薦使用 |
APPCache | 方便構建離線APP | 離線APP、靜態文件緩存,不推薦使用 |
IndexedDB | 存儲任何類型數據、簡單、支持索引 | 結構關係複雜的數據存儲 Web SQL DataBase的替代 |
File System API | 支持文件系統的操作 | 數據適合以文件進行管理的場景,Android系統還不支持 |
* 瀏覽器緩存機制
主要前端負責,Android 端不需要進行特別的配置。
Cache-Control用戶控制文件在本地緩存有效時長。Cache-Control:max-age=600
表示文件在本地緩存600秒,從發出請求算起,如果有請求該資源,瀏覽器不會發送HTTP請求,直接使用本地緩存文件。
Last=Modified是標識文件在服務器上的最新更改時間。下次請求時,如果文件緩存過期,瀏覽器通過 If-Modified-Since 字段帶上這個時間,發送給服務器,由服務器比較時間戳來判斷文件是否有修改。如果沒有則返回 304 告訴瀏覽器使用緩存;如果有修改,則返回200,同時返回最新的文件。
Cache-Control和Last=Modified配合使用。
Cache-Control 還有一個同功能的字段:Expires。Expires 的值一個絕對的時間點,如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在這個時間點之前,緩存都是有效的。Expires 是 HTTP1.0 標準中的字段,Cache-Control 是 HTTP1.1 標準中新加的字段,功能一樣,都是控制緩存的有效時間。當這兩個字段同時出現時,Cache-Control 是高優化級的。
Etag也是和 Last-Modified 一樣,對文件進行標識的字段。不同的是,Etag 的取值是一個對文件進行標識的特徵字串。在向服務器查詢文件是否有更新時,瀏覽器通過 If-None-Match 字段把特徵字串發送給服務器,由服務器和文件最新特徵字串進行匹配,來判斷文件是否有更新。沒有更新回包304,有更新回包200。Etag 和 Last-Modified 可根據需求使用一個或兩個同時使用。兩個同時使用時,只要滿足基中一個條件,就認爲文件沒有更新。
Last-Modified是服務端文件最新修改的時間,If-Modified-Since是客戶端緩存存儲的文件最新修改的時間,客戶端發起請求的時候,帶上If-Modified-Since給服務端去驗證,如果If-Modified-Since和Last-Modified相同,則表示版本相同,返回304,客戶端緩存可以繼續使用;不相同則返回200,刷新客戶端緩存
Dom Storage(Web Storage)存儲機制
配合前端使用,使用時需要打開 DomStorage 開關。
DOM 存儲被設計爲用來提供一個更大存儲量、更安全、更便捷的存儲方法,從而可以代替掉將一些不需要讓服務器知道的信息存儲到 cookies 裏的這種傳統方法。Dom Storage 機制類似 Cookies,但有一些優勢。
Dom Storage 是通過存儲字符串的 Key/Value 對來提供的,並提供 5MB (不同瀏覽器可能不同,分 HOST)的存儲空間(Cookies 才 4KB)。另外 Dom Storage 存儲的數據在本地,不像 Cookies,每次請求一次頁面,Cookies 都會發送給服務器。
DOM Storage 分爲
sessionStorage
和localStorage
。localStorage 對象和 sessionStorage 對象使用方法基本相同,它們的區別在於作用的範圍不同。sessionStorage 用來存儲與頁面相關的數據,它在頁面關閉後無法使用。而 localStorage 則持久存在,在頁面關閉後也可以使用。比如:頁面的操作需要跳到其他頁面操作然後再跳回來,但又想保留之前用戶輸入的信息,就可以這樣臨時存儲數據。
webSettings.setDomStorageEnabled(true);
在使用時,Android端只是設置一下,其他的需要Web開發去實現。
Web SQL Database 存儲機制(不再推薦使用,官方停止維護)
爲了兼容性,在 Android 內嵌 Webview 中,需要通過 Webview 設置接口啓用 SQL Database,同時還要設置數據庫文件的存儲路徑。
webSettings.setDatabaseEnabled(true); final String dbPath = getApplicationContext().getDir("db",Context.MODE_PRIVATE).getPath(); webSettings.setDatabasePath(dbPath)
Application Cache 存儲機制
Application Cache(簡稱 AppCache)似乎是爲支持 Web App 離線使用而開發的緩存機制。它的緩存機制類似於瀏覽器的緩存(Cache-Control 和 Last-Modified)機制,都是以文件爲單位進行緩存,且文件有一定更新機制。但 AppCache 是對瀏覽器緩存機制的補充,不是替代。
AppCache 的原理有兩個關鍵點:manifest 屬性和 manifest 文件。會在HTML頭部定義一個manifest文件,裏面聲明瞭哪些需要緩存的文件。也有5MB的空間限制。
不過根據官方文檔,AppCache 已經不推薦使用了,標準也不會再支持。現在主流的瀏覽器都是還支持 AppCache的,以後就不太確定了。同樣給出 Android 端啓用 AppCache 的代碼。
webSettings.setAppCacheEnabled(true); final String cachePath = getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath(); webSettings.setAppCachePath(cachePath); webSettings.setAppCacheMaxSize(5*1024*1024);
Indexed Database 存儲機制
IndexedDB 也是一種數據庫的存儲機制,但不同於已經不再支持的 Web SQL Database。IndexedDB 不是傳統的關係數據庫,可歸爲 NoSQL 數據庫。IndexedDB 又類似於 Dom Storage 的 key-value 的存儲方式,但功能更強大,且存儲空間更大。
Android 在4.4開始加入對 IndexedDB 的支持,只需打開允許 JS 執行的開關就好了。
webSettings.setJavaScriptEnabled(true);
File System API(Android 暫不支持)
File System API 是 H5 新加入的存儲機制。它爲 Web App 提供了一個虛擬的文件系統,就像 Native App 訪問本地文件系統一樣。由於安全性的考慮,這個虛擬文件系統有一定的限制。Web App 在虛擬的文件系統中,可以進行文件(夾)的創建、讀、寫、刪除、遍歷等操作。很可惜到目前,Android 系統的 WebView 還不支持 File System API。
常用資源預加載
上面介紹的都是二次啓動,首次加載H5頁面可以預加載資源文件,如(JS、CSS、圖片等資源)
mWebView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {
WebResourceResponse response = null;
// 檢查該資源是否已經提前下載完成。我採用的策略是在應用啓動時,用戶在 wifi 的網絡環境下 // 提前下載 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 會自行請求網絡加載資源。
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本地化及延遲加載
直接將常用JS腳本放在assert文件夾中,在WebView調用了onPageFinished()方法進行加載。注意:需要在JS文件中寫入一個JS文件載入完畢的事件。
Android 的 OnPageFinished 事件會在 Javascript 腳本執行完成之後纔會觸發。如果在頁面中使 用JQuery,會在處理完 DOM 對象,執行完 $(document).ready(function() {}); 事件自會後纔會渲染並顯示頁面。而同樣的頁面在 iPhone 上卻是載入相當的快,因爲 iPhone 是顯示完頁面纔會觸發腳本的執行。所以我們這邊的解決方案延遲 JS 腳本的載入,這個方面的問題是需要Web前端工程師幫忙優化的。
使用第三方WebView內核
WebView 的兼容性是個大問題。Android4.4版本Google使用了Chromium
替代Webkit
作爲WebView內核,第三方ROM對原生WebView作出修改。
可以使用第三方內核,比如騰訊瀏覽服務,SDK只有212KB。
WebView與Native交互
Java WebView 與 JavaScript交互
loadUrl(“javascript:method('參數')”)
myWebView.addJavascriptInterface(new JsInteration(), "control”);
public class JsInteration {
@JavascriptInterface
public void toastMessage(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
@JavascriptInterface
public void onSumResult(int result) {
Log.i(LOGTAG, "onSumResult result=" + result);
}
}
調用時:window.control.toastMessage("message");
WebView導致的內存泄露
Android 原生的WebView存在內存泄漏,根治該問題的辦法是:爲WebView開啓一個進程,通過AIDL與主進程通信,用完WebView直接銷燬整個進程。缺點是:涉及到Android進程間通信。
<activity android:name=".webview.WebViewActivity"
android:launchMode="singleTop"
android:process=":remote"
android:screenOrientation="unspecified" />
在關閉時:
@Override
protected void onDestroy() {
super.onDestroy();
System.exit(0);
}
還有一個辦法:使用自定義的WebView,而且是在Java代碼中直接new出來,傳入 applicationContext 來防止activity引用被濫用。
Safe Java-JS WebView Bridge
拋棄使用高風險的WebView addJavascriptInterface方法,通過對js層調用函數及回調函數的包裝,支持異步回調,方法參數支持js所有已知的類型,包括number、string、boolean、object、function。
原理:在頁面加載30%時,把js文件load進來。