在 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");

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

<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" 的網頁載入。