WebViewCompat.navigate — это улучшенная альтернатива WebView.loadUrl , обеспечивающая точный контроль над загрузкой веб-страниц, управлением историей и отслеживанием жизненного цикла навигации в WebView .
Ранее запуск навигации по страницам с помощью loadUrl имел существенные ограничения:
- Замена записи в истории невозможна: вы не можете заменить текущую запись в истории, что делает невозможным переход на новую страницу без добавления записи в стек возврата.
- Разделение коллбэков: В
WebViewClientотсутствовал прямой механизм для сопоставления конкретного вызоваloadUrlс последующими событиями коллбэка. - Дополнительные заголовки не были сохранены: пользовательские заголовки, переданные в
loadUrlне были сохранены как часть состоянияWebView, поэтому они были потеряны при восстановлении состояния.
API WebViewCompat.navigate решает эти проблемы, предоставляя следующие возможности:
- Замена записи в истории навигации: позволяет заменить текущую страницу в стеке истории
WebView. - Отслеживание коррелированных обратных вызовов: Возвращает объект
Navigation, который служит уникальным идентификатором на всех этапах жизненного цикла навигации. - Поддержка заголовков сохраненного состояния: Дополнительные заголовки надежно сохраняются в пакете состояния
WebView, чтобы их можно было повторно использовать при восстановлении состояния.
Основные возможности и ограничения
Прежде чем использовать WebViewCompat.navigate , следует учесть следующие правила и ограничения работы:
Потокобезопасность: вызов
WebViewCompat.navigateнеобходимо выполнять в основном потоке пользовательского интерфейса.Отмена и приоритет: Навигацию, выполняющуюся в процессе, нельзя отменить явным образом. Однако инициирование нового вызова
navigateв том жеWebViewотменяет любую активную навигацию.Поддержка схем URI: Поддерживаются стандартные (например,
https:иhttp::) и пользовательские схемы URI. Схемаjavascript:не поддерживается.Ограничение на размер URL-адреса: максимальная поддерживаемая длина строки URL-адреса составляет 2 МБ.
Проверка наличия функций: Всегда проверяйте доступность функций с помощью
WebViewFeature.isFeatureSupportedперед вызовом API, чтобы обеспечить совместимость с различными версиями APK WebView.
Инициировать навигацию и отслеживать жизненный цикл.
Для настройки навигации и отслеживания её жизненного цикла выполните следующие действия:
- Зарегистрируйте реализацию
NavigationListenerс помощьюWebViewCompat.addNavigationListenerво время настройкиWebView, чтобы получать структурированные коллбэки жизненного цикла. Зарегистрируйте слушатель один раз (а не при каждом вызове навигации), чтобы предотвратить утечки памяти и дублирование выполнения коллбэков. - Создайте экземпляр
NavigationParametersс помощьюNavigationParameters.Builder, чтобы указать необязательные параметры поведения, такие как замена истории или пользовательские HTTP-заголовки. - Вызовите метод
WebViewCompat.navigate, передав в качестве параметров экземплярWebView, целевой URL и другие параметры.
WebViewCompat.navigate возвращает объект Navigation , который однозначно идентифицирует запрос. В функциях обратного вызова NavigationListener сравните этот объект с входящим параметром Navigation , чтобы отслеживать конкретную навигацию.
Пример реализации
В следующем примере показано, как настроить параметры навигации, вызвать WebViewCompat.navigate и отслеживать события жизненного цикла навигации:
Котлин
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);
}
}
Режимы отказов и обработка ошибок
API WebViewCompat.navigate предоставляет различные механизмы для обработки ошибок конфигурации и сбоев навигации во время выполнения:
Исключения, связанные с недопустимыми аргументами.
Передача недопустимых аргументов приводит к синхронному IllegalArgumentException . К распространенным причинам относятся следующие:
- Передача значения
nullдля обязательных ненулевых параметров (webView,urlилиparams). - Указание неподдерживаемой схемы URL, например,
javascript:. - Передача некорректных ключей или значений HTTP-заголовков, не соответствующих спецификациям RFC 2616.
Ошибки в процессе навигации
Если во время сетевого запроса или загрузки страницы происходит сбой (например, код состояния HTTP 404, ошибка разрешения DNS или ошибка SSL), WebViewCompat.navigate все равно возвращает действительный объект Navigation .
После завершения навигации проверьте следующие методы экземпляра Navigation внутри вашего коллбэка onNavigationCompleted , чтобы диагностировать причину ошибки:
-
getStatusCode: Возвращает код состояния HTTP-ответа (например,404или500). -
getWebResourceError: Возвращает объектWebResourceErrorCompat, содержащий подробную информацию о сетевых ошибках, таких как таймауты подключения или ошибки поиска хоста. -
didCommitErrorPage: Указывает, выполнил лиWebViewоперацию фиксации и отобразил ли пользователю страницу ошибки. -
didCommit: Указывает, успешно ли была зафиксирована навигация на целевой странице без прерывания.
Сохранение управления пакетами состояний
При передаче дополнительных заголовков с помощью NavigationParameters , WebView сохраняет эти заголовки в своем пакете сохраненного состояния, чтобы их можно было повторно использовать при восстановлении состояния. Однако большие наборы заголовков могут существенно увеличить размер Bundle сохраненного состояния.
Если вам необходимо ограничить размер пакета, чтобы предотвратить исключение TransactionTooLargeException во время сохранения состояния Android, используйте WebViewCompat.saveState . Этот метод позволяет установить максимальный размер пакета в байтах и, при необходимости, исключить элементы истории:
Котлин
// 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добавляет слушатель, а не заменяет существующий, зарегистрируйте свойNavigationListenerодин раз во время настройкиWebView, чтобы избежать утечек памяти и дублирования выполнения обратных вызовов при последующих переходах.Контролируйте размер сохраняемого состояния: при передаче больших заголовочных файлов используйте
WebViewCompat.saveStateс явным указанием границ размера, чтобы избежать сохранения избыточных данных состояния.
Дополнительные ресурсы
Чтобы узнать больше о возможностях встроенного веб-интерфейса и оптимизации производительности, ознакомьтесь со следующими руководствами:
- Упростите реализацию WebView с помощью Jetpack Webkit.
- Спекулятивная загрузка в WebView
- Оптимизация запуска WebView
- Обработка завершения процесса рендеринга WebView