WebView 使用和方法(持更)

正式項目經(jīng)常是Android 和 iOS 同時開發(fā)的,使用H5進行部分開發(fā),可以減少開發(fā)成本,又便于更新,所以在實際項目中會用 WebView 做很多事情。
在開發(fā)當(dāng)中也遇到很多的問題和深坑,WebView 雖然主要類不多,但是方法甚多。再此做知識總結(jié),也為有同樣需求和問題的同仁們提供微薄的幫助,其中也借鑒了很多公開的解決方案,會附上相應(yīng)傳送門。
如果內(nèi)容有幫助到你,請給個大拇哥?

先看下導(dǎo)圖(由于內(nèi)容較多,分開更新)
Part 1 傳送門 WebView 使用和方法
Part 2 傳送門 常見功能 & 問題

1. WebView 基本使用

1.1 基本使用

在 Android 應(yīng)用開發(fā)中會經(jīng)常要嵌套H5來進行混合式開發(fā),WebView是不可或缺的容器。

使用 WebView 加載一個網(wǎng)頁很容易

  1. AndroidManifest.xml 中添加網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
  1. 在布局中添加 WebView
<WebView
    android:id="@+id/webView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
/>
  1. ActivityFragment 中獲取控件
WebView mWebview = findViewById(R.id.webView);
// 也可以通過 new 的形式創(chuàng)建 WebView
  1. 加載目標地址
webView.loadUrl("https://developer.android.google.cn/");

so easy~
WebView 有很豐富的功能,繼續(xù)學(xué)習(xí)

1.2 加載頁面

//加載一個遠程網(wǎng)頁
webView.loadUrl("https://developer.android.google.cn/");

// 加載assets中資源
webView.loadUrl("file:///android_asset/test.html");

//加載sdcard中子源
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

使用 loadUrl,不過需要注意,這里因為是使用本地數(shù)據(jù),所以傳入的url需要做些處理,例如:
  a 如果html文件存于assets:則加前綴:file:///android_asset/
  b 如果html文件存于sdcard:則加前綴:content://com.android.htmlfileprovider/sdcard/
  注意:content 前綴可能導(dǎo)致異常,也可使用file:///sdcard/ 或者 file:/sdcard 做前綴。

2. 加載設(shè)置

2.1 頁面自適應(yīng)屏幕

settings.setUseWideViewPort(true); // 將圖片調(diào)整到適合webview的大小
settings.setLoadWithOverviewMode(true);  // 縮放至屏幕的大小

2.2 縮放

settings.setSupportZoom(true);//啟用縮放功能
settings.setBuiltInZoomControls(true);//使用WebView內(nèi)置的縮放功能
settings.setDisplayZoomControls(false);//隱藏屏幕中的虛擬縮放按鈕

2.3 個性化設(shè)置

settings.setTextZoom(100);//字體百分比,替代原API:setTextSize

安卓端上可能因為設(shè)置了系統(tǒng)字體大小導(dǎo)致 h5 頁面布局異常
該方法可設(shè)置 webview 內(nèi)部字體的縮放比例。而字體單位是 px,它其實設(shè)置的是 px 的縮放比例。通過強制設(shè)置為100%,來使得用戶的外部設(shè)置無法內(nèi)部 webview 的字體大小呈現(xiàn)。

settings.setMediaPlaybackRequiresUserGesture(false);//SDK>18 是否支持手勢控制網(wǎng)頁媒體,比如視頻的全屏
String ua = webview.getSettings().getUserAgentString();
webview.getSettings().setUserAgentString(ua+"; 自定義標記");

