导航延迟时间是衡量用户体验的一项关键指标。为了帮助开发者缩短此延迟时间,WebView 提供了用于推测加载的 API,让您的应用能够在用户明确导航到内容之前提取或呈现内容。
WebView 支持三种主要类型的推测加载:预连接、预提取和 Prerender(预渲染)。
通过实现推测加载策略,您可以实现以下目标:
- 大幅缩短网页内容加载延迟时间: 将网络启动时间提前到应用生命周期的早期阶段。
- 提高导航成功率: 通过预热网络和缓存,导航不太可能因瞬时网络问题而失败。
- 提升感知响应速度: 尤其是 Prerender(预渲染),可实现即时转换,让应用感觉明显更快。
选择推测加载策略
这些策略之间的主要区别在于其范围:预连接 API 基于来源,这意味着它只需要目标网域。预提取和 Prerender(预渲染)API 基于网址,这意味着它们需要确切的网页路径。
由于预连接在来源级别运行,因此可以在应用生命周期的早期阶段启动,甚至在您知道用户将导航到的具体内容或网页之前启动。
下表对这三种策略进行了比较,以帮助您为自己的使用场景选择合适的策略:
| 功能 | 预连接 | 预提取 | Prerender(预渲染) |
|---|---|---|---|
| 主要目标 | 预热连接 | 仅缓存 HTML(不缓存 JavaScript 或 CSS) | Prerender(预渲染)整个网页 |
| 范围 | 配置文件级别(在 WebView 之间共享) | 配置文件级别(在 WebView 之间共享) | WebView 级别(绑定到特定 WebView) |
| Jetpack WebKit API | androidx.webkit.Profile |
androidx.webkit.Profile |
androidx.webkit.WebViewCompat |
| 核心 API 方法 | preconnect(...) |
prefetchUrlAsync(...) |
prerenderUrlAsync(...) |
| 配置 | 不适用 | PrefetchCache.setMaxPrefetches()PrefetchCache.setPrefetchTtlSeconds() |
setMaxPrerenders() |
| 资源使用情况 | 低(网络) | 中(网络、内存) | 高(CPU、内存、网络) |
| 适用情形 | 当目标来源已知,但具体网址尚未确定时。 | 当确切网址已知且导航很可能发生时,缓存会在 WebView 之间共享。 | 当确切网址已知且导航在特定 WebView 中非常确定时。 |
| 福利 | 加快来源上任何网址的连接设置速度 | 加快匹配网址的网络加载速度 | 激活后真正实现即时导航 |
预连接到来源
预连接通过预先执行指定来源的 DNS 查找和 TCP/TLS 握手来加快未来的加载速度。
与需要确切目标网址的预提取和 Prerender(预渲染)不同,预连接严格基于来源。这让您能够比预提取和 Prerender(预渲染)更早地进行预连接调用。
这种低资源、配置文件级别的策略可缩短共享该配置文件的任何 WebView 的初始延迟时间,前提是该来源尚未被访问过。 连接保持打开状态约 30 秒,通过消除握手开销,使任何后续的跨源 HTTP 请求、导航或子资源受益。
实现
如需启动预连接,请在 Profile 实例上调用 preconnect(String url)。此 API 必须在界面线程上调用,并且需要支持 WebViewFeature.PRECONNECT。
该 API 在来源上运行,但为方便起见,可以提供完整网址
(例如 https://www.example.com/index.html)。这会自动被视为
对来源(例如 https://www.example.com)的调用。可以通过多次调用此 API 连接到多个来源
。
Kotlin
// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
profile.preconnect("https://www.example.com/index.html")
// This initiates a connection to the origin https://www.example.com
}
Java
// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
profile.preconnect("https://www.example.com/index.html");
// This initiates a connection to the origin https://www.example.com
}
常见配置:PrefetchParameters 和 PrerenderParameters
预提取和 Prerender(预渲染)都使用 PrefetchParameters 或 PrerenderParameters 来自定义请求。借助这些类,您可以提供
额外的标头和提示,例如 No-Vary-Search
配置。
Kotlin
// Isolated configuration specifically for Cache-Level Prefetching
val prefetchParams = PrefetchParameters.Builder()
.addAdditionalHeader("X-Custom-Client", "Android-App-V2")
.setExpectedNoVarySearchHeader(
NoVarySearchHeader.varyExcept(true, listOf("session_id", "click_ref"))
)
.build()
Java
PrefetchParameters prefetchParams = new PrefetchParameters.Builder()
.addAdditionalHeader("X-Custom-Header", "value")
/**
* Hint to ignore specific query parameters during cache matching.
* This allows the cache to match even if the tracking_id differs.
*/
.setExpectedNoVarySearchHeader(
NoVarySearchHeader.varyExcept(true, Arrays.asList("tracking_id"))
)
/**
* Determines if Client Hints are sent.
* NOTE: This is ignored for Prerendering API requests, which default to
* the WebView's WebSettings.getJavaScriptEnabled() value.
*/
.setJavaScriptEnabled(true)
.build();
预提取内容
预提取会下载网址的主要 HTML 资源,并将其存储在配置文件的网络缓存中。在 WebView 中,Profile 充当浏览器数据的容器,包括 Cookie、HTTP 缓存和服务工作器。由于预提取是配置文件级别的操作,因此与该配置文件关联的任何 WebView 都可以利用缓存的响应。
实现
如需启动预提取,请在 Profile 实例上调用 prefetchUrlAsync()。此操作仅支持 HTTPS 架构。
Kotlin
profile.prefetchUrlAsync(
url,
prefetchParams,
cancellationSignal,
executor,
object : WebViewOutcomeReceiver<PrefetchResult, PrefetchException> {
override fun onResult(result: PrefetchResult) {
if (result.wasDuplicate()) {
// URL and No-Vary-Search permutations already exist in the cache layer
} else {
// The HTML payload has been successfully secured in the HTTP cache
}
}
override fun onError(error: PrefetchException) {
when (error) {
is PrefetchNetworkException -> {
// Isolates network layer or server-side HTTP anomalies
val code = error.httpStatusCode
// Facilitates rapid diagnosis of 4xx or 5xx server responses
}
else -> {
// Catches generalized execution failures and system constraints
}
}
}
}
)
Java
profile.prefetchUrlAsync(
url,
prefetchParams,
cancellationSignal,
executor,
new WebViewOutcomeReceiver<PrefetchResult, PrefetchException>() {
@Override
public void onResult(PrefetchResult result) {
if (result.wasDuplicate()) {
// URL and No-Vary-Search permutations already exist in the cache layer
} else {
// The HTML payload has been successfully secured in the HTTP cache
}
}
@Override
public void onError(PrefetchException error) {
if (error instanceof PrefetchNetworkException) {
// Isolates network layer or server-side HTTP anomalies
int code = ((PrefetchNetworkException) error).httpStatusCode;
// Facilitates rapid diagnosis of 4xx or 5xx server responses
} else {
// Catches generalized execution failures and system constraints
}
}
}
);
拦截生命周期
WebView 预提取请求会改变 shouldInterceptRequest() 回调的触发时间和方式。由于这会直接影响预提取的内容是否成功使用,因此了解两步生命周期至关重要:
1. 推测阶段(预提取请求)
调用 prefetchUrlAsync() 时,WebView 会在后台下载主要 HTML 资源。对于此后台请求,系统会完全跳过
shouldInterceptRequest()。通常在拦截器内处理的任何自定义逻辑、授权令牌或标头注入都不会应用于预提取的 HTML 资源。
**2. 导航阶段(用户激活)
当应用明确导航到网址(例如,使用 loadUrl())或用户点击匹配的链接时,WebView 会确定是否可以使用预提取的缓存:
主要 HTML 评估: WebView 此时会触发主要 HTML 的
shouldInterceptRequest()。如需成功从预提取缓存传送网页,拦截器必须返回null。如果您返回自定义WebResourceResponse,WebView 会尊重您的拦截器并完全绕过预提取缓存。子资源评估: 在清除预提取的 HTML 以供使用后,
shouldInterceptRequest()会针对完成网页呈现所需的所有后续子资源(例如,图片、脚本和 CSS)正常触发。
关键行为
以下操作特征和资格检查决定了 WebView 如何启动和管理预提取请求:
- 线程安全: 可以从任何线程启动请求。
- 资格: 在启动提取之前,WebView 会通过检查以下内容来确保请求安全且在上下文中适当:
- 现有 Cookie: 为了保护用户隐私并防止类似 CSRF 的副作用,如果请求需要特定的经过身份验证的 Cookie,而这些 Cookie 可能会触发服务器上的状态更改,WebView 可能会跳过预提取。
- Service Worker 存在: 如果 Service Worker 已控制网址的范围,WebView 可以遵循 Service Worker 的提取处理程序,而不是启动标准网络预提取。
- 代理可用性: WebView 会验证当前网络路径(包括任何已配置的代理)是否稳定,以避免在复杂的网络配置下推测性请求失败。
- 如果预提取无法启动(即使使用有效的参数),通常是因为 WebView 已确定后台请求可能会干扰用户的当前会话或安全状态。
- 取消: 使用
CancellationSignal终止正在进行的请求并阻止其被缓存。
Prerender(预渲染)网页
Prerender(预渲染)会创建隐藏的“网页内容”,以便在后台完全呈现网页,包括脚本执行和子资源提取。Prerender(预渲染)依赖于与预提取相同的底层基础架构。如果应用启动 Prerender(预渲染),WebView 会先预提取响应,以传送 Prerender(预渲染)导航,从而避免冗余的网络活动。
实现
Prerender(预渲染)是 WebView 实例级别的操作。从界面线程使用 WebViewCompat 调用 prerenderUrlAsync()。
Kotlin
WebViewCompat.prerenderUrlAsync(
webView,
url,
cancellationSignal,
executor,
params,
object : PrerenderOperationCallback {
override fun onPrerenderActivated() {
// Called when the user navigates to the URL and the hidden page is swapped in
}
override fun onError(exception: Throwable) {
// exception is an instance of PrerenderException
// Handle prerender failure (for example, memory pressure or disallowed JavaScript APIs)
}
}
)
Java
WebViewCompat.prerenderUrlAsync(webView, url, cancellationSignal, executor, params, new PrerenderOperationCallback() {
@Override
public void onPrerenderActivated() {
// Called when the user navigates to the URL and the hidden page is swapped in.
}
@Override
public void onError(@NonNull Throwable exception) {
// Handle prerender failure (for example, resource constraints or disallowed APIs).
}
});
预提取和 Prerender(预渲染)都是完全异步的。prefetchUrlAsync() 可以从任何线程调用,而 prerenderUrlAsync() 必须从界面线程启动。
技术限制
为了平衡即时导航与系统健康状况,WebView 强制执行以下运行时限制:
- 内存压力: 如果设备 RAM 不足,WebView 会取消预渲染的网址。
- 禁止使用的 API: JavaScript 在后台上下文中尝试访问某些 API(例如,音频播放、提醒)的任何操作都会立即终止 Prerender(预渲染)。
- 实例限制: 每个 WebView 允许的活跃预渲染网址数量有限制。
网址匹配和 No-Vary-Search (NVS)
WebView 需要可靠的匹配算法,以确保预加载的资源仅用于其预期的导航。
完全匹配与 NVS 匹配
默认情况下,预提取和 Prerender(预渲染)需要完全匹配的网址。如果导航的网址与预加载的网址相同,则会立即从缓存传送。如果查询参数不同,WebView 会使用以下 No-Vary-Search (NVS) 规则:
- 提示: 开发者在启动期间提供
setExpectedNoVarySearchHeader()提示。如果导航的网址与请求网址减去提示的参数匹配,WebView 会短暂阻止,以等待服务器的实际标头。 - 服务器标头: 来自服务器的 NVS 响应标头是最终权威。如果服务器确认应忽略查询差异,则匹配项会从缓存传送。否则,WebView 会回退到冷网络加载。
No-Vary-Search (NVS) 适用于高级使用场景,大多数开发者可能不需要它,因为他们会将完全相同的网址传递给预提取和
loadUrl()。只有当预提取网址和 loadUrl() 网址之间的查询参数存在差异时,才需要此指南。
全局配置
通过配置 PrefetchCache 限制和最大预渲染数,在配置文件级别调整推测加载行为。您还可以将自定义预提取限制重置为系统默认值:
Kotlin
// Configure prefetch cache limits
profile.prefetchCache.setMaxPrefetches(10)
profile.prefetchCache.setPrefetchTtlSeconds(60)
// Reset to system defaults when needed
profile.prefetchCache.clearMaxPrefetches()
// Configure maximum active prerenders
profile.setMaxPrerenders(2)
Java
// Configure prefetch cache limits
PrefetchCache prefetchCache = profile.getPrefetchCache();
prefetchCache.setMaxPrefetches(10);
prefetchCache.setPrefetchTtlSeconds(60);
// Reset to system defaults when needed
prefetchCache.clearMaxPrefetches();
// Configure maximum active prerenders
profile.setMaxPrerenders(2);
错误处理和异常
推测性操作使用 OutcomeReceiverCompat 或 PrerenderOperationCallback 来报告结果。
主要异常
当推测加载操作失败时,错误处理程序会报告以下主要异常类型之一,以帮助您诊断特定的失败场景:
PrefetchException: 所有异步预提取错误的基类。PrefetchNetworkException: 表示网络或服务器级别失败。 它可以包含httpStatusCode字段(例如,404 或 503),以帮助诊断服务器端问题。PrerenderException: 所有与 Prerender(预渲染)相关的错误的超类,例如因内存压力或在后台使用禁止使用的 API(如音频播放)而导致的失败。
优化策略
遵循以下建议,以最大限度地发挥推测性加载的优势,同时节省系统资源:
- 尽早启动: 在应用启动期间或导航目的地很可能出现时立即开始预提取。
- 集成策略: 如果您 Prerender(预渲染)已在预提取缓存中的网址,则 Prerender(预渲染)导航会从该缓存传送,从而避免冗余的网络请求。
- 监控配额: Prerender(预渲染)会占用大量资源。最好为几个可能的候选对象进行预提取,并为最有可能的导航保留 Prerender(预渲染)。
- 架构支持: 确保所有网址都使用强制性 HTTPS 架构。无效的架构或 null 输入会触发同步
IllegalArgumentException。
其他资源
如需详细了解如何调试 Web 应用、优化 WebView 启动性能以及处理渲染器进程终止,请参阅以下资源: