1. 程式人生 > >Android WebView 開發使用筆記

Android WebView 開發使用筆記

最近在做的一個專案,涉及到關於WebView的監控,因此花了些時間查閱資料,並通過在頁面載入完成後插入JS程式碼成功監控到了所需的資訊,目前功能已全部完成,因此打算寫下來做個總結,這一篇先記錄一下對WebView的基本使用以及注意事項,下一篇詳細講述一下對WebView監控的實現。

基本使用

主要就是開啟Javascript支援,設定WebViewClient和WebChromeClient等操作,下面程式碼中都有註釋

webview = (WebView) findViewById(R.id.webview);
WebSettings setting = webview.getSettings();
setting.setJavaScriptEnabled(true
);// 如果訪問的頁面中有JavaScript,或者需要通過js和頁面互動,則必須設定支援Javascript webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 重寫這個方法用來指定url,比如只有特定的url才在webview裡開啟,否則還是啟動瀏覽器去開啟,返回true或者false return false; } // 如果要顯示進度條,可以通過onPageStarted和onPageFinished這兩個方法實現,在onPageFinished結束即可
@Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); } }); webview.setWebChromeClient(new WebChromeClient() { // 可以通過攔截JS中的如下3個提示方法,也就是幾種樣式的對話方塊(在JS中有三個常用的對話方塊方法alert、comfirm、prompt),
// 得到他們的訊息內容,然後解析即可。這樣可達到修改彈出框樣式與app風格統一,或者與原生代碼進行互動的目的, @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { return super.onJsAlert(view, url, message, result); } @Override public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { return super.onJsConfirm(view, url, message, result); } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { return super.onJsPrompt(view, url, message, defaultValue, result); @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); } }); //webView.loadUrl(file:///android_asset/test.html);//如果是載入本地檔案則用這種寫法 webview.loadUrl(url); //最後呼叫loadUrl載入網頁

最後別忘了在AndroidManifest.xml中新增網路許可權:

<uses-permission android:name="android.permission.INTERNET" />

攔截請求

WebView 呼叫 loadUrl 後,會首先根據URL獲取響應,然後再將響應顯示到頁面上,但我們可以在獲取響應過程中重新改變請求URL或者直接將響應替換掉,具體的替換可以在 WebViewClient 中shouldInterceptRequest(WebView view, WebResourceRequest request)方法實現:

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    String response = "<html><title>測試攔截</title><body>替換成了我自己的內容</body></html>";
    WebResourceResponse webResourceResponse = 
        new WebResourceResponse("text/html", "utf-8", new ByteArrayInputStream(response.getBytes()));
    return webResourceResponse;
}

這裡需要注意幾點:

  1. 另外還有一個同名不同參方法shouldInterceptRequest (WebView view, String url) ,但是在 API 21已經過時了,因此這裡沒有去覆蓋了;
  2. shouldInterceptRequest 方法是在非UI執行緒中,因此當需要與 View 互動要小心。但是一想,既然是在非UI執行緒中,那麼我們其實也可以在其中做網路請求,比如獲取 http://www.google.com 的響應資料,將請求的百度替換為 google等;
  3. 通過攔截請求,也可以做到在網頁內容中刪除某些內容以使在手機上顯示好看,只需將響應中的部分內容刪除之後再交由WebView去顯示即可,這個就跟具體的業務相關了。

JS程式碼與原生代碼互動

在平時與WebVew打交道的過程中,其實我們或多或少都用過如下三種方式或之一來實現WebView中JS與原生代碼的互動:

  1. 利用WebViewClient介面回撥方法攔截url
    這種方式就是為WebVew新增 WebViewClient 回撥介面,在 shouldOverrideUrlLoading 回撥方法中攔截url,然後解析這個url的協議,如果發現是我們約定好的協議就開始解析引數執行具體邏輯;

  2. 利用WebChromeClient回撥介面的三個方法攔截訊息
    前面基本使用真的程式碼中也已經註釋過了,為WebVew新增 新增 WebChromeClient 介面後,可以攔截JS中的幾個提示方法,也就是幾種樣式的對話方塊,在JS中有三個常用的對話方塊方法:

    • alert 是彈出警告框,在文本里面加入\n就可以換行。
    • confirm 彈出確認框,會返回布林值,通過這個值可以判斷點選時確認還是取消。true表示點選了確認,false表示點選了取消。
    • prompt 彈出輸入框,點選確認返回輸入框中的值,點選取消返回null。
      這三種對話方塊都是可以在本地攔截到的,那麼這種方法的原理就是攔截這些方法,得到他們的訊息內容,然後解析即可;
  3. 通過addJavascriptInterface方法進行新增物件對映
    addJavascriptInterface方法需要兩個引數,第一個引數:Android本地物件;第二個引數:JS程式碼中需要使用的物件。所以這裡其實就相當於一個對映關係,把Android中的本地物件和JS中的物件關聯即可,然後就可以通過JS物件.方法名呼叫到這個Android本地物件中的方法。

Android4.2 之後,為了修復之前的安全漏洞,需要為Android本地物件中的方法加上註解約束@JavascriptInterface,只有加上這個註解的方法才會被JS呼叫到。關於漏洞的嚴重性以及解決的一些辦法,可以參考這篇文章 http://blog.csdn.net/leehong2005/article/details/11808557

注意如果程式碼進行了混淆,則需要在proguard.cfg檔案中新增對JSObject 的處理,這裡不再展開。

public class JSObject {
    @JavascriptInterface
    public void getMsg(String msg) {
        //handleMsg(msg);
    }
}
webview.addJavascriptInterface(new JSObject(), "myObj");

然後就可以在JS程式碼中通過myObj.getMsg(“this is a msg”)呼叫到JSObject中的getMsg方法了。

另外,如果是要在Android程式碼中呼叫JS方法或者注入JS程式碼,則可以通過loadUrl這個方法:

webView.loadUrl("javascript:jsMethod()");
webview.loadUrl("javascript:xxx")

但是,當需要注入一整個JS檔案的時候,就要通過在WebViewClient的onPageFinished方法中去想辦法了,可以有兩種辦法

  1. 當webview載入完之後,讀取整個js檔案中的內容,然後將整個檔案內容以字串的形式,通過webview.loadUrl(“javascript:fileContentString”)注入,程式碼如下所示:
URL url = new URL("js檔案的網路地址");
in = url.openStream();
byte buff[] = new byte[1024];
ByteArrayOutputStream fromFile = new ByteArrayOutputStream();
FileOutputStream out = null;
do {
    int numread = in.read(buff);
    if (numread <= 0) {
        break;
    }
    fromFile.write(buff, 0, numread);
} while (true);
String wholeJS = fromFile.toString();
@Override
public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);
    webview.loadUrl("javascript:" + wholeJS);
}
  1. 頁面載入完之後,直接向webview對應的html中加入
