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;
}
這裡需要注意幾點:
- 另外還有一個同名不同參方法shouldInterceptRequest (WebView view, String url) ,但是在 API 21已經過時了,因此這裡沒有去覆蓋了;
- shouldInterceptRequest 方法是在非UI執行緒中,因此當需要與 View 互動要小心。但是一想,既然是在非UI執行緒中,那麼我們其實也可以在其中做網路請求,比如獲取 http://www.google.com 的響應資料,將請求的百度替換為 google等;
- 通過攔截請求,也可以做到在網頁內容中刪除某些內容以使在手機上顯示好看,只需將響應中的部分內容刪除之後再交由WebView去顯示即可,這個就跟具體的業務相關了。
JS程式碼與原生代碼互動
在平時與WebVew打交道的過程中,其實我們或多或少都用過如下三種方式或之一來實現WebView中JS與原生代碼的互動:
利用WebViewClient介面回撥方法攔截url
這種方式就是為WebVew新增 WebViewClient 回撥介面,在 shouldOverrideUrlLoading 回撥方法中攔截url,然後解析這個url的協議,如果發現是我們約定好的協議就開始解析引數執行具體邏輯;利用WebChromeClient回撥介面的三個方法攔截訊息
前面基本使用真的程式碼中也已經註釋過了,為WebVew新增 新增 WebChromeClient 介面後,可以攔截JS中的幾個提示方法,也就是幾種樣式的對話方塊,在JS中有三個常用的對話方塊方法:- alert 是彈出警告框,在文本里面加入\n就可以換行。
- confirm 彈出確認框,會返回布林值,通過這個值可以判斷點選時確認還是取消。true表示點選了確認,false表示點選了取消。
- prompt 彈出輸入框,點選確認返回輸入框中的值,點選取消返回null。
這三種對話方塊都是可以在本地攔截到的,那麼這種方法的原理就是攔截這些方法,得到他們的訊息內容,然後解析即可;
通過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方法中去想辦法了,可以有兩種辦法
- 當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);
}
- 頁面載入完之後,直接向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的快取可以分為頁面快取和資料快取。
頁面快取是指載入一個網頁時的html、JS、CSS等頁面或者資源資料,這些快取資源是由於瀏覽器的行為而產生,開發者只能通過配置HTTP響應頭影響瀏覽器的行為才能間接地影響到這些快取資料。他們的索引存放在/data/data/package_name/databases下。他們的檔案存放在/data/data/package_name/cache/xxxwebviewcachexxx下。資料夾的名字在2.x和4.x上有所不同,但都資料夾名字中都包含webviewcache;
資料快取分為兩種: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載入一個本地的錯誤提示頁面。
首先需要編寫一個html檔案,比如error.html,用來出錯的時候展示給使用者,然後將該檔案放置到程式碼根目錄的assets資料夾下;
隨後我們需要複寫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);
}
參考連結: