Android 網路程式設計系列(3)WebView 詳解

顧小魚發表於2017-11-10

前言

在上一篇關於WebView的文章中,介紹了 WebView 的基本使用方法、WebView 頁面處理和歷史記錄以及和 JS 呼叫原生程式碼的相關內容。今天就在上一篇文章的基礎上,補充一些 WebView 的更深入的東西。

主要從如下幾點來進行補充:

  1. 載入本地的 HTML 頁面
  2. WebView 一些方法的講解
  3. WebView 與 WebViewClient、WebChromeClient 的關係和作用
  4. WebViewClient 和 WebChromeCient 的方法介紹
  5. 錯誤碼的處理
  6. 利用 WebView 來下載檔案
  7. 遠端注入漏洞的解決方案

載入本地 HTML 頁面

前一篇文章中說到使用 WebView 需要開啟 Internet 的許可權,這個說法其實是不準確的。開啟許可權只是因為我們使用 loadUrl() 方法來獲取 URL 的內容時需要聯網。當我們選擇載入本地的 HTML 頁面(比如 assets 目錄下的 HTML 檔案或者使用 loadData() 方法載入一段 HTML 程式碼),並不需要網路許可權。

載入 assets 目錄下的 HTML 檔案的方式和載入網頁是一致的,都是使用 WebView 的 loadUrl() 方法。

mWebView.loadUrl("file:///android_asset/index.html");複製程式碼

WebView 部分方法詳解

上篇文章在介紹 WebView 的歷史記錄時,列舉了部分相關的方法並做了簡單的描述。這部分主要補充幾個 load 方法的介紹。

void loadData (String data, String mimeType, String encoding)

載入指定的 data 資料,有時需要載入的不是一個完整的網頁,而是一個 HTML 片段。

data 表示給定編碼的 String 型別 HTML 文字。mimeType 表示 data 的 MIMIE 型別,比如 “text/html”。encoding 表示 data 的編碼格式。

這個方法中有兩個地方需要注意:

  • JavaScript 有同源限制。意味著在此頁面中執行的指令碼無法訪問任何不是通過 data 載入的資料。避免同源限制可以採用 loadDataWithBaseURL() 方法。
  • encoding 指定了 data 是使用 base64 編碼還是使用 URL 編碼。如果 data 是使用 base64 編碼的,encoding 的值一定為 “base64”,如果是其他值的編碼方式(包括 null),則預設使用 ASCII 編碼方式。對於超出 ASCII 範圍的字元比如 ‘#’,‘%’,'\','?',應該轉化為 ”%+原字元的十六進位制值“ 如 ‘%23’、‘%25’、‘%27’、‘%3f’。

示例如下:

String htmlString = "<html><title>this is title</title><body>this is web content.</body></html>";
mWebView.loadData(htmlString, "text/html","utf-8");複製程式碼

void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)

這個方法比上面那個方法只多了兩個引數,一個是 baseUrl,一個是 historyUrl。baseUrl 指定了 data 資料的基準地址。我們知道,在 HTML 程式碼中通常會有超連結或者圖片,而這些連結或者圖片地址通常使用的都是相對路徑,如果沒有一個基準,WebView 就無法訪問到這些資源。

下面我們在 assets 目錄下模擬網站資源的相對路徑,來幫助我們更好地理解:

assets 目錄下建立了 web 目錄,裡面有一張圖片名為 logo.png

String htmlStr = "this is our logo:<img src="/logo.png"/>"
mWebView.loadData(htmlStr, "text/html","utf-8");
mWebView.loadDataWithBaseUrl("file:///android_asset/web",htmlstr,"text/html","utf-8",null);複製程式碼

使用 loadData() 方法無法載入圖片,只有使用 loadDataWithBaseUrl() 方法才能載入。

void loadUrl(String url)

載入指定 URL 的內容。

void loadUrl(String url, Map additionalHttpHeaders)

載入指定的 URL 的內容,並攜帶 http header 資料。

void reload()

重新載入當前頁面。頁面中所有資源會重新載入。

WebSettings getSettings()

獲取對這個 WebView 進行設定的 WebSettings 物件。

