迁移到 Android 4.4 中的 WebView

Android 4.4(API 级别 19)引入了基于 Chromium 的新版 WebView。此项变更会升级 WebView 的性能并标准化对 HTML5、CSS3 和 JavaScript 的支持,以与最新的网络浏览器保持一致。在 Android 4.4 及更高版本的设备上运行时,使用 WebView 的所有应用都将继承这些升级。

本文档介绍了关于 WebView 的其他变更,如果您将 targetSdkVersion 设置为“19”或更高版本,则应注意这些变更。

注意:如果您的 targetSdkVersion 设置为“18”或更低版本,则 WebView 将在“怪异模式”下尽可能紧密地运行,以避免上述某些行为变更,同时仍为应用提供性能和网络标准升级。不过要注意这一点,Android 4.4 根本不支持单列和窄列布局以及默认缩放级别,可能会存在未发现的其他行为差异,因此请务必在 Android 4.4 或更高版本上测试您的应用,即使 targetSdkVersion 设置为“18”或更低版本也是如此。

为了帮助您解决将应用迁移到 Android 4.4 中的 WebView 时可能遇到的任何问题,您可以调用 setWebContentsDebuggingEnabled() 以通过桌面设备上的 Chrome 启用远程调试。借助 WebView 中的这项新功能,您可以在 WebView 中运行时检查和分析网页内容、脚本和网络活动。如需了解详情,请参阅 Android 上的远程调试

用户代理更改

如果您根据用户代理向 WebView 提供内容,则应注意用户代理字符串可能稍有变化,现在包含 Chrome 版本:

    Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16H) AppleWebKit/537.36
    (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36
    

如果您需要检索用户代理,但不需要为应用存储它或不想实例化 WebView,则应使用静态方法 getDefaultUserAgent()。不过,如果您打算替换 WebView 中的用户代理字符串,则可以改用 getUserAgentString()

多线程和线程拦截

如果您从应用的界面线程之外的任何线程针对 WebView 调用方法,可能会导致意外结果。例如,如果您的应用使用多个线程,则可以使用 runOnUiThread() 方法确保代码在界面线程中执行:

Kotlin

    runOnUiThread {
        // Code for WebView goes here
    }
    

Java

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // Code for WebView goes here
        }
    });
    

另外,请确保您一定不会拦截界面线程。某些应用在等待 JavaScript 回调时会出现此错误。例如,不要使用如下代码:

Kotlin

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()")
    while (result == null) {
        Thread.sleep(100)
    }
    

Java

    // This code is BAD and will block the UI thread
    webView.loadUrl("javascript:fn()");
    while(result == null) {
      Thread.sleep(100);
    }
    

您可以改用新方法 evaluateJavascript() 来异步运行 JavaScript。

自定义网址处理

新的 WebView 会在请求资源和解析使用自定义网址架构的链接时应用其他限制。例如,如果您实现 shouldOverrideUrlLoading()shouldInterceptRequest() 之类的回调,那么 WebView 仅针对有效网址调用它们。

如果您使用的是自定义网址架构或基准网址,并注意到您的应用接收的对这些回调的调用较少或无法在 Android 4.4 上加载资源,请确保请求按照 RFC 3986 的规定指定有效的网址。

例如,新的 WebView 可能不会针对如下链接调用您的 shouldOverrideUrlLoading() 方法:

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

用户点击此类链接后出现的结果可能会有所不同:

  • 如果您通过调用 loadData() 或者包含无效或为 null 的基本网址的 loadDataWithBaseURL() 来加载网页,则不会在该页面上收到此类链接的 shouldOverrideUrlLoading() 回调。

    注意:当您使用 loadDataWithBaseURL() 且基准网址无效或设为 null 时,您加载的内容中的所有链接都必须为绝对链接。

  • 如果您通过调用 loadUrl() 加载页面或使用 loadDataWithBaseURL() 提供有效基准网址,则会收到页面上的此类链接的 shouldOverrideUrlLoading() 回调,但您收到的网址将是绝对网址(相对于当前页面)。例如,您收到的网址将为 "http://www.example.com/showProfile"(而不只是 "showProfile")。

您可以使用如下所示的自定义架构,而不是如上所述在链接中使用简单字符串:

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

然后,您可以在 shouldOverrideUrlLoading() 方法中处理此网址,如下所示:

Kotlin

    // The URL scheme should be non-hierarchical (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 should be non-hierarchical (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;
    }
    

如果您无法更改 HTML,则可以使用 loadDataWithBaseURL() 并设置由自定义架构和有效主机组成的基准网址,例如 "example-app://<valid_host_name>/"。例如:

Kotlin

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA, null, "UTF-8", null)
    

Java

    webView.loadDataWithBaseURL("example-app://example.co.uk/", HTML_DATA,
            null, "UTF-8", null);
    