String js = "var script = document.createElement('script');" +
                "    script.src = 'http://locahost:8080/xxx/xxx.js';" +
                "    script.onload = function() { xxx(); };"  + 
                "    document.body.appendChild(script);";
@Override
public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);
    webview.loadUrl("javascript:" + js);
}

WebView快取

WebView的快取可以分為頁面快取和資料快取。

  1. 頁面快取是指載入一個網頁時的html、JS、CSS等頁面或者資源資料,這些快取資源是由於瀏覽器的行為而產生,開發者只能通過配置HTTP響應頭影響瀏覽器的行為才能間接地影響到這些快取資料。他們的索引存放在/data/data/package_name/databases下。他們的檔案存放在/data/data/package_name/cache/xxxwebviewcachexxx下。資料夾的名字在2.x和4.x上有所不同,但都資料夾名字中都包含webviewcache;

  2. 資料快取分為兩種:AppCache和DOM Storage(Web Storage)。他們是因為頁面開發者的直接行為而產生。所有的快取資料都由開發者直接完全地掌控。

AppCache使我們能夠有選擇的快取web瀏覽器中所有的東西,從頁面、圖片到指令碼、css等等。尤其在涉及到應用於網站的多個頁面上的css和JavaScript檔案的時候非常有用。其大小目前通常是5M,在WebView上需要手動開啟(setAppCacheEnabled),並設定路徑(setAppCachePath)和容量(setAppCacheMaxSize)

如果需要儲存一些簡單的用key/value對即可解決的資料,DOM Storage是非常完美的方案。根據作用範圍的不同,有Session Storage和Local Storage兩種,分別用於會話級別的儲存(頁面關閉即消失)和本地化儲存(除非主動刪除,否則資料永遠不會過期)。在WebView中可以手動開啟DOM Storage(setDomStorageEnabled)並設定儲存路徑(setDatabasePath)

假設activity中已經有了一個WebView的例項物件,可以參考如下程式碼最WebSettings 進行設定:

 WebSettings webseting = webview.getSettings();  
 setting.setJavaScriptEnabled(true);
 webseting.setDomStorageEnabled(true);             
 webseting.setAppCacheMaxSize(1024*1024*8); //設定緩衝大小,這裡設的是8M  
 String appCacheDir = getApplicationContext().getCacheDir().getAbsolutePath();
 webseting.setAppCachePath(appCacheDir);  
 webseting.setAllowFileAccess(true);  
 webseting.setAppCacheEnabled(true);  
 webseting.setCacheMode(WebSettings.LOAD_DEFAULT);   

處理WebView中頁面需要的其他許可權

比如從安卓5.0起WebView開始支援WebRTC,如果用WebView載入一個通過WebRTC進行音視訊通訊的頁面,我們會發現即使Manifest當中請求了攝像頭和麥克風許可權,並且系統中也已授予,但還是無法呼叫到,頁面無法顯示內容,這時候我們其實還需要重寫WebChromeClient中的一個方法:

public void onPermissionRequest(PermissionRequest request) {
    request.grant(request.getResources());
}

錯誤處理

由於伺服器各種原因導致出錯,最平常的比如404錯誤,通常情況下瀏覽器會顯示一個錯誤提示頁面,用來提示使用者頁面出錯了,但這個頁面一般比較簡陋,也有可能與我們的頁面風格不搭, 這個時候我們就需要為WebView載入一個本地的錯誤提示頁面。

  1. 首先需要編寫一個html檔案,比如error.html,用來出錯的時候展示給使用者,然後將該檔案放置到程式碼根目錄的assets資料夾下;

  2. 隨後我們需要複寫WebViewClient的onRecievedError方法,該方法傳回了錯誤碼,根據錯誤型別可以進行不同的錯誤分類處理。

webview.setWebViewClient(new WebViewClient(){
    @Override
    public void onReceivedError(WebView view, int errorCode,
        String description, String failingUrl) {
        switch(errorCode) {
            case HttpStatus.SC_NOT_FOUND:
                view.loadUrl("file:///android_assets/error.html");
                break;
        }
    }
});

當然,出錯的時候也可以選擇隱藏掉webview,而顯示native的錯誤處理控制元件,這個也只需要在onReceivedError進行相應的處理即可。

處理返回鍵監聽

public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
        webView.goBack();
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

參考連結: