一、WebViewClient
1、概述
前面我們雖然實現了交互,但可能我們會有一個很簡單的需求,就是在開始加載網頁的時候顯示進度條,加載結束以後隱藏進度條,這要怎麼做?
這些簡單的需求,Android開發的老人們肯定都已經想到了,這些有關各種事件的回調都被封裝在WebViewClient類中了,在WebViewClient中有各種的回調方法,就是在某個事件發生時供我們監聽
使用方法如下:
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public void onPageStarted(WebView view, String url, Bitmap favicon) {
-
super.onPageStarted(view, url, favicon);
-
Log.d(TAG,"onPageStarted");
-
}
-
-
@Override
-
public void onPageFinished(WebView view, String url) {
-
super.onPageFinished(view, url);
-
Log.d(TAG,"onPageFinished");
-
}
-
});
直接調用WebView.setWebViewClient方法即可設置WebViewClient回調,這裏重寫的兩個函數,onPageStarted會在WebView開始加載網頁時調用,onPageFinished會在加載結束時調用。這兩個函數就可以完成我們開篇時的需求:在開始加載時顯示進度條,在結束加載時隱藏進度條。
2、WebViewClient中函數概述
在WebViewClient中除了上面我們列舉出的onPageStarted、onPageFinished還有很多其它函數,分別是:
-
-
-
-
public void onPageStarted(WebView view, String url, Bitmap favicon)
-
-
-
-
public void onPageFinished(WebView view, String url)
-
-
-
-
public boolean shouldOverrideUrlLoading(WebView view, String url)
-
-
-
-
public void onReceivedError(WebView view, int errorCode,String description, String failingUrl)
-
-
-
-
public void onReceivedSslError(WebView view, SslErrorHandler handler,SslError error)
-
-
-
-
public WebResourceResponse shouldInterceptRequest(WebView view,
-
String url) {
-
return null;
-
}
上面的方法比較多,我們一個個來看
3、WebViewClient之onPageStarted與onPageFinished
onPageStarted:通知主程序頁面當前開始加載。該方法只有在加載main frame時加載一次,如果一個頁面有多個frame,onPageStarted只在加載main
frame時調用一次。也意味着若內置frame發生變化,onPageStarted不會被調用,如:在iframe中打開url鏈接。
onPageFinished:通知主程序頁面加載結束。方法只被main frame調用一次。
我們就利用上面的想法來舉個例子:開始加載時顯示加載圓圈,結束加載時隱藏加載圓圈
-
public class MyActivity extends Activity {
-
private WebView mWebView;
-
-
private ProgressDialog mProgressDialog;
-
private String TAG = "qijian";
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
mWebView = (WebView)findViewById(R.id.webview);
-
mProgressDialog = new ProgressDialog(this);
-
mWebView.getSettings().setJavaScriptEnabled(true);
-
-
mWebView.loadUrl("http://blog.csdn.net/harvic880925");
-
mWebView.setWebViewClient(new WebViewClient(){
-
-
@Override
-
public boolean shouldOverrideUrlLoading(WebView view, String url) {
-
mWebView.loadUrl(url);
-
return true;
-
}
-
-
@Override
-
public void onPageStarted(WebView view, String url, Bitmap favicon) {
-
super.onPageStarted(view, url, favicon);
-
mProgressDialog.show();
-
}
-
-
@Override
-
public void onPageFinished(WebView view, String url) {
-
super.onPageFinished(view, url);
-
mProgressDialog.hide();
-
}
-
});
-
}
-
}
效果圖如下:
從效果圖中可以明顯看出,在加載頁面的時候會顯示圓形加載框,在加載成功以後會隱藏加載框。
4、WebViewClient之shouldOverrideUrlLoading
該函數的完整聲明如下:
-
public boolean shouldOverrideUrlLoading(WebView view, String url)
這個函數會在加載超鏈接時回調過來;所以通過重寫shouldOverrideUrlLoading,可以實現對網頁中超鏈接的攔截;
返回值是boolean類型,表示是否屏蔽WebView繼續加載URL的默認行爲,因爲這個函數是WebView加載URL前回調的,所以如果我們return true,則WebView接下來就不會再加載這個URL了,所有處理都需要在WebView中操作,包含加載。如果我們return false,則系統就認爲上層沒有做處理,接下來還是會繼續加載這個URL的。WebViewClient默認就是return
false的:
-
public boolean shouldOverrideUrlLoading(WebView view, String url) {
-
return false;
-
}
(1)、如何在WebView中加載在線網址
在上一篇中,我們提到,如果要在WebView中加載在線網址,必須重寫WebViewClient
現在網上鋪天蓋地的都是重寫shouldOverrideUrlLoading來將URL加載進WebView,但在用多了WebView以後會發現,直接下面這樣寫,就可以實現在WebVIew中加載網頁:
-
public class MyActivity extends Activity {
-
private WebView mWebView;
-
-
private ProgressDialog mProgressDialog;
-
private String TAG = "qijian";
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
mWebView = (WebView)findViewById(R.id.webview);
-
mProgressDialog = new ProgressDialog(this);
-
mWebView.getSettings().setJavaScriptEnabled(true);
-
-
mWebView.setWebViewClient(new WebViewClient());
-
-
mWebView.loadUrl("http://blog.csdn.net/harvic880925");
-
}
-
}
效果圖如下:
從效果圖中可以看出即僅僅設置WebViewClient對象,使用它的默認回調就可以實現在WebView中加載在線URL了:
-
mWebView.setWebViewClient(new WebViewClient());
(2)、shouldOverrideUrlLoading用途
由於每次超鏈接在加載前都會先走shouldOverrideUrlLoading回調,所以我們如果想攔截某個URL,將其轉換成其它URL可以在這裏做。
比如,我們攔截所有包含“blog.csdn.net”的地址,將其替換成”www.baidu.com”:
效果圖如下:
代碼如下:
-
public class MyActivity extends Activity {
-
private WebView mWebView;
-
-
private ProgressDialog mProgressDialog;
-
private String TAG = "qijian";
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
mWebView = (WebView)findViewById(R.id.webview);
-
mProgressDialog = new ProgressDialog(this);
-
mWebView.getSettings().setJavaScriptEnabled(true);
-
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public boolean shouldOverrideUrlLoading(WebView view, String url) {
-
if (url.contains("blog.csdn.net")){
-
view.loadUrl("http://www.baidu.com");
-
}else {
-
view.loadUrl(url);
-
}
-
return true;
-
}
-
});
-
-
mWebView.loadUrl("http://blog.csdn.net/harvic880925");
-
}
-
}
最關鍵的位置在:
-
public boolean shouldOverrideUrlLoading(WebView view, String url) {
-
if (url.contains("blog.csdn.net")){
-
view.loadUrl("http://www.baidu.com");
-
}else {
-
view.loadUrl(url);
-
}
-
return true;
-
}
如果在當前webview加載的url中包含“blog.csdn.net”,則將其轉換成”www.baidu.com”
這裏需要非常注意的是:如果我們在shouldOverrideUrlLoading中return true,就表示告訴系統我們已經攔截了URL並做處理,不需要再觸發系統默認的行爲()在WebView中加載URL;所以對於其它URL我們需要在else裏重新調用view.loadUrl(url)來加載;不然WebView將會白屏,因爲這個URL根本就沒有加載進WebView,在shouldOverrideUrlLoading這就被我們攔截掉了。
那麼問題來了,在我們return true了以後,WebView還會請求網絡嗎?我們來抓下請求:
從請求中可以看到,我們雖然攔截了”http://blog.csdn.net/harvic880925“但是仍然還是會請求網絡的。只是請求以後結果並沒有通過WebView加載。
那問題來了,如果我們return false呢,如果我們return false的話,是不需要else語句的,因爲系統默認會加載這個URL,所以上面的語句與下面的意義相等:
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public boolean shouldOverrideUrlLoading(WebView view, String url) {
-
if (url.contains("blog.csdn.net")){
-
view.loadUrl("http://www.baidu.com");
-
}
-
return false;
-
}
-
}
所以相對而言,我們使用return false好像更方便,只需要對需要攔截的URL進行攔截,攔截以後,讓WebView處理默認操作即可。
所以結論來了:
在利用shouldOverrideUrlLoading來攔截URL時,如果return true,則會屏蔽系統默認的顯示URL結果的行爲,不需要處理的URL也需要調用loadUrl()來加載進WebVIew,不然就會出現白屏;如果return false,則系統默認的加載URL行爲是不會被屏蔽的,所以一般建議大家return false,我們只關心我們關心的攔截內容,對於不攔截的內容,讓系統自己來處理即可。
5、WebViewClient之onReceivedError
onReceivedError的完整聲明如下:
-
public void onReceivedError(WebView view, int errorCode,String description, String failingUrl)
加載錯誤的時候會產生這個回調,在其中可做錯誤處理,比如我們可以加載一個錯誤提示頁面
這裏有四個參數:
- WebView view:當前的WebView實例
- int errorCode:錯誤碼
- String description:錯誤描述
- String failingUrl:當前出錯的URL
我們可以先寫一個錯誤提示的本地頁面:(error.html)
-
<!DOCTYPE html>
-
<html lang="en">
-
<head>
-
<meta charset="UTF-8">
-
<title>Title</title>
-
</head>
-
<body>
-
<h1 id="h">啊哈,出錯了……</h1>
-
</body>
-
</html>
然後在加載返回錯誤時,重新加載錯誤頁面
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
-
super.onReceivedError(view, errorCode, description, failingUrl);
-
mWebView.loadUrl("file:///android_asset/error.html");
-
}
-
});
效果圖如下:
源碼在文章底部給出
6、WebViewClient之onReceivedSslError
我們知道HTTPS協議是通過SSL來通信的,所以當使用HTTPS通信的網址(以https://開頭的網站)出現錯誤時,就會通過onReceivedSslError回調通知過來,它的函數聲明爲:
-
-
-
-
public void onReceivedSslError(WebView view, SslErrorHandler handler,SslError error)
- WebView view:當前的WebView實例
- SslErrorHandler handler:當前處理錯誤的Handler,它只有兩個函數SslErrorHandler.proceed()和SslErrorHandler.cancel(),SslErrorHandler.proceed()表示忽略錯誤繼續加載,SslErrorHandler.cancel()表示取消加載。在onReceivedSslError的默認實現中是使用的SslErrorHandler.cancel()來取消加載,所以一旦出來SSL錯誤,HTTPS網站就會被取消加載了,如果想忽略錯誤繼續加載就只有重寫onReceivedSslError,並在其中調用SslErrorHandler.proceed()
- SslError error:當前的的錯誤對象,SslError包含了當前SSL錯誤的基本所有信息,大家自己去看下它的方法吧,這裏就不再展開了。
示例(1)、默認加載SSL出錯的網站——出現空白頁面
我們先舉個例子來看下默認情況下加載SSL有錯的網站,WebView的表現是怎樣的:(12306是通過Https協議來傳輸的,但是它的SSL證書是有問題的,所以我們就以12306網站爲例)
-
public class MyActivity extends Activity {
-
private WebView mWebView;
-
-
private ProgressDialog mProgressDialog;
-
private String TAG = "qijian";
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
mWebView = (WebView)findViewById(R.id.webview);
-
mProgressDialog = new ProgressDialog(this);
-
mWebView.getSettings().setJavaScriptEnabled(true);
-
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-
super.onReceivedSslError(view, handler, error);
-
Log.e(TAG,"sslError:"+error.toString());
-
}
-
-
mWebView.loadUrl("https://www.12306.cn/");
-
}
-
}
在這裏僅僅重寫onReceivedSslError,並調用super.onReceivedSslError(view, handler, error);來調用默認的處理方式,然後把錯誤日誌打出來:
錯誤日誌如下:
示例(2)、使用SslErrorHandler.proceed()來繼續加載
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-
-
-
handler.proceed();
-
Log.e(TAG,"sslError:"+error.toString());
-
}
-
});
這裏做了兩個改變:
第一:註釋掉super.onReceivedSslError(view, handler, error);,取消系統的默認行爲,我們看源碼,可以發現在WebViewClient中onReceivedSslError的默認實現是這樣的:
-
public void onReceivedSslError(WebView view, SslErrorHandler handler,
-
SslError error) {
-
handler.cancel();
-
}
所以默認是取消繼續加載的,所以我們必須註釋掉super.onReceivedSslError(view, handler, error)來取消這個默認行爲!
第二:調用handler.proceed();來忽略錯誤繼續加載頁面。
所以此時的效果圖爲:
示例(3):在SSL發生錯誤時,onReceivedError會被回調嗎?——不會
大家可能還有一個疑問:當SSL發生錯誤時,我們說會回調onReceivedSslError,我們前面還說了一個出錯時會回調的函數:onReceivedError,那麼問題來了,當出現SSL錯誤時onReceivedError會被回調嗎?
答案是不會的,我們來做個實驗:
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-
-
handler.proceed();
-
Log.e(TAG,"sslError:"+error.toString());
-
}
-
-
@Override
-
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
-
super.onReceivedError(view, errorCode, description, failingUrl);
-
Log.e(TAG,"onReceivedError:"+errorCode+" "+description);
-
}
-
});
在代碼中我們同時使用onReceivedSslError和onReceivedError來接收錯誤,看下在出錯時,哪個函數中會打出LOG,結果的日誌如下:
從日誌中明顯可以看出,只有onReceivedSslError的接收日誌,所以在SSL出錯時,是不會觸發onReceivedError回調的
所以對於onReceivedSslError結論來了:
當出現SSL錯誤時,WebView默認是取消加載當前頁面,只有去掉onReceivedSslError的默認操作,然後添加SslErrorHandler.proceed()才能繼續加載出錯頁面
當HTTPS傳輸出現SSL錯誤時,錯誤會只通過onReceivedSslError回調傳過來
7、WebViewClient之shouldInterceptRequest
在每一次請求資源時,都會通過這個函數來回調,比如超鏈接、JS文件、CSS文件、圖片等,也就是說瀏覽器中每一次請求資源時,都會回調回來,無論任何資源!但是必須注意的是shouldInterceptRequest函數是在非UI線程中執行的,在其中不能直接做UI操作,如果需要做UI操作,則需要利用Handler來實現,該函數聲明如下:
-
public WebResourceResponse shouldInterceptRequest(WebView view,
-
String url) {
-
return null;
-
}
該函數會在請求資源前調用,我們可以通過返回WebResourceResponse的處理結果來讓WebView直接使用我們的處理結果。如果我們不想處理,則直接返回null,系統會繼續加載該資源。
利用這個特性,我們可以解決一個需求:假如網頁中需要加載本地的圖片,我們就可以通過攔截shouldInterceptRequest,並返回結果即可
比如下面的一段HTML代碼中,img字段加載圖片的地址是:http://localhost/qijian.png,這是我自定義的一個網址,在Android中,當發現要加載這個地址的資源時,我們將它換成本地的圖片
-
<!DOCTYPE html>
-
<html lang="en">
-
<head>
-
<meta charset="UTF-8">
-
<title>Title</title>
-
</head>
-
<body>
-
<h1 id="h">歡迎光臨啓艦的blog</h1>
-
<img src="http://localhost/qijian.png"/>
-
</body>
-
</html>
然後是Native代碼:
-
public class MyActivity extends Activity {
-
private WebView mWebView;
-
-
private ProgressDialog mProgressDialog;
-
private String TAG = "qijian";
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
mWebView = (WebView)findViewById(R.id.webview);
-
mProgressDialog = new ProgressDialog(this);
-
mWebView.getSettings().setJavaScriptEnabled(true);
-
-
-
mWebView.setWebViewClient(new WebViewClient(){
-
@Override
-
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
-
try {
-
if (url.equals("http://localhost/qijian.png")) {
-
AssetFileDescriptor fileDescriptor = getAssets().openFd("s07.jpg");
-
InputStream stream = fileDescriptor.createInputStream();
-
WebResourceResponse response = new WebResourceResponse("image/png", "UTF-8", stream);
-
return response;
-
}
-
}catch (Exception e){
-
Log.e(TAG,e.getMessage());
-
}
-
return super.shouldInterceptRequest(view, url);
-
}
-
});
-
-
mWebView.loadUrl("file:///android_asset/web.html");
-
}
這裏代碼比較容易理解,當發現當前加載資源的url是我們自定義的http://localhost/qijian.png時,就直接將本地的圖片s07.jpg作爲結果返回。有關使用WebResourceResponse來構造結果的方法,我這裏就不再展開了,內容實在是太多了,想具體瞭解針對不同情況如何返回結果的話,自己搜下相關資料吧。
結果圖如下:
7、WebViewClient之其餘函數
上面講了常用的大部分函數,還些一些函數,並不怎麼用,這裏由於篇幅有限就不再講了,把函數聲明和作用列出來供大家參考
-
-
-
-
public void onLoadResource(WebView view, String url)
-
-
-
-
-
public void onScaleChanged(WebView view, float oldScale, float newScale)
-
-
-
-
-
-
-
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event)
-
-
-
-
onFormResubmission(WebView view, Message dontResend, Message resend)
-
-
-
-
doUpdateVisitedHistory(WebView view, String url, boolean isReload)
-
-
-
-
-
-
-
onUnhandledInputEvent(WebView view, InputEvent event)
-
-
-
-
onReceivedLoginRequest(WebView view, String realm, String account, String args)
-
-
-
-
onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)
-
-
-
-
-
-
-
-
-
onReceivedClientCertRequest(WebView view, ClientCertRequest request)<span style="color:#006600;">
-
</span>
二、其它事件處理
上面講了有關WebViewClient的用法,但其中還有一些小問題是WebViewClient無法解決的,比如返回按鍵、滾動事件監聽等,下面我們就這些問題來跟大家做下探討
1、返回按鍵
如果用webview點鏈接看了很多頁以後,如果不做任何處理,點擊系統“Back”鍵,整個瀏覽器會調用finish()而結束自身,如果希望瀏覽的網頁回退而不是退出瀏覽器,需要在當前Activity中處理並消費掉該Back事件。 覆蓋Activity類的onKeyDown(int keyCoder,KeyEvent event)方法。
-
public class MyActivity extends Activity {
-
private WebView mWebView;
-
-
private ProgressDialog mProgressDialog;
-
private String TAG = "qijian";
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
-
mWebView = (WebView)findViewById(R.id.webview);
-
mProgressDialog = new ProgressDialog(this);
-
mWebView.getSettings().setJavaScriptEnabled(true);
-
mWebView.setWebViewClient(new WebViewClient());
-
-
mWebView.loadUrl("http://blog.csdn.net/harvic880925/");
-
}
-
-
@Override
-
public boolean onKeyDown(int keyCode, KeyEvent event) {
-
-
if(keyCode==KeyEvent.KEYCODE_BACK) {
-
if(mWebView.canGoBack()) {
-
mWebView.goBack();
-
return true;
-
} else {
-
System.exit(0);
-
}
-
}
-
return super.onKeyDown(keyCode, event);
-
}
-
}
在未重寫onKeyDown前的效果圖:點擊回退按鈕,整個Activity就銷燬了
重寫onKeyDown後的效果圖:
可見在重寫onKeyDown後,點擊回退按鈕時,就會回退到WebView的上一個頁面。
2、滾動事件監聽
我們都知道監聽滾動事件一般都是設置setOnScrollChangedListener,可惜的是 WebView並沒有給我們提供這樣的方法,但是我們可以重寫WebView,覆蓋裏面的一個方法: protected void onScrollChanged(final int l, final int t, final int oldl,final
int oldt){} 然後再對外提供一個接口,示例代碼如下:
-
public class MyWebView extends WebView {
-
-
private OnScrollChangedCallback mOnScrollChangedCallback;
-
-
public MyWebView(Context context) {
-
super(context);
-
}
-
-
public MyWebView(Context context, AttributeSet attrs) {
-
super(context, attrs);
-
}
-
-
public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
}
-
-
@Override
-
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-
super.onScrollChanged(l, t, oldl, oldt);
-
if (mOnScrollChangedCallback != null) {
-
mOnScrollChangedCallback.onScroll(l,t,oldl,oldt);
-
}
-
}
-
-
public OnScrollChangedCallback getOnScrollChangedCallback() {
-
return mOnScrollChangedCallback;
-
}
-
-
public void setOnScrollChangedCallback(
-
final OnScrollChangedCallback onScrollChangedCallback) {
-
mOnScrollChangedCallback = onScrollChangedCallback;
-
}
-
-
public static interface OnScrollChangedCallback {
-
public void onScroll(int left,int top ,int oldLeft,int oldTop);
-
}
-
}
這段代碼難度不大,就不再細講了。
3、如何強制使用外部瀏覽器打開網頁
如果不想在 webview 中顯示網頁,而是直接跳轉到瀏覽器的話,可以像下邊那樣調用。
-
Uri uri = Uri.parse("http://www.example.com");
-
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-
startActivity(intent);
這裏是使用隱式Intent的方式來啓用外部應用,有關隱式Intent的知識,可以參考:
《intent詳解(一)》
《intent詳解(二)》
好了,這篇文章就到這裏了,下篇文章給大家繼續講解有關WebView的知識。
如果本文有幫到你,記得加關注哦
源碼下載地址:http://download.csdn.net/detail/harvic880925/9534186
轉載請標明出處:http://blog.csdn.net/harvic880925/article/details/51523983