有效主机名称应符合 RFC 3986 并且必须在末尾包含尾部斜杠,否则系统可能会丢弃来自已加载网页的所有请求。

视口更改

视口 target-densitydpi 不再受支持

以前,WebView 支持名为 target-densitydpi 的视口属性来帮助网页指定其预期的屏幕密度。此属性已不再受支持,您应迁移到使用标准解决方案来处理图片和 CSS,如WebView 中的精美像素风格界面中所述。

在视口较小时放大视口

以前,如果将视口宽度的值设置为小于或等于“320”,则它会设置为“device-width”;如果将视口高度的值设置为小于或等于 WebView 高度,则它会设置为“device-height”。不过,在新的 WebView 中运行时,系统会使用宽度值或高度值,并放大 WebView 以填充屏幕宽度。

不支持多视口标记

以前,如果您在网页中添加了多个视口标记,则 WebView 会合并所有标记中的属性。在新的 WebView 中,系统仅使用最后一个视口并忽略所有其他视口。

默认缩放级别已弃用

用于在页面上获取并设置初始缩放级别的 getDefaultZoom()setDefaultZoom() 方法不再受支持,您应改为在网页中定义相应的视口。

注意:Android 4.4 及更高版本根本不支持这些 API。即使您的 targetSdkVersion 设置为“18”或更低版本,这些 API 也不会造成任何影响。

如需了解如何在 HTML 中定义视口属性,请参阅 WebView 中的精美像素风格界面

如果无法在 HTML 中设置视口的宽度,则应调用 setUseWideViewPort() 以确保页面具有较大的视口。例如:

Kotlin

    webView.settings.apply {
        useWideViewPort = true
        loadWithOverviewMode = true
    }
    

Java

    WebSettings settings = webView.getSettings();
    settings.setUseWideViewPort(true);
    settings.setLoadWithOverviewMode(true);
    

样式更改

后台 CSS 简写形式会替换 background-size

Chrome 浏览器和其他浏览器采用这种方法已经有一段时间了,但现在如果您也指定了 background 样式,则 WebView 也会替换 background-size 的 CSS 设置。例如,此处的尺寸会重置为默认值:

    .some-class {
      background-size: contain;
      background: url('images/image.png') no-repeat;
    }
    

解决方法是直接在两个属性之间来回切换。

    .some-class {
      background: url('images/image.png') no-repeat;
      background-size: contain;
    }
    

尺寸采用 CSS 像素来代替屏幕像素

以前,尺寸参数(如 window.outerWidthwindow.outerHeight)会以实际屏幕像素返回值。在新的 WebView 中,这些参数会根据 CSS 像素返回值。

尝试针对尺寸调整元素或其他计算使用并计算物理尺寸(以像素为单位)的做法通常不妥。不过,如果您停用了缩放功能并将初始比例设置为 1.0,则可以使用 window.devicePixelRatio 获取比例,然后用 CSS 像素值乘以该值。您也可以改为创建 JavaScript 绑定,从 WebView 本身查询像素尺寸。

如需了解详情,请参阅 quirksmode.org

NARROW_COLUMNS 和 SINGLE_COLUMN 不再受支持

新的 WebView 不支持 WebSettings.LayoutAlgorithmNARROW_COLUMNS 值。

注意:Android 4.4 及更高版本根本不支持这些 API。即使您的 targetSdkVersion 设置为“18”或更低版本,这些 API 也不会造成任何影响。

您可以通过以下方式处理此更改:

  • 更改应用的样式:

    如果您可以控制页面上的 HTML 和 CSS,则可能会发现更改内容设计可能是最可靠的方法。例如,在您引用许可的屏幕上,您可能需要在 <pre> 标记内封装文本,可以使用以下样式执行此操作:

    <pre style="word-wrap: break-word; white-space: pre-wrap;">

    如果您尚未为页面定义视口属性,这可能会非常有帮助。

  • 使用新的 TEXT_AUTOSIZING 布局算法:

    如果您使用窄列来提高移动设备上的各种桌面版网站的可读性,并且无法更改 HTML 内容,则新的 TEXT_AUTOSIZING 算法可能是 NARROW_COLUMNS 的合适替代方法。

此外,新的 WebView 也不支持之前已弃用的 SINGLE_COLUMN 值。

在 JavaScript 中处理触摸事件

如果您的网页直接在 WebView 中处理触摸事件,请确保您还一并处理 touchcancel 事件。在以下情况下,系统将调用 touchcancel,如果未收到调用,可能会导致出现问题:

  • 用户触摸某个元素(系统因此调用 touchstarttouchmove)并滚动页面,从而导致系统抛出 touchcancel
  • 用户触摸某个元素(系统调用 touchstart),但系统未调用 event.preventDefault(),导致 touchcancel 提前抛出(因此 WebView 假定您不想使用触摸事件)。