WebSettings 是管理 WebView 設定狀態的類。當首次建立 WebView 時,它會獲得一組預設設定。這些預設設定可以通過 WebSettings 的任何 getter 方法呼叫返回。從 WebView.getSettings() 獲取的WebSettings 物件與 WebView 的使用壽命相關。如果 WebView 已被破壞,WebSettings 上的任何方法呼叫都將丟擲一個 IllegalStateException 異常。

上一篇文章中提到 WebView 本身不支援 JavaScript,需要通過 WebSettings 來設定,即:

mWebView.getSettings().setJavaScriptEnabled(true);複製程式碼

這裡介紹其他幾個常用的 WebSettings 的 setter 方法,來對 WebView 進行設定。

setCacheMode(int mode)
設定是否支援快取及快取模式,預設值為 LOAD_DEFAULT。
setDefaultFontSize(int size)
設定預設的字型大小。有效值在 1~72 之間,預設值為 16。
setSupportZoom(boolean support)
設定是否支援縮放,預設支援。
setDisplayZoomControls(boolean enabled)
設定是否顯示縮放按鈕。預設值為 true。

WebView 與 WebViewClient、WebChromeClient 的關係和作用

在上篇文章中介紹了通過覆蓋 WebViewClient 的shouldOverrideUrlLoading() 方法來使用當前應用展示頁面,而不是通過手機瀏覽器來開啟。這裡就係統地講講 WebView 和 WebViewClient、WebChromeClient 的關係以及它們各自的作用。

WebView 在載入網頁時會產生各種事件,並回撥給應用程式以便在網頁載入過程進行一些額外的處理。如果所有的工作都由 WebView 本身來完成,那麼它的工作量就太大了,因此 Android 中引入了WebVIewClient 和 WebChromeClient 類來幫助 WebView 分擔這些事件處理的回撥。

WebView 負責載入網頁以及網頁渲染。
WebViewClient 的主要職責是幫助 WebView 處理各種通知、回撥事件。
WebChromeClient 的作用是監聽網頁載入進度以及對網頁標題,網頁圖示、JS 對話方塊進行處理等等。

WebViewClient 和 WebChromeCient 的方法介紹

WebViewClient 部分方法說明
//網頁開始載入時的回撥函式
void onPageStarted(WebView view, String url, Bitmap favicon)
//網頁載入結束時的回撥函式
void    onPageFinished(WebView view, String url)
//網頁載入出錯時的回撥函式
void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
//選擇使用應用程式進行頁面展示時需要覆蓋該方法
boolean shouldOverrideUrlLoading(WebView view, String url)複製程式碼
WebChromeClient 部分方法說明
//接收到頁面 title 時回撥,獲取到 title 可以設定到應用的 ActionBar 上。
void onReceivedTitle(WebView view, String title)
//接收到頁面的圖示時回撥
void onReceivedIcon(WebView view, Bitmap icon)
//當頁面載入進度發生變化時回撥,可以設定一個 ProgressBar 來顯示載入進度
void onProgressChanged(WebView view, int newProgress)
//當頁面彈出 Javascript 警報對話方塊時呼叫
//客戶端可以設定是否對這個對話方塊進行處理
boolean    onJsAlert(WebView view, String url, String message, JsResult result)
//當頁面彈出 Javascript 確認對話方塊時呼叫
//客戶端可以設定是否對這個對話方塊進行處理
boolean    onJsConfirm(WebView view, String url, String message, JsResult result)
//當頁面彈出 Javascript 提示對話方塊時呼叫
//客戶端可以設定是否對這個對話方塊進行處理
boolean    onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)複製程式碼

錯誤碼的處理

在瀏覽網頁時經常會遇到 404 錯誤,也就是訪問的頁面不存在,這裡就簡單介紹一下如何處理 WebView 中的 404 錯誤。對錯誤碼的處理需要覆蓋 WebViewClient 類中的 onReceiveError() 方法,對於 404 錯誤,有兩個常用的處理方法:

第一,載入一個 assets 目錄下的寫好的 404 頁面

class MyWebViewClient extends WebViewClient{
    @Override
    public void onReceivedError(WebView view, int errorCode, 
        String description, String failingUrl) {
        view.loadUrl("file:///android_asset/404.html");
    }
}
mWebView.setWebViewClient(new MyWebViewClient());複製程式碼

第二,使用 Android 中的 ImageView 或者 TextVIew 來顯示錯誤資訊。

TextView tvError = (TextView) findViewById(R.id.tv_error);
class MyWebViewClient extends WebViewClient{
    @Override
    public void onReceivedError(WebView view, int errorCode, 
        String description, String failingUrl) {
        mWebView.setVisibility(View.GONE);
        tvError.setVisibility(View.VISIBLE);
        tvError.setText("something goes wrong");
    }
}
mWebView.setWebViewClient(new MyWebViewClient());複製程式碼

利用 WebView 下載檔案

使用 WebView 進行檔案下載同樣有兩種常用的方法,一種是使用隱式 Intent,呼叫系統瀏覽器進行下載任務;一種是獲取到下載檔案的 URL,建立執行緒進行非同步下載。

我們這裡就簡單介紹一下使用隱式 Intent 的方法來下載檔案,第二種方法將在後面一篇關於 HttpUrlConnection 的文章中進行介紹。

WebView 有一個 setDownloadListener() 方法,當 URL 的內容不能被 WebView 引擎渲染時(檔案),應該被下載。需要新建一個 DownloadListener 的實現類。在該實現類的 onDownloadStart() 方法中,可以獲取到當前下載檔案的 URL。之後就可以通過隱式 Intent 來呼叫系統瀏覽器進行下載。

class MyDownloadListener implements DownloadListener{

    @Override
    public void onDownloadStart(String url, String userAgent,
        String contentDisposition,
        String mimetype, long contentLength) {
        if(url.endsWith(".apk")){
            Uri uri = Uri.parse(url);
            Intent i = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(i);
        }
    }
}
mWebView.loadUrl("http://www.wandoujia.com/");
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setDownloadListener(new MyDownloadListener());複製程式碼

上述程式碼實現了在豌豆莢網站中下載 apk 檔案的效果。

遠端注入漏洞的解決方案

上篇文章中講到了在 Android 4.2 版本之前使用 addJavascriptInterface() 方法會存在安全隱患,攻擊者可以通過反射機制來對客戶端進行攻擊。那麼在 4.2 之前,這個漏洞有沒有什麼解決方案呢?

答案是有的,在網上看了一些資料,找到了對應的方案。在上面的 WebChromeClient 的方法介紹時提到了一個 onJsPrompt() 方法,這個方法在 JS 呼叫 prompt 方法時會在本地回撥。通過這個方法,JS 能把資訊(文字)傳遞到 Java,而 Java 也能把資訊(文字)傳遞到 JS 中。客戶端和網頁前端的開發者之間協商好一個小型的協議,通過 onJsPrompt() 將遠端呼叫所需的資訊按照協議封裝成文字發給客戶端,客戶端根據協議解析這段文字,提取出所需的資訊,利用反射機制進行本地的方法呼叫,如果有返回值就將返回值通過onJsPrompt() 傳遞給 JS。就能夠實現 JS 遠端呼叫 Java 程式碼了。

這樣的解決方案我也只是一知半解,所以只能介紹到這裡,在參考資料中會有更加詳細的介紹。

結束語

這篇文章結束後,對於 WebView 的一些常見的方法和這些方法背後的原理都略作介紹。由於是在學習階段,很多知識和原理並不能做到多麼深入,但是通過寫這兩篇關於 WebView 的文章,去看了很多的部落格,研究了一番官方文件,收穫還是非常大的。接下來就要去學習 HttpUrlConnection 的相關用法,下篇文章見。

參考文章

以下是在學習過程中看的一些部落格,不斷向這些作者學習,努力深入知識點,然後寫出儘量高質量的部落格。

blog.csdn.net/leehong2005…
Android WebView 的 Js 物件注入漏洞解決方案
blog.csdn.net/typename/ar…
blog.csdn.net/typename/ar…
blog.csdn.net/typename/ar…
Android WebView 開發詳解(共3篇)
www.jianshu.com/p/93cea79a2…
JS 與 WebView 互動存在的一些問題

相關文章