WebViewCompat.navigate 是 WebView.loadUrl 的增强型替代方案,可对 WebView 中的网页加载、历史记录管理和导航生命周期跟踪进行精细控制。
之前,使用 loadUrl 启动网页导航存在明显的限制:
- 无法替换历史记录条目:您无法替换当前历史记录条目,因此无法在不向后退堆栈添加条目的情况下导航到新网页。
- 解耦的回调:在
WebViewClient中,没有直接机制将特定的loadUrl调用与后续回调事件相关联。 - 未保存额外的标头:传递给
loadUrl的自定义标头未保存为WebView状态的一部分,因此在恢复状态时会丢失。
WebViewCompat.navigate API 通过引入以下功能来解决这些问题:
- 导航历史记录条目替换:用于替换
WebView历史记录堆栈中的当前网页。 - 相关回调跟踪:返回一个
Navigation对象,该对象可作为导航生命周期所有阶段的唯一标识符。 - 已保存状态标头支持:额外的标头会可靠地保存在
WebView状态 bundle 中,以便在恢复状态时可以重复使用。
主要功能和限制
在采用 WebViewCompat.navigate 之前,请考虑以下操作规则和限制:
线程安全:您必须在界面(主)线程上调用
WebViewCompat.navigate。取消和优先级:无法明确取消飞行中的导航。不过,在同一
WebView上发起新的navigate调用会取代任何有效的导航。URI 协议支持:支持标准(例如
https:和http:)和自定义 URI 协议。不支持javascript:方案。网址大小限制:支持的网址字符串长度上限为 2 MB。
功能检查:在调用 API 之前,请务必使用
WebViewFeature.isFeatureSupported检查功能是否可用,以保持不同 WebView APK 版本之间的兼容性。
启动导航并跟踪生命周期
如需配置导航并跟踪其生命周期,请执行以下操作:
- 在
WebView设置期间使用WebViewCompat.addNavigationListener注册NavigationListener实现,以接收结构化的生命周期回调。注册一次监听器(而不是在每次导航调用时注册),以防止内存泄漏和重复执行回调。 - 使用
NavigationParameters.Builder构建NavigationParameters实例,以指定可选行为,例如历史记录替换或自定义 HTTP 标头。 - 调用
WebViewCompat.navigate,并传递WebView实例、目标网址和参数。
WebViewCompat.navigate 返回一个唯一标识相应请求的 Navigation 对象。在 NavigationListener 回调中,将此对象与传入的 Navigation 参数进行比较,以跟踪该特定导航。
实现示例
以下示例演示了如何配置导航参数、调用 WebViewCompat.navigate 以及监听导航生命周期事件:
Kotlin
class WebNavigationManager(private val webView: WebView) {
// Track the navigation instance returned by the API
private var currentNavigation: Navigation? = null
init {
// 1. Define listener to observe navigation lifecycle events
val listener = object : NavigationListener {
override fun onNavigationStarted(navigation: Navigation) {
if (navigation == currentNavigation) {
// Navigation started
}
}
override fun onNavigationRedirected(navigation: Navigation) {
if (navigation == currentNavigation) {
// Navigation encountered a redirect
}
}
override fun onNavigationCompleted(navigation: Navigation) {
if (navigation == currentNavigation) {
if (navigation.didCommit()) {
// Navigation committed successfully
} else if (navigation.didCommitErrorPage()) {
// Navigation committed an error page
val statusCode = navigation.statusCode
val error = navigation.webResourceError
}
}
}
override fun onFirstContentfulPaintMillis(page: Page, durationMillis: Long) {
// Match page with current navigation
if (page == currentNavigation?.page) {
// Page rendering started (First Contentful Paint achieved)
}
}
}
// 2. Register listener on the main thread
WebViewCompat.addNavigationListener(webView, listener)
}
@UiThread
fun navigateToPage(url: String) {
// Check feature availability
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
// Fall back to standard loadUrl if navigate API is unavailable
webView.loadUrl(url)
return
}
// 3. Configure navigation parameters
val params = NavigationParameters.Builder()
.setShouldReplaceCurrentEntry(true)
.addAdditionalHeaders(
mapOf("X-Test-Navigate-Header" to "TestValue")
)
.build()
// 4. Initiate navigation on the UI thread
currentNavigation = WebViewCompat.navigate(webView, url, params)
}
}
Java
public class WebNavigationManager {
private Navigation mCurrentNavigation;
private final WebView mWebView;
public WebNavigationManager(@NonNull WebView webView) {
mWebView = webView;
setupListener();
}
private void setupListener() {
// 1. Define listener to observe navigation lifecycle events
NavigationListener listener = new NavigationListener() {
@Override
public void onNavigationStarted(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
// Navigation started
}
}
@Override
public void onNavigationRedirected(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
// Navigation encountered a redirect
}
}
@Override
public void onNavigationCompleted(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
if (navigation.didCommit()) {
// Navigation committed successfully
} else if (navigation.didCommitErrorPage()) {
// Navigation committed an error page
int statusCode = navigation.getStatusCode();
WebResourceErrorCompat error = navigation.getWebResourceError();
}
}
}
@Override
public void onFirstContentfulPaintMillis(@NonNull Page page, long durationMillis) {
if (mCurrentNavigation != null && page.equals(mCurrentNavigation.getPage())) {
// Page rendering started (First Contentful Paint achieved)
}
}
};
// 2. Register listener on the main thread
WebViewCompat.addNavigationListener(mWebView, listener);
}
@UiThread
public void navigateToPage(@NonNull String url) {
// Check feature availability
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
// Fall back to standard loadUrl if navigate API is unavailable
mWebView.loadUrl(url);
return;
}
// 3. Configure navigation parameters
NavigationParameters params = new NavigationParameters.Builder()
.setShouldReplaceCurrentEntry(true)
.addAdditionalHeaders(Collections.singletonMap(
"X-Test-Navigate-Header", "TestValue"
))
.build();
// 4. Initiate navigation on the UI thread
mCurrentNavigation = WebViewCompat.navigate(mWebView, url, params);
}
}
故障模式和错误处理
WebViewCompat.navigate API 提供了用于处理配置错误和运行时导航失败的不同机制:
参数无效异常
传递无效实参会触发同步 IllegalArgumentException。常见原因包括:
- 为必需的非 null 参数(
webView、url或params)传递null。 - 提供不受支持的网址协议,例如
javascript:。 - 传递格式错误且不符合 RFC 2616 规范的 HTTP 标头键或值。
导航流程错误
如果在网络请求或网页加载期间发生故障(例如 HTTP 404 状态代码、DNS 解析失败或 SSL 错误),WebViewCompat.navigate 仍会返回有效的 Navigation 对象。
导航完成后,检查 onNavigationCompleted 回调中的 Navigation 实例上的以下方法,以诊断失败原因:
getStatusCode:返回 HTTP 响应状态代码(例如404或500)。getWebResourceError:返回一个WebResourceErrorCompat对象,其中详细说明了网络错误,例如连接超时或主机查找失败。didCommitErrorPage:表示WebView是否已提交并向用户显示错误页面。didCommit:表示导航是否成功提交到目标网页,而未被中止。
保存状态软件包管理
当您使用 NavigationParameters 传递其他标头时,WebView 会将这些标头保存在其已保存的状态软件包中,以便在恢复状态时可以重复使用这些标头。不过,大量标头可能会大幅增加保存状态 Bundle 的大小。
如果您需要限制软件包大小以防止在保存 Android 状态期间出现 TransactionTooLargeException,请使用 WebViewCompat.saveState。此方法可让您设置以字节为单位的最大软件包大小限制,并可选择性地排除前向历史记录项:
Kotlin
// Save state with a maximum bundle size limit (for example, 64 KB)
val maxSizeBytes = 64 * 1024
val includeForwardState = false
val outState = Bundle()
WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState)
Java
// Save state with a maximum bundle size limit (for example, 64 KB)
int maxSizeBytes = 64 * 1024;
boolean includeForwardState = false;
Bundle outState = new Bundle();
WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState);
生成的软件包仍与标准 WebView.restoreState 方法兼容。
迁移和实施建议
为确保在 WebView 中导航时获得最佳性能和稳定性,请遵循以下建议:
从
loadUrl迁移到navigate:将所有旧版WebView.loadUrl调用迁移到WebViewCompat.navigate。这样可确保历史记录管理的一致性,并确保标头始终作为保存状态的一部分进行保存。始终验证功能支持:在调用 API 之前,请使用
WebViewFeature.isFeatureSupported确认运行时支持,以防出现旧版 WebView。关联导航实例:使用返回的
Navigation对象来区分并发导航或在管理多个WebView实例时过滤回调。在初始化期间注册一次监听器:由于
WebViewCompat.addNavigationListener会添加监听器,而不是替换现有监听器,因此请在WebView设置期间注册一次NavigationListener,以避免内存泄漏和在后续导航中重复执行回调。监控保存状态大小:传递大型标头载荷时,请使用具有明确大小边界的
WebViewCompat.saveState,以避免保存过多的状态数据。
其他资源
如需详细了解嵌入式 Web 功能和性能优化,请参阅以下指南: