管理 WebView 物件

Android 提供多種 API,協助您管理應用程式中顯示網頁內容的WebView 物件。

本頁面說明如何使用這些 API 更有效地處理 WebView 物件,進而提升應用程式的穩定性和安全性。

Version API

從 Android 7.0 (API 級別 24) 開始,使用者可以選擇多種不同的套件,在 WebView 物件中顯示網頁內容。Jetpack Webkit 程式庫包含 getCurrentWebViewPackage() 方法,可擷取與應用程式中顯示網頁內容的套件相關資訊。如果應用程式嘗試使用特定套件的 WebView 實作項目顯示網頁內容時發生錯誤,這個方法就很有用。

如要使用這個方法,請新增下列程式碼片段中顯示的邏輯:

Kotlin

val webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(appContext)
Log.d("MY_APP_TAG", "WebView version: ${webViewPackageInfo.versionName}")

Java

PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(appContext);
Log.d("MY_APP_TAG", "WebView version: " + webViewPackageInfo.versionName);

Google 安全瀏覽服務

為提供更安全的瀏覽體驗,WebView 物件會使用 Google 安全瀏覽驗證網址, 讓應用程式在使用者嘗試前往可能不安全的網站時顯示警告。

雖然 EnableSafeBrowsing 的預設值為 true,但有時您可能只想有條件地啟用安全瀏覽,或停用安全瀏覽。Android 8.0 (API 級別 26) 以上版本支援使用 setSafeBrowsingEnabled() 為個別 WebView 物件切換安全瀏覽功能。

如要讓所有 WebView 物件都選擇不接受安全瀏覽檢查,請在應用程式的資訊清單檔案中加入下列 <meta-data> 元素:

<manifest>
    <application>
        <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
                   android:value="false" />
        ...
    </application>
</manifest>

定義程式輔助動作

如果 WebView 例項嘗試載入 Google 分類為已知威脅的網頁,WebView 預設會顯示插頁,警告使用者該網頁含有已知威脅。這個畫面會提供選項,讓使用者選擇繼續載入網址,或是返回安全的前一頁。

如果指定 Android 8.1 (API 級別 27) 以上版本為目標,可以透過程式輔助方式定義應用程式對已知威脅的回應方式,方法如下:

  • 您可以控管應用程式是否向安全瀏覽回報已知威脅。
  • 您可以讓應用程式在每次遇到歸類為已知威脅的網址時,自動執行特定動作,例如返回安全模式。

下列程式碼片段說明如何指示應用程式的 WebView 執行個體,在遇到已知威脅後一律返回安全狀態:

MyWebActivity.java

Kotlin

private lateinit var superSafeWebView: WebView
private var safeBrowsingIsInitialized: Boolean = false

// ...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    superSafeWebView = WebView(this)
    superSafeWebView.webViewClient = MyWebViewClient()
    safeBrowsingIsInitialized = false

    if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
        WebViewCompat.startSafeBrowsing(this, ValueCallback<Boolean> { success ->
            safeBrowsingIsInitialized = true
            if (!success) {
                Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!")
            }
        })
    }
}

Java

private WebView superSafeWebView;
private boolean safeBrowsingIsInitialized;

// ...

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    superSafeWebView = new WebView(this);
    superSafeWebView.setWebViewClient(new MyWebViewClient());
    safeBrowsingIsInitialized = false;

    if (WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
        WebViewCompat.startSafeBrowsing(this, new ValueCallback<Boolean>() {
            @Override
            public void onReceiveValue(Boolean success) {
                safeBrowsingIsInitialized = true;
                if (!success) {
                    Log.e("MY_APP_TAG", "Unable to initialize Safe Browsing!");
                }
            }
        });
    }
}

MyWebViewClient.java

Kotlin

class MyWebViewClient : WebViewClientCompat() {
    // Automatically go "back to safety" when attempting to load a website that
    // Google identifies as a known threat. An instance of WebView calls this
    // method only after Safe Browsing is initialized, so there's no conditional
    // logic needed here.
    override fun onSafeBrowsingHit(
            view: WebView,
            request: WebResourceRequest,
            threatType: Int,
            callback: SafeBrowsingResponseCompat
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
            callback.backToSafety(true)
            Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
        }
    }
}

Java

public class MyWebViewClient extends WebViewClientCompat {
    // Automatically go "back to safety" when attempting to load a website that
    // Google identifies as a known threat. An instance of WebView calls this
    // method only after Safe Browsing is initialized, so there's no conditional
    // logic needed here.
    @Override
    public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
            int threatType, SafeBrowsingResponseCompat callback) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
            callback.backToSafety(true);
            Toast.makeText(view.getContext(), "Unsafe web page blocked.",
                    Toast.LENGTH_LONG).show();
        }
    }
}

HTML5 Geolocation API

如果應用程式指定 Android 6.0 (API 級別 23) 以上版本,Geolocation API 僅支援 HTTPS 等安全來源。如果來源不安全,系統會自動拒絕所有對 Geolocation API 的要求,不會叫用對應的 onGeolocationPermissionsShowPrompt() 方法。

停用指標收集功能

WebView 可以在使用者同意後,將匿名診斷資料上傳至 Google。系統會針對每個例項化的 WebView,如要停用這項功能,請在資訊清單的 <application> 元素中建立下列標記:

<manifest>
    <application>
    ...
    <meta-data android:name="android.webkit.WebView.MetricsOptOut"
               android:value="true" />
    </application>
</manifest>

只有在使用者同意應用程式未選擇停用時,系統才會從應用程式上傳資料。如要進一步瞭解如何停用診斷資料回報功能,請參閱「WebView 報表中的使用者隱私權」。

Termination Handling API

如果系統終止轉譯器來回收必要記憶體,或轉譯器程序當機,導致物件的轉譯器程序消失,終止處理 API 就會處理這些情況。WebView使用這個 API 可讓應用程式繼續執行,即使轉譯器程序消失也沒問題。

如果載入特定網頁時,轉譯器當機,嘗試再次載入該網頁可能會導致新的 WebView 物件出現相同的轉譯當機行為。

下列程式碼片段說明如何在 Activity 中使用這項 API:

Kotlin

    
inner class MyRendererTrackingWebViewClient : WebViewClient() {
    private var mWebView: WebView? = null

    override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
        if (!detail.didCrash()) {
            // Renderer is killed because the system ran out of memory. The app
            // can recover gracefully by creating a new WebView instance in the
            // foreground.
            Log.e("MY_APP_TAG", ("System killed the WebView rendering process " +
                "to reclaim memory. Recreating..."))

            mWebView?.also { webView ->
                val webViewContainer: ViewGroup = findViewById(R.id.my_web_view_container)
                webViewContainer.removeView(webView)
                webView.destroy()
                mWebView = null
            }

            // By this point, the instance variable "mWebView" is guaranteed to
            // be null, so it's safe to reinitialize it.

            return true // The app continues executing.
        }

        // Renderer crashes because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!")

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you handle the crash more gracefully and let
        // your app continue executing, you must destroy the current WebView
        // instance, specify logic for how the app continues executing, and
        // return "true" instead.
        return false
    }
}

Java

public class MyRendererTrackingWebViewClient extends WebViewClient {
    private WebView mWebView;

    @Override
    public boolean onRenderProcessGone(WebView view,
            RenderProcessGoneDetail detail) {
        if (!detail.didCrash()) {
            // Renderer is killed because the system ran out of memory. The app
            // can recover gracefully by creating a new WebView instance in the
            // foreground.
            Log.e("MY_APP_TAG", "System killed the WebView rendering process " +
                    "to reclaim memory. Recreating...");

            if (mWebView != null) {
                ViewGroup webViewContainer =
                        (ViewGroup) findViewById(R.id.my_web_view_container);
                webViewContainer.removeView(mWebView);
                mWebView.destroy();
                mWebView = null;
            }

            // By this point, the instance variable "mWebView" is guaranteed to
            // be null, so it's safe to reinitialize it.

            return true; // The app continues executing.
        }

        // Renderer crashes because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!");

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you handle the crash more gracefully and let
        // your app continue executing, you must destroy the current WebView
        // instance, specify logic for how the app continues executing, and
        // return "true" instead.
        return false;
    }
}

Renderer Importance API

WebView 物件以多程序模式運作時,應用程式可彈性處理記憶體不足的情況。您可以使用 Android 8.0 推出的 Renderer Importance API,為指派給特定 WebView 物件的轉譯器設定優先順序政策。具體來說,您可能會希望在顯示應用程式 WebView 物件的算繪器遭到終止時,應用程式的主要部分能繼續執行。舉例來說,如果您預期 WebView 物件不會顯示很長一段時間,就可以執行這項操作,讓系統回收渲染器使用的記憶體。

下列程式碼片段說明如何為與應用程式 WebView 物件相關聯的算繪器程序指派優先順序:

Kotlin

val myWebView: WebView = ...
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true)

Java

WebView myWebView;
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true);

在這個程式碼片段中,算繪器的優先順序與應用程式的預設優先順序相同,或與預設優先順序繫結。當相關聯的 WebView 物件不再顯示時,true 引數會將算繪器的優先順序降低至 RENDERER_PRIORITY_WAIVED。換句話說,true 引數表示應用程式不在意系統是否讓算繪器程序保持運作。事實上,這個較低的優先順序等級很可能會導致系統在記憶體不足時終止轉譯器程序。

如要進一步瞭解如何診斷及最佳化使用網頁內容時的應用程式記憶體用量,請參閱「管理及診斷 WebView 記憶體」。如要進一步瞭解系統如何處理程序間的記憶體不足情況,請參閱「程序和應用程式生命週期」。

保留狀態和瀏覽記錄

當系統回收背景資源或裝置設定變更時,應用程式的活動及其 WebView 物件可能會遭到刪除。如要保留使用者的瀏覽背景資訊,請在 onSaveInstanceState() 中使用 saveState(Bundle) 方法,將瀏覽記錄序列化為 Bundle,然後使用 restoreState(Bundle) 還原。

由於 Android 會對整個程序的 savedInstanceState 強制執行 1 MB 的嚴格限制,因此使用標準架構 API 序列化大型瀏覽工作階段可能會導致 TransactionTooLargeException。如要強制執行大小限制或安全地捨棄轉送記錄項目,請使用 Jetpack Webkit 方法 WebViewCompat.saveState()

如需有效管理狀態的詳細策略和最佳做法,請參閱「有效管理 WebView 狀態」。