Android WebView詳解

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-ControlLast=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 分爲 sessionStoragelocalStorage。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進來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章