有時和H5交互的時候,H5需要一些設(shè)備信息,可以通過自定義 agent 處理,里面本身也包含一些內(nèi)置瀏覽內(nèi)核的信息
Mozilla/5.0 (Linux; Android 6.0; HTC D10w Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.106 Mobile Safari/537.36

2.4 Https 加載 Http 混合模式

當(dāng) WebView 加載 https 的地址中有 http 的地址時(比如 https 地址含有 http 的圖片) WebView 無法加載 http 的資源

原因是 Android 5.0 (Lollipop)開始,WebView 默認不支持同時加載 Https 和 Http 混合模式。此時可以通過 setMixedContentMode() 方法設(shè)置混合模式

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
  • MIXED_CONTENT_NEVER_ALLOW:(Android 5.0 以上默認值)
    Webview不允許一個安全的站點(https)去加載非安全的站點內(nèi)容(http),比如,https網(wǎng)頁內(nèi)容的圖片是 http 鏈接。強烈建議App使用這種模式,因為這樣更安全。

  • MIXED_CONTENT_ALWAYS_ALLOW:(Android 5.0 以下默認值)
    在這種模式下,WebView是可以在一個安全的站點(Https)里加載非安全的站點內(nèi)容(Http),這是WebView最不安全的操作模式,盡可能地不要使用這種模式。

  • MIXED_CONTENT_COMPATIBILITY_MODE
    在這種模式下,當(dāng)涉及到混合式內(nèi)容時,WebView會嘗試去兼容最新Web瀏覽器的風(fēng)格。一些不安全的內(nèi)容(Http)能被加載到一個安全的站點上(Https),而其他類型的內(nèi)容將會被阻塞。這些內(nèi)容的類型是被允許加載還是被阻塞可能會隨著版本的不同而改變,并沒有明確的定義。這種模式主要用于在App里面不能控制內(nèi)容的渲染,但是又希望在一個安全的環(huán)境下運行。

    參考:Android5.0 WebView中Http和Https混合問題

3. 頁面監(jiān)聽與攔截

3.1 WebViewClient

幫助 WebView 處理各種通知、請求事件、記錄頁面加載過程的。其中就包括URL地址,我們可以通過它來監(jiān)控到地址的調(diào)用過程

// 設(shè)置 WebViewClient
mWebView.setWebViewClient(mWebViewClient); 

shouldOverrideUrlLoading

用戶可選擇是否攔截加載 URL

如果返回值為 true,攔截 WebView 加載 url,false 允許 WebView 加載 url

If a WebViewClient is provided, returning true causes the current WebView to abort loading the URL, while returning false causes the WebView to continue loading the URL as usual.

boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
boolean shouldOverrideUrlLoading(WebView view, String url) (API>21)

可以在這個方法里做什么呢,比如點擊到已經(jīng)定義好的 url 協(xié)議 電話號碼 tel:// 時,那么可以在這里做攔截,跳轉(zhuǎn)到系統(tǒng)撥號界面。

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("tel:")) {
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        startActivity(intent);
        return true;
    }
    super.shouldOverrideUrlLoading(view, url);
}

所以在實際項目中,可以在這里處理自定義的一些跳轉(zhuǎn)協(xié)議。

onPageStarted() 開始載入頁面調(diào)用

開始載入頁面調(diào)用的,我們可以設(shè)定一個 loading 的頁面,告訴用戶程序在等待網(wǎng)絡(luò)響應(yīng)。

onPageFinished() 頁面加載結(jié)束時調(diào)用。

頁面加載結(jié)束時調(diào)用 onPageFinished()

onLoadResource()

在加載頁面資源時會調(diào)用,每一個資源(比如圖片)的加載都會調(diào)用一次。

onReceivedError()

加載頁面的服務(wù)器出現(xiàn)錯誤時調(diào)用。

App 里面使用 webview 控件的時候遇到了諸如 404 這類的錯誤的時候,若也顯示瀏覽器里面的那種錯誤提示頁面就顯得很丑陋了,那么這個時候我們的 app 就可以加載一個本地的錯誤提示頁面,比如:

@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
    super.onReceivedError(view, request, error);
    view.loadUrl("file:///android_assets/error_handle.html");
}

有一些常見的 error
-2:net::ERR_NAME_NOT_RESOLVED 服務(wù)器不存在
-8:net::ERR_CONNECTION_TIMED_OUT 連接超時,可能被墻掉了

onReceivedSslError()

SSL 證書驗證錯誤

比如處理 https 請求時,webView 默認時不處理 https 請求的,頁面顯示空白。

webView.setWebViewClient(new WebViewClient() {    
        @Override    
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {    
            handler.proceed();    //表示等待證書響應(yīng)
        // handler.cancel();      //表示掛起連接,為默認方式
        // handler.handleMessage(null);    //可做其他處理
        }    
    });

onReceivedHttpError()

響應(yīng)服務(wù)器返回的 Http 錯誤,當(dāng)一個 http 正常響應(yīng)時,狀態(tài)碼會是 200,當(dāng)狀態(tài)碼異常時可以用該方法監(jiān)聽。

這里可以和頁面做一些約定,比如頁面沒有登錄,就返回 402,當(dāng) errorcode = 402 時,就跳轉(zhuǎn)到登錄頁面。

@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
    super.onReceivedHttpError(view, request, errorResponse);
    int code = errorResponse.getStatusCode();      
    if (code == 402){
        // toLogin();
    }
}

3.2 WebChromeClient

和 webView 的網(wǎng)頁內(nèi)容管理有關(guān),它的成員方法幫助 WebView 處理 JS 的彈框、網(wǎng)站圖標、網(wǎng)站title,加載進度等等。

  // 設(shè)置 WebChromeClient
 mWebView.setWebChromeClient(mWebChromeClient);

頁面加載進度 onProgressChange()

這個方法會在網(wǎng)頁加載過程中多次觸發(fā),當(dāng) newProgress = 100 時,可以認為網(wǎng)頁加載完成。這個方法比 onPageFinished 更為準確,一般用來實現(xiàn)自定義進度條加載。

/**
 * 頁面提示框
 * @param view
 * @param newProgress 加載進度
 */
public void onProgressChanged(WebView view, int newProgress) {
    super.onProgressChanged(view, newProgress);
}

顯示頁面標題 onReceivedTitle()

通常會使用 Activity 或 Fragment 加載網(wǎng)頁,我們想讓頁面標題隨著網(wǎng)頁內(nèi)容變化的話,就可以用這個方法獲取當(dāng)前頁面的 title。

public void onReceivedTitle(WebView view, String title) {
     //過濾掉服務(wù)器返回的連接地址作為標題 過濾掉 http 開頭
     if (!title.startsWith("http")) {
         mTvTitle.setText(title);
     }
}

頁面提示框 onJsAlert()

html 可以用下文 Android 與 JS 交互中的html 代碼測試


/**
 * 頁面提示框
 * @param message alert 彈出窗口中的提示信息(提示或警告信息對話框,僅一個確認按鈕)
 * @param result 向網(wǎng)頁中的 Javascript 代碼反饋本次操作結(jié)果(result.confirm 代表點擊了確定按鈕,result.cancel 代表點擊了取消按鈕)
 * @return boolean {@code true} if the request is handled or ignored.{@code false} if WebView needs to show the default dialog. 
 */
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
//return super.onJsAlert(view, url, message, result);
    new AlertDialog.Builder(mContext)
            .setTitle("JsAlert")
            .setMessage(message)
            .setPositiveButton("確定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.confirm();
                }
            })
            .setCancelable(false)
            .show();
    return true;
}

頁面選擇框 onJsConfirm()


/**
 * 頁面選擇框
 * @param message confirm 彈出窗口中的提示信息(確認對話框,有確認、取消兩個按鈕)
 * @param result 向網(wǎng)頁中的 Javascript 代碼反饋本次操作結(jié)果(result.confirm 代表點擊了確定按鈕,result.cancel 代表點擊了取消按鈕)
 */
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
}

頁面選擇框 onJsPrompt()


/**
 * 頁面選擇框
 * @param message prompt 彈出窗口中的提示信息(輸入信息對話框,有一個輸入框,還有確認、取消兩個按鈕)
 * @param result 向網(wǎng)頁中的 Javascript 代碼反饋本次操作結(jié)果
 */
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
    final EditText editText = new EditText(MainActivity.this);
    editText.setText("默認數(shù)據(jù)");//設(shè)置默認數(shù)據(jù)
    new AlertDialog.Builder(MainActivity.this)
            .setTitle("JsPromt")
            .setView(editText)//為彈出窗口設(shè)置輸入框
            .setPositiveButton("確定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.confirm(editText.getText().toString());//向Javascript傳遞輸入值
                }
            })
            .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.cancel();
                }
            })
            .setCancelable(false)
            .show();
    return true;
}

4. 網(wǎng)頁的前進與后退

在 Android 中 如果在 WebView 觸發(fā)物理返回鍵會直接關(guān)閉整個 WebView,所以需要監(jiān)聽事件,用 WebView 的返回處理。

webView.goBack();//跳到上個頁面
webView.goForward();//跳到下個頁面
webView.canGoBack();//是否可以跳到上一頁(如果返回false,說明已經(jīng)是第一頁)
webView.canGoForward();//是否可以跳到下一頁(如果返回false,說明已經(jīng)是最后一頁)

實際項目中可以可以監(jiān)聽物理鍵的返回鍵,也可以重寫 onBackPressed()方法,以下代碼段僅做參考:

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (KeyEvent.KEYCODE_BACK == keyCode) {
        if (mWebView != null && mWebView.canGoBack()) {
            mWebView.goBack();
        } else {
            finish();
        }
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

5. 緩存

加載 html 界面時:

  • 當(dāng)加載 html 頁面時,WebView 會在/data/data/包名目錄下生成 database 與 cache 兩個文件夾。
  • 請求的 URL記錄保存在 WebViewCache.db,而 URL的內(nèi)容是保存在 WebViewCache 文件夾下。

緩存方式

settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
  • LOAD_CACHE_ONLY: 不使用網(wǎng)絡(luò),只讀取本地緩存數(shù)據(jù)
  • LOAD_DEFAULT: (默認)根據(jù)cache-control決定是否從網(wǎng)絡(luò)上取數(shù)據(jù)
  • LOAD_NO_CACHE: 不使用緩存,只從網(wǎng)絡(luò)獲取數(shù)據(jù)
  • LOAD_CACHE_ELSE_NETWORK:只要本地有,無論是否過期,或者no-cache,都使用緩存中的數(shù)據(jù)

PS:這里還有其他和緩存相關(guān)的,由于還沒有搞的很清楚,暫時不總結(jié)了

清除緩存

//清除網(wǎng)頁訪問留下的緩存
//由于內(nèi)核緩存是全局的因此這個方法不僅僅針對webview而是針對整個應(yīng)用程序.
webView.clearCache(true);

//清除當(dāng)前webview訪問的歷史記錄
//只會webview訪問歷史記錄里的所有記錄除了當(dāng)前訪問記錄
webView.clearHistory();

//這個api僅僅清除自動完成填充的表單數(shù)據(jù),并不會清除WebView存儲到本地的數(shù)據(jù)
webView.clearFormData();

6. Android 和 JS 的交互

允許 WebView 與 Js 交互,先設(shè)置權(quán)限

settings.setJavaScriptEnabled(true); //使 WebView 支持 JS
settings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通過JS打開新窗口
  • setJavaScriptEnabled 設(shè)置支持 JS,比如下面的代碼段 放到 assets 文件下,使用loadUrl("file:///android_asset/index.html") 打開網(wǎng)頁,如果改屬性為 false 點擊就不能彈出 alert

  • setJavaScriptCanOpenWindowsAutomatically 這個方法目前沒測試出到底有什么用途。無論 true 還是 false 都能代開網(wǎng)頁。不過以防萬一,還是設(shè)置上吧

index.html 文件(可放入asset 文件夾下測試)

<!DOCTYPE html>
<head>
    <meta charset=utf-8>
    <meta http-equiv=X-UA-Compatible content="IE=edge">
    <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
    <title></title>
</head>

<body>
    <button id="app">點擊</button>
</body>

<script>
  document.getElementById("app").onclick = function () {
     alert('Tina')
     //window.open("http://www.baidu.com")
  }
</script>
</html>

6.1 Android 調(diào)用 JS

在 html 有如下 javascript 方法(可放進上面 script 中驗證)

function jsFunc(){   
    alert('調(diào)用 JS 方法')
}

Android 主動調(diào)用 JavaScript 的函數(shù),使用如下方法

mWebView.loadUrl("javascript:jsFunc()");

加參也一樣,比如調(diào)用下面方法

function jsFunc(msg){   
    alert(msg)
}

WebView 操作:

mWebView.loadUrl("javascript:jsFunc(\'jsFunc:Tina\')");
  • 注意字符串要加引號

6.2 JS 調(diào)用 Android

Android 使用如下方法和 Js 建立聯(lián)系

addJavascriptInterface(Object obj, String interfaceName); 

第一個參數(shù)為類的對象,第二是自定義別名,Js 通過這個別名來調(diào)用 Java 的方法

前面的 Html 中,點擊 button 觸發(fā)事件,通過 Android 別名調(diào)用 Android 的 callJava 方法

  document.getElementById("app").onclick = function () {
     window.android.callJava()
  }

設(shè)置 WebView

 mWebView.addJavascriptInterface(new JSRelation(mContext), "android");

在定義的類中添加相關(guān)方法 方法需添加@JavascriptInterface注解

public class JSRelation {
    private Context mContext;
    public JSRelation(Context context) {
        this.mContext = context;
    }

    @JavascriptInterface
    public void callJava(){
        Toast.makeText(mContext,"Android:callJava",Toast.LENGTH_LONG).show();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容