WebView你真的熟悉嗎?

風靈使發表於2018-03-29

Android手機中內建了一款高效能webkit核心瀏覽器,在SDK中封裝為一個叫做WebView元件。下面總結一下使用webview遇到的那些事、那些坑。

一、webview的基本使用方法

1. 新增許可權:AndroidManifest.xml中設定許可權"android.permission.INTERNET",否則會出Web page not available錯誤。

2. 在要Activity中生成一個WebView元件:WebView webView = new WebView(this);或者可以在activity的layout檔案裡新增webview控制元件

3. 設定WebView基本資訊:

mWebView = (WebView) findViewById(R.id.wb);
mWebView.getSettings().setJavaScriptEnabled(true);//支援javascript
mWebView.requestFocus();//觸控焦點起作用mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);//取消滾動條
mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);//設定允許js彈出alert對話方塊
//load本地
mWebView.loadUrl("file:///android_asset/hellotest.html");
//load線上
//mWebView.loadUrl("http://www.google.com");
//js訪問android,定義介面
mWebView.addJavascriptInterface(new JsInteration(), "control");
//設定了Alert才會彈出,重新onJsAlert()方法return true可以自定義處理資訊
mWebView.setWebChromeClient(new WebChromeClient() {    
@Override    
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {        
//return super.onJsAlert(view, url, message, result);        
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();        
return true;  
  }});

4. 設定WevView要顯示的網頁:
網際網路用:webView.loadUrl("http://www.google.com");
本地檔案用:webView.loadUrl("file:///android_asset/XX.html");
本地檔案存放在:assets檔案中

5. 如果希望點選連結由自己處理,而不是新開Android的系統browser中響應該連結。給WebView新增一個事件監聽物件(WebViewClient)並重寫其中的一些方法: shouldOverrideUrlLoading:對網頁中超連結按鈕的響應。當按下某個連線時WebViewClient會呼叫這個方法,並傳遞引數

public boolean shouldOverrideUrlLoading(WebView view,String url){
       view.loadUrl(url);
       return true;          
        }

6. 處理https請求
webView預設是不處理https請求的,頁面顯示空白,需要進行如下設定:

