管理 WebView 对象

Android 提供多种 API 来帮助您管理在应用中显示网页内容的 WebView 对象。

本页介绍如何使用这些 API 更有效地处理 WebView 对象,从而提高应用的稳定性和安全性。

Version API

从 Android 7.0(API 级别 24)开始,要使用 WebView 对象显示网页内容,用户可以从几个不同的软件包中选择。Android 8.0(API 级别 26)及更高版本包含的 API 可提取在应用中显示网页内容的软件包相关信息。在分析仅当应用尝试使用特定软件包的 WebView 实现显示网页内容时出现的错误时,此 API 特别有用。

要使用此 API,请添加以下代码段中显示的逻辑:

Kotlin

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

Java

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

注:如果设备设置有误,则 WebView.getCurrentWebViewPackage() 函数可能会返回 null。如果您在不支持 WebView 的设备(例如 Wear OS 设备)上运行应用,它也会返回 null

Google Safe Browsing API

为向用户提供更安全的浏览体验,WebView 对象会使用 Google 安全浏览功能验证网址,以使您的应用在其试图导航至可能不安全的网站时向用户显示警告。

EnableSafeBrowsing 的默认值为 true 时,您可能有时会希望仅根据条件启用安全浏览功能或停用此功能。Android 8.0(API 级别 26)及更高版本支持使用 setSafeBrowsingEnabled()。在较低 API 级别编译的应用无法使用 setSafeBrowsingEnabled(),并且应在清单中将 EnableSafeBrowsing 值更改为 false,才能为 WebView 的全部实例停用此功能。

如果您的应用适配 Android 7.1(API 级别 25)或更低版本,您可以选择 WebView 对象以根据 Google 安全浏览的不安全网站列表检查网址,只需将以下 <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 对象的 loadUrl() 函数。

以下代码段演示如何指示应用的 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

    superSafeWebView.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;

    superSafeWebView.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 : WebViewClient() {
    // Automatically go "back to safety" when attempting to load a website that
    // Google has identified 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: SafeBrowsingResponse
    ) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        callback.backToSafety(true)
        Toast.makeText(view.context, "Unsafe web page blocked.", Toast.LENGTH_LONG).show()
    }
}

Java

public class MyWebViewClient extends WebViewClient {
    // Automatically go "back to safety" when attempting to load a website that
    // Google has identified 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, SafeBrowsingResponse callback) {
        // The "true" argument indicates that your app reports incidents like
        // this one to Safe Browsing.
        callback.backToSafety(true);
        Toast.makeText(view.getContext(), "Unsafe web page blocked.",
                Toast.LENGTH_LONG).show();
    }
}

HTML5 Geolocation API

对于适配 Android 6.0(API 级别 23)及更高版本的应用,仅 HTTPS 等安全的起点支持 Geolocation API。如果不调用相应的 onGeolocationPermissionsShowPrompt() 函数,非安全起点的所有 Geolocation API 请求都会被自动拒绝。

停用指标收集功能

经用户同意,WebView 可将匿名诊断数据上传到 Google。实例化 WebView 的各应用会按应用收集数据。您可以停用此功能,只需在清单的 <application> 元素中创建以下标记即可:

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

只有用户同意未退出应用时,才能从应用上传数据。

Termination Handling API

此 API 可处理 WebView 对象的渲染器进程消失(可能是因为系统终止了渲染器以回收急需的内存,或是因为渲染器进程本身已崩溃)的情况。通过使用此 API,即使渲染器进程已经消失,您也可以继续执行应用。

注意:如果您的应用在渲染器进程消失后继续执行,便无法重复使用关联的 WebView 实例,无论渲染器进程是终止还是崩溃。您的应用必须从视图层次中移除,然后销毁该实例才能继续执行。然后,该应用必须创建一个全新的 WebView 实例才能继续渲染网页。

请注意,如果渲染器在加载特定网页时崩溃,则尝试再次加载同一个网页可能会导致新的 WebView 对象表现出同样的渲染崩溃行为。

以下代码段演示如何使用此 API:

Kotlin

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

    override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
        if (!detail.didCrash()) {
            // Renderer was 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 crashed 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 choose to handle the crash more gracefully
        // and allow your app to continue executing, you should 1) destroy the
        // current WebView instance, 2) specify logic for how the app can
        // continue executing, and 3) 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 was 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 crashed 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 choose to handle the crash more gracefully
        // and allow your app to continue executing, you should 1) destroy the
        // current WebView instance, 2) specify logic for how the app can
        // continue executing, and 3) 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);

在此代码段中,渲染器的优先级“绑定到”应用的默认优先级(即两者相同)。true 参数的作用是,当关联的 WebView 对象不再可见时,将渲染器的优先级降低至 RENDERER_PRIORITY_WAIVED。换句话说,true 参数会指明您的应用并不关心系统是否会让渲染器进程保持活动状态。事实上,这个较低的优先级可能会使渲染器进程在内存不足的情况下被终止。

警告:为保持应用的稳定性,除非您还使用 Termination Handle API 来指定 WebView 在其关联的渲染器消失时作何反应,否则不应更改 WebView 对象的渲染器优先级政策。

要详细了解系统如何处理内存不足的情况,请参阅进程和应用生命周期