Android 5.0 行为变更已经好长时间了,之前进行 WebView 的开发一直没有出现问题,所有也就没有关注这次行为变更对 WebVie 带来的影响。直到要为一个视频网站开发的 APP 里嵌入 WebView 进行音视频播放,才发现问题所在。大概描述一下情况:视频网站使用了 HTTPS 加密,但视频资源采用的是 HTTP,几经测试,发现网站取消 HTTPS 加密后,WebView 就可以正常加载视频了。于是就顺藤摸瓜,有了这篇文章。开始文章之前,先来看看 Google 官方的描述 Android 5.0 行为变更之 WebView。
1. WebView 行为变更
Android 5.0 更改了应用的默认行为。
- 如果您的应用是面向 API 级别 21 或更高级别:
- 默认情况下,系统会阻止混合内容和第三方 Cookie。要允许混合内容和第三方 Cookie,请分别使用 setMixedContentMode() 和 setAcceptThirdPartyCookies() 方法。
- 系统现在可以智能地选择要绘制的 HTML 文档部分。这个新的默认行为有助于减少内存占用和提升性能。如果您要一次渲染整个文档,可通过调用 enableSlowWholeDocumentDraw() 停用此优化。
- 如果您的应用是面向低于 21 的 API 级别:
- 系统允许混合内容和第三方 Cookie,并始终一次渲染整个文档。
2. MixedContentMode
WebView 行为变更中提到了从 Android 5.0( API 21 )开始,系统默认会阻止 WebView 从一个安全(HTTPS)源去加载非安全(HTTP)源的内容。例如,从一个 HTTPS 站点的页面加载一个 HTTP 站点的图片,这种行为从 Android 5.0 开始是被禁止的。如果想要 WebView 兼容 HTTPS 和 HTTP 的加载,需要设置 MixedContentMode 为允许模式,MixedContentMode 有三种模式,详细说明如下:
MixedContentMode | 值 | 描述 |
---|---|---|
MIXED_CONTENT_ALWAYS_ALLOW | 0 | WebView 允许安全站点(HTTPS)去加载来自任何其他来源的内容,即使该来源不安全。 这是 WebView 最不安全的操作模式,尽可能的不要使用这种模式。 |
MIXED_CONTENT_NEVER_ALLOW | 1 | Webview 禁止安全站点(HTTPS)去加载非安全的站点(HTTP)的内容。比如,HTTPS 网页内容的图片是 HTTP 链接。强烈建议 App 使用这种模式,因为这样更安全,这也是从 Android 5.0 开始,WebView 的默认模式。 |
MIXED_CONTENT_COMPATIBILITY_MODE | 2 | Webview 会尝试与最新 Web 浏览器处理混合内容的方式保持一致。一些不安全(HTTP)的内容可能被加载到安全(HTTPS)的站点上,而其他类型的内容将会被阻塞。这些内容的类型是被允许加载还是被阻塞可能会随着版本的不同而改变,并没有明确的定义。这种模式主要用于在 App 里面不能控制内容的渲染,但是又希望在一个安全的环境下运行。 |
3. WebView 代码编写
了解了 WebView 行为变更及 MixedContentMode 后,我们来分析一下文章开始遇到的问题:
- 在 Android 5.0 以下,默认采用 MIXED_CONTENT_ALWAYS_ALLOW 模式,即总是允许 WebView 同时加载 HTTPS 和 HTTP;
- 从 Android 5.0 开始,默认采用 MIXED_CONTENT_NEVER_ALLOW 模式,即总是禁止 WebView 同时加载 HTTPS 和 HTTP。虽然官网给出的建议是,为了安全考虑,使用 MIXED_CONTENT_NEVER_ALLOW 模式,但是在实际应用中,当我们的服务器已经升级到 HTTPS,但是一些页面的资源是第三方的,我们不能要求第三方也都升级到 HTTPS,所以我们只能根据系统版本,用代码去设置加载模式为 MIXED_CONTENT_ALWAYS_ALLOW。
下面的代码片段来自 android.webkit.WebSettings.setMixedContentMode(int mode)
,该方法的注释中明确了不同 Android 版本下对应的 MixedContentMode。
1 | /** |
所以,setMixedContentMode(int mode) 的使用也非常简单,在 WebView 加载页面之前,对版本进行判断,设置加载模式为 MIXED_CONTENT_ALWAYS_ALLOW 即可,像下面这样:
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |