在 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。您可以透過 WebView 附加的 WebSettings 啟用這項功能。使用 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 可在使用者輕觸按鈕時,透過新版介面建立浮動式訊息:

<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 主要用於啟動特定網址的意圖。實作此 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" 的網頁載入。