webView.setWebViewClient(new WebViewClient() { 
@Override public void onReceivedSslError(WebView view, 
SslErrorHandler handler, SslError error) { 
handler.proceed(); 
// handler.cancel(); 
// handler.handleMessage(null); } });

onReceivedSslErrorwebView處理ssl證照設定

其中handler.proceed();表示等待證照響應
handler.cancel();表示掛起連線,為預設方式
handler.handleMessage(null);可做其他處理
另外還有其他一些可重寫的方法

  1. 接收到Http請求的事件onReceivedHttpAuthRequest(WebView view,HttpAuthHandler handler, String host, String realm)
  2. 載入頁面完成的事件public void onPageFinished(WebView view, String url){ }
    同樣道理,我們知道一個頁面載入完成,於是我們可以關閉loading條,切換程式動作。
  3. 載入頁面開始的事件public void onPageStarted(WebView view, String url,Bitmap favicon) { }
    這個事件就是開始載入頁面呼叫的,通常我們可以在這設定一個loading的頁面,告訴使用者程式在等待網路響應。通過這幾個事件,我們可以很輕鬆的控制程式操作,一邊用著瀏覽器顯示內容,一邊監控著使用者操作實現我們需要的各種顯示方式,同時可以防止使用者產生誤操作。
  4. 如果用webview點連結看了很多頁以後,如果不做任何處理,點選系統“Back”鍵,整個瀏覽器會呼叫finish()而結束自身,如果希望瀏覽的網頁回退而不是退出瀏覽器,需要在當前Activity中處理並消費掉該Back事件。 覆蓋Activity類的onKeyDown(int keyCoder,KeyEvent event)方法。
 @Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {  
  if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {    
    webView.goBack();// 返回前一個頁面   
     return true;   
 }    
return super.onKeyDown(keyCode, event);
}

二、webview與js的互動(相互呼叫引數、傳值)

前端網頁全部程式碼,文章最後有示例專案完整原始碼

<!DOCTYPE html><html><head>    <meta charset="utf-8">    
<title>jaydenxiao遇上了webview</title>    
<script>
function sayHello() {
alert("我是無參無返回toast") 
}
function alertMessage(message) { 
alert(message)  
}
function toastMessage(message) {
window.control.toastMessage(message)
}
function sumToJava(number1, number2){
window.control.onSumResult(number1 + number2)
}
function sumToJava2(number1, number2) {
return number1 + number2;
}    
</script>
</head><body>
<button type="button" id="button" onclick="toastMessage('js呼叫了android方法')">js訪問android中方法</button>
</body>
</html>

呼叫示例:

js呼叫Java

呼叫格式為window.jsInterfaceName.methodName(parameterValues)
此例中我們使用的是control作為注入介面名稱。

function toastMessage(message) { 
window.control.toastMessage(message) 
}
 function sumToJava(number1, number2){ 
window.control.onSumResult(number1 + number2)
 }

Java呼叫JS

webView呼叫js的基本格式為webView.loadUrl(“javascript:methodName(parameterValues)”)

1. android呼叫js無參無返回值函式

final String call = "javascript:sayHello()";
mWebView.post(new Runnable() {    
@Override    
public void run() { 
       mWebView.loadUrl(call);    
}});

2. android呼叫js有參無返回值函式

final String call = "javascript:alertMessage(\"" + "我是android傳過來的內容,hey man" + "\")";
mWebView.post(new Runnable() {    
@Override    
public void run() { 
       mWebView.loadUrl(call);    
}});

3. android呼叫js有參有返回值函式(4.4之前)
Android在4.4之前並沒有提供直接呼叫js函式並獲取值的方法,所以在此之前,常用的思路是 java呼叫js方法,js方法執行完畢,再次呼叫java程式碼將值返回。

**(1).android呼叫js程式碼 **

final String call = "javascript:sumToJava(1,2)";
mWebView.post(
new Runnable() {  
  @Override   
 public void run() {       
 mWebView.loadUrl(call);   
 }});

(2).js函式處理,並將結果通過呼叫android方法返回
網頁端:

function sumToJava(number1, number2){ 
window.control.onSumResult(number1 + number2) 
} 

(3).android在回撥方法中獲取js函式返回值

@JavascriptInterfacepublic void onSumResult(int result) {   
Toast.makeText(getApplicationContext(), "我是android呼叫js方法(4.4前),入參是1和2,js返回結果是" + result, Toast.LENGTH_LONG).show();
}

4. android呼叫js有參有返回值函式(4.4以上):
Android 4.4以上使用evaluateJavascript即可。這裡展示一個簡單的互動示例 具有返回值的js方法
js程式碼如下:

function sumToJava2(number1, number2) {
return number1 + number2;
}

android程式碼如下:

@TargetApi(Build.VERSION_CODES.KITKAT)
public void Android2JsHaveParmHaveResult2(View view) { 
 mWebView.evaluateJavascript("sumToJava2(3,4)", new ValueCallback<String>() {
@Override 
public void onReceiveValue(String Str) {   
Toast.makeText(getApplicationContext(), "我是android呼叫js方法(4.4後),入參是3和4,js返回結果是" + Str, Toast.LENGTH_LONG).show(); 
  }    
});}

三、webview遇到的那些坑與解決方法

1. WebView的記憶體洩露。
這個問題,很難清晰描述,你在谷歌裡搜 webview lead memory 能搜到很多結果 甚至還有給谷歌提交的issue 哈哈,我也無法給出一個清晰的答案 在什麼時候 什麼版本那些手機上一定會出現記憶體洩露,
但是根據一些monkey結果來看,有時,webview記憶體洩露的情況還是很嚴重的,尤其是當你載入的頁面比較龐大的時候。解決方案參考下微信和qq的做法,試了一下是目前效果最好的,
就是 當你要用webview的時候,記得最好 另外單獨開一個程式 去使用webview 並且當這個 程式結束時,請手動呼叫System.exit(0)。
這是目前對於webview 記憶體洩露 最好的解決方案。使用此方法 所有因為webview引發的 資源無法釋放等問題 全部可以解決。

2. getSettings().setBuiltInZoomControls(true) 引發的crush
這個方法呼叫以後 如果你觸控螢幕 彈出那個提示框還沒消失的時候 你如果activity結束了 就會報錯了。3.0以上 4.4以下很多手機會出現這種情況
所以為了規避他,我們通常是在activity的onDestroy方法裡手動的將webiew設定成 setVisibility(View.GONE)

3.onPageFinished 函式到底有用沒有?
多數開發者都是參考的http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url-in-android 這個上面的高票答案。
但其實根據我自己觀察,這個函式並沒有什麼卵用,有的時候是提前結束,有的時候就遲遲無法結束,你信這個函式 還不如信上帝,甚至於onProgressChanged這個函式
都比onPageFinished 要準一些。如果你的產品經理堅持你一定要實現這種功能的話,我建議你 提早結束他,否則卡在那使用者遲遲動不了 這種體驗不好。
有空的同學可以跟一下原始碼,onPageFinished 在不同的核心裡 呼叫的時機都不一樣。說實話 我也很醉。。。這個問題 有完美解決方案的 請知會我一下。。。

4.後臺無法釋放js 導致耗電。
這個可能很少有人知道,你如果webview載入的html裡 有一些js 一直在執行比如動畫之類的東西,如果此刻webview 掛在了後臺
這些資源是不會被釋放,使用者也無法感知,導致一直佔有cpu 耗電特別快,所以大家記住了,如果遇到這種情況 請在onstoponresume裡分別把setJavaScriptEnabled();
給設定成falsetrue

5.如果實在不想用開額外程式的方式解決webview 記憶體洩露的問題,那麼下面的方法很大程度上可以避免這種情況

public void releaseAllWebViewCallback() { 
 if (android.os.Build.VERSION.SDK_INT < 16) { 
 try { 
 Field field = WebView.class.getDeclaredField("mWebViewCore"); 
field = field.getType().getDeclaredField("mBrowserFrame"); 
 field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true); 
 field.set(null, null);
 } catch (NoSuchFieldException e) {
 if (BuildConfig.DEBUG) {
  e.printStackTrace();
  }
 } catch (IllegalAccessException e) {
 if (BuildConfig.DEBUG) {
  e.printStackTrace();
  }
  }
 } else {
 try {
 Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
 if (sConfigCallback != null) {
 sConfigCallback.setAccessible(true);
 sConfigCallback.set(null, null);
  }
 } catch (NoSuchFieldException e) {
if (BuildConfig.DEBUG) {
  e.printStackTrace();
  }
 } catch (ClassNotFoundException e) {
 if (BuildConfig.DEBUG) {
  e.printStackTrace();
  }
 } catch (IllegalAccessException e) {
 if (BuildConfig.DEBUG) {
  e.printStackTrace();
  }
  }
  }
 }

webviewdestroy方法裡 呼叫這個方法就行了。

最後附上示例原始碼

相關文章