在 WebView 中建構網頁應用程式

使用 WebView 提供網頁應用程式或網頁,做為用戶端應用程式的一部分。WebView 類別是 Android View 類別的擴充功能,可讓您將網頁顯示為活動版面配置的一部分。不含完整開發網路瀏覽器的功能,例如導覽控制項或網址列。根據預設,所有 WebView 都會顯示網頁。

WebView 可協助您在應用程式中提供可能需要更新的資訊,例如使用者協議或使用手冊。在 Android 應用程式中,您可以建立包含 WebViewActivity,然後用它來顯示代管在網路上的文件。

當應用程式將資料提供給需要網路連線才能擷取資料 (例如電子郵件) 的使用者提供時,WebView 也能派上用場。在這種情況下,您可能會發現在 Android 應用程式中建構 WebView,可顯示網頁包含所有使用者資料,而不是執行網路要求,然後剖析資料並在 Android 版面配置中顯示。相反地,您可以設計專為 Android 裝置打造的網頁,然後在可載入網頁的 Android 應用程式中實作 WebView

本文件說明如何開始使用 WebView、如何將網頁中的 JavaScript 與 Android 應用程式中的用戶端程式碼繫結、如何處理網頁導覽,以及如何在使用 WebView 時管理視窗。

在舊版 Android 上使用 WebView

如要在執行應用程式的裝置上安全地使用近期的 WebView 功能,請新增 AndroidX Webkit 程式庫。這是一個靜態程式庫,您可以將其新增至應用程式,以使用較舊的平台版本無法使用的 android.webkit API。

將其新增至 build.gradle 檔案,如下所示:

Kotlin

dependencies {
    implementation("androidx.webkit:webkit:1.8.0")
}

Groovy

dependencies {
    implementation ("androidx.webkit:webkit:1.8.0")
}

詳情請參閱 GitHub 上的 WebView 範例

在應用程式中新增 WebView

若要在應用程式中加入 WebView,您可以在活動版面配置中加入 <WebView> 元素,也可以在 onCreate() 中將整個 Activity 視窗設為 WebView

在活動版面配置中新增 WebView

如要在版面配置中為應用程式新增 WebView,請將下列程式碼加入活動的版面配置 XML 檔案:

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

如要在 WebView 中載入網頁,請使用 loadUrl(),如以下範例所示:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");

在 onCreate() 中新增 WebView

如要在活動的 onCreate() 方法中改為將 WebView 新增至應用程式,請使用類似以下的邏輯:

Kotlin

val myWebView = WebView(activityContext)
setContentView(myWebView)

Java

WebView myWebView = new WebView(activityContext);
setContentView(myWebView);

然後載入頁面:

Kotlin

myWebView.loadUrl("http://www.example.com")

Java

myWebView.loadUrl("https://www.example.com");

或從 HTML 字串載入網址:

Kotlin

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
val unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
val encodedHtml = Base64.encodeToString(unencodedHtml.toByteArray(), Base64.NO_PADDING)
myWebView.loadData(encodedHtml, "text/html", "base64")

Java

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
String unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(),
        Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

您的應用程式必須可以存取網際網路。如要取得網際網路存取權,請在資訊清單檔案中要求 INTERNET 權限,如以下範例所示:

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

您可以透過下列任一方式自訂 WebView

  • 使用 WebChromeClient 啟用全螢幕支援。如果 WebView 需要權限修改主機應用程式的 UI,例如建立或關閉視窗,或向使用者傳送 JavaScript 對話方塊,系統也會呼叫此類別。如要進一步瞭解如何在這種情況下進行偵錯,請參閱「對網頁應用程式偵錯」。
  • 處理會影響內容轉譯的事件,例如使用 WebViewClient 處理提交或瀏覽時的錯誤。您也可以使用這個子類別來攔截網址載入作業。
  • 修改 WebSettings 來啟用 JavaScript。
  • 使用 JavaScript 存取已插入 WebView 的 Android 架構物件。

在 WebView 中使用 JavaScript

如果您要在 WebView 中載入的網頁使用 JavaScript,您必須為 WebView 啟用 JavaScript。啟用 JavaScript 後,即可在應用程式程式碼和 JavaScript 程式碼之間建立介面。

啟用 JavaScript

根據預設,WebView 會停用 JavaScript。您可以透過附加至 WebViewWebSettings 來啟用。使用 getSettings() 擷取 WebSettings,然後使用 setJavaScriptEnabled() 啟用 JavaScript。

請參閱以下範例:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings 可讓您存取其他許多實用的設定。舉例來說,如果您是針對 Android 應用程式中的 WebView 開發專屬網頁應用程式,可以使用 setUserAgentString() 定義自訂使用者代理程式字串,然後查詢網頁中的自訂使用者代理程式,確認要求您的網頁的用戶端是否為您的 Android 應用程式。

將 JavaScript 程式碼繫結至 Android 程式碼

在 Android 應用程式中開發專為 WebView 設計的網頁應用程式時,您可以在 JavaScript 程式碼和用戶端 Android 程式碼之間建立介面。舉例來說,JavaScript 程式碼可以在 Android 程式碼中呼叫方法來顯示 Dialog,而不是使用 JavaScript 的 alert() 函式。

如要在 JavaScript 和 Android 程式碼之間繫結新介面,請呼叫 addJavascriptInterface(),並傳遞用於繫結至 JavaScript 的類別例項,以及 JavaScript 可呼叫以存取類別的介面名稱。

舉例來說,您可以在 Android 應用程式中加入下列類別:

Kotlin

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

Java

public class WebAppInterface {
    Context mContext;

    /** Instantiate the interface and set the context. */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page. */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

在本範例中,WebAppInterface 類別可讓網頁使用 showToast() 方法建立 Toast 訊息。

您可以使用 addJavascriptInterface() 將這個類別繫結至在 WebView 中執行的 JavaScript,如以下範例所示:

Kotlin

val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

Java

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

這項操作會建立名為 Android 的介面,用於在 WebView 中執行 JavaScript 的介面。此時,網頁應用程式可以存取 WebAppInterface 類別。舉例來說,以下 HTML 和 JavaScript 會在使用者輕觸按鈕時,使用新介面建立浮動式訊息:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

您不需要從 JavaScript 初始化 Android 介面。WebView 會自動提供給您的網頁使用。因此,當使用者輕觸按鈕時,showAndroidToast() 函式會使用 Android 介面呼叫 WebAppInterface.showToast() 方法。

處理頁面瀏覽

根據預設,當使用者輕觸 WebView 中的網頁連結時,Android 會啟動可處理網址的應用程式。一般來說,預設的網路瀏覽器會開啟並載入到達網頁網址。不過,您可以覆寫 WebView 的這項行為,讓連結在 WebView 中開啟。這樣一來,您就可以讓使用者向後或向前瀏覽由 WebView 維護的網頁歷史記錄。

如要開啟使用者輕觸的連結,請使用 setWebViewClient()WebView 提供 WebViewClient。使用者輕觸的所有連結會在 WebView 中載入。如要進一步控管點擊連結的載入位置,請自行建立 WebViewClient 來覆寫 shouldOverrideUrlLoading() 方法。以下範例假設 MyWebViewClientActivity 的內部類別。

Kotlin

private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // This is your website, so don't override. Let your WebView load
            // the page.
            return false
        }
        // Otherwise, the link isn't for a page on your site, so launch another
        // Activity that handles URLs.
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}

Java

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if ("www.example.com".equals(request.getUrl().getHost())) {
      // This is your website, so don't override. Let your WebView load the
      // page.
      return false;
    }
    // Otherwise, the link isn't for a page on your site, so launch another
    // Activity that handles URLs.
    Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
    startActivity(intent);
    return true;
  }
}

然後為 WebView 建立這個新 WebViewClient 的執行個體:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

現在,當使用者輕觸連結時,系統會呼叫 shouldOverrideUrlLoading() 方法,以檢查網址主機是否與特定網域相符 (如上述範例所定義)。如果相符,這個方法會傳回 false,且不會覆寫網址載入。可讓 WebView 照常載入網址。如果網址主機不相符,系統會建立 Intent 來啟動預設的 Activity 來處理網址,進而解析使用者的預設網路瀏覽器。

處理自訂網址

WebView 會在要求資源及解析使用自訂網址配置的連結時套用限制。舉例來說,如果您實作回呼 (例如 shouldOverrideUrlLoading()shouldInterceptRequest()),則 WebView 只會針對有效網址叫用這些回呼。

舉例來說,WebView 可能不會針對以下連結呼叫 shouldOverrideUrlLoading() 方法:

<a href="showProfile">Show Profile</a>

系統以不一致的方式在 WebView 中處理無效網址,就像上述範例中顯示的網址一樣,因此建議您改用格式正確的網址。您可以針對貴機構控制的網域使用自訂配置或 HTTPS 網址。

如同上述範例,您可以不如上述範例,在連結中使用簡易字串,如下所示:

<a href="example-app:showProfile">Show Profile</a>

接著,您可以在 shouldOverrideUrlLoading() 方法中處理這個網址,如下所示:

Kotlin

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}

Java

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

shouldOverrideUrlLoading() API 主要用於啟動特定網址的意圖。實作時,請務必針對 WebView 處理的網址傳回 false。但並非僅限於啟動意圖。您可以將啟動意圖替換為上述程式碼範例中的任何自訂行為。

WebView 覆寫網址載入作業時,會自動累積已造訪網頁的記錄。您可以使用 goBack()goForward() 返回或往前瀏覽記錄。

例如,以下展示 Activity 如何使用裝置返回按鈕返回上一個畫面:

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // Check whether the key event is the Back button and if there's history.
    if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
        myWebView.goBack()
        return true
    }
    // If it isn't the Back button or there isn't web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // Check whether the key event is the Back button and if there's history.
    if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
        myWebView.goBack();
        return true;
    }
    // If it isn't the Back button or there's no web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event);
}

如果應用程式使用 AndroidX AppCompat 1.6.0 以上版本,您可以進一步簡化先前的程式碼片段:

Kotlin

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}

Java

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack();
    }
}

如果使用者可造訪的網頁歷史記錄,canGoBack() 方法會傳回 true。同樣地,您可以使用 canGoForward() 檢查是否有正向記錄。如果您未執行這項檢查,則在使用者到達記錄結束時,goBack()goForward() 就不會有任何作用。

處理裝置設定變更

在執行階段期間,當裝置的設定有所變更,例如使用者旋轉裝置或關閉輸入法編輯器 (IME) 時,活動狀態就會變更。這些變更會導致系統刪除 WebView 物件的活動,並建立新的活動,同時建立一個新的 WebView 物件,用於載入已刪除物件的網址。如要修改活動的預設行為,您可以在資訊清單中變更其處理 orientation 變更的方式。如要進一步瞭解如何在執行階段期間處理設定變更,請參閱「處理設定變更」。

管理視窗

根據預設,系統會忽略開啟新視窗的要求。無論透過 JavaScript 或連結中的目標屬性開啟,皆是如此。您可以自訂 WebChromeClient 以提供自己的開啟多個視窗行為。

為了提升應用程式的安全性,建議您避免開啟彈出式視窗和新視窗。最安全的實作方式是將 "true" 傳遞至 setSupportMultipleWindows(),但不要覆寫 setSupportMultipleWindows() 依附的 onCreateWindow() 方法。這個邏輯會禁止在連結中使用 target="_blank" 的任何頁面載入,