Когда приложение впервые использует WebView, система выполняет определенные задачи по запуску.
Процесс запуска ресурсоемкий. По умолчанию это происходит неявно в потоке UI, когда приложение впервые вызывает API из пакетов android.webkit или androidx.webkit или разворачивает макет, содержащий тег WebView.
Почему это важно
Поскольку этот неявный запуск происходит полностью в основном потоке, он блокирует обработку пользовательского ввода в приложении и значительно увеличивает риск возникновения ошибок "Приложение не отвечает" (ANR). Дополнительную информацию о том, как Android обрабатывает однопоточную модель выполнения, можно найти в статье Обзор процессов и потоков.
Триггеры для неявного запуска
Неявный запуск может быть вызван следующими способами:
- Программно – вызов API, например
WebSettings.getUserAgentString(). - Использование макетов. Вызов
setContentView()илиlayoutInflater.inflate()для ресурса XML, который включает<WebView>.
Неявный запуск также может негативно повлиять на бизнес-показатели, такие как время запуска приложения и время до первого показа. Если неявная инициализация не подходит для вашего приложения, используйте startUpWebView.
В этой статье рассказывается, как оптимизировать запуск WebView с помощью startUpWebView API.
Управляйте запуском WebView
Чтобы повысить производительность и сократить количество ошибок ANR, используйте API startUpWebView, доступный в библиотеке Jetpack Webkit. Этот API позволяет точно контролировать, когда запускается WebView. Она переносит значительную часть рабочей нагрузки при запуске в фоновый поток и позволяет выполнять любую работу, которая должна происходить в потоке UI, частями, а не одним большим монолитным блоком. Это освобождает поток UI для параллельной обработки других критически важных задач приложения, снижая вероятность блокировки пользовательского интерфейса.
API использует обратный вызов androidx.webkit.WebViewOutcomeReceiver, позволяющий отслеживать успешные инициализации.
Чтобы использовать этот API, добавьте библиотеку Jetpack Webkit в файл build.gradle.
Убедитесь, что вы используете версию 1.16.0 или более позднюю:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
Как использовать startUpWebView API
Способ оптимизации процесса запуска зависит от того, когда в приложении должен показываться компонент WebView.
Когда WebView не находится на критическом пути
Если вашему приложению не нужно сразу загружать WebView, вы можете полностью скрыть затраты на инициализацию. Вызовите startUpWebView на раннем этапе жизненного цикла приложения и дождитесь обратного вызова.
Рекомендуем дождаться обратного вызова, прежде чем вызывать другие API WebView. Если вы активируете startUpWebView, но не дождетесь завершения инициализации, прежде чем использовать другие компоненты WebView, система заблокирует поток UI. Приложение может получить некоторую выгоду от фоновой работы, которая уже была выполнена, но не максимальную.
Когда WebView находится на критическом пути
Если основной путь пользователя в приложении сразу требует WebView, вы, вероятно, не можете позволить себе ждать завершения запуска WebView. В этом случае вам все равно нужно вызвать startUpWebView как можно раньше в жизненном цикле приложения (например, в Application.onCreate), но не ждать обратного вызова. Вместо этого используйте WebView API напрямую, когда это необходимо.
Чтобы получить максимальную пользу от асинхронного запуска, отложите создание экземпляра WebView или вызов API WebView до тех пор, пока не будут выполнены все остальные операции потока UI, имеющие критическое значение (например, развертывание иерархий макетов, инициализация других SDK или отрисовка начального кадра).
Если вы вызовете startUpWebView и сразу после этого вызовете WebView API в основном потоке, поток UI будет заблокирован, пока не завершится инициализация. В этом случае эффективность не повысится.
Если использование WebView может стать критическим путем, но вы не хотите полностью запускать WebView, вы можете выборочно запустить задачи запуска WebView, которые могут выполняться в фоновом потоке, освобождая поток UI для других критически важных задач приложения. Для этого можно использовать shouldRunUiThreadStartUpTasks(false).
Позже в жизненном цикле приложения вы можете снова вызвать startUpWebView с помощью shouldRunUiThreadStartUpTasks(true), чтобы завершить оставшиеся задачи запуска в потоке UI. Ждать ли обратного вызова в этот момент, зависит от того, является ли использование WebView критически важным.
Пример реализации
API использует обратный вызов androidx.webkit.WebViewOutcomeReceiver, позволяющий отслеживать успешные инициализации или обрабатывать ошибки диагностики.
Метод startUpWebView можно безопасно вызывать несколько раз из разных частей приложения. Не рекомендуем реализовывать простой цикл повторных попыток.
В приведенном ниже примере кода показано, как использовать API WebViewCompat.startUpWebView для асинхронной инициализации.
Kotlin
import android.content.Context
import android.util.Log
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewOutcomeReceiver
import androidx.webkit.WebViewStartUpConfig
import androidx.webkit.WebViewStartUpResult
import androidx.webkit.WebViewStartupException
import java.util.concurrent.Executors
fun initializeWebView(context: Context) {
// 1. Create a startup configuration specifying the background thread
// that WebView will use to run its initialization tasks.
val startUpConfig = WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build()
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
object : WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException> {
override fun onResult(result: WebViewStartUpResult) {
// Success: The WebView has finished its background initialization.
// This callback is guaranteed to be invoked on the UI thread.
setupWebView()
}
override fun onError(error: WebViewStartupException) {
// Failure: The initialization encountered a startup exception.
Log.e("WebViewStartup", "Failed to initialize WebView", error)
}
}
)
}
Java
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewOutcomeReceiver;
import androidx.webkit.WebViewStartUpConfig;
import androidx.webkit.WebViewStartUpResult;
import androidx.webkit.WebViewStartupException;
import java.util.concurrent.Executors;
public void initializeWebView(Context context) {
// 1. Create the startup configuration specifying the background thread pool
// to handle internal non-UI initialization processes.
WebViewStartUpConfig startUpConfig = new WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build();
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
new WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException>() {
@Override
public void onResult(@NonNull WebViewStartUpResult result) {
// Success: The WebView has finished its background initialization.
// This callback is invoked directly on the UI thread.
setupWebView();
}
@Override
public void onError(@NonNull WebViewStartupException error) {
// Failure: Handled using the concrete WebViewStartupException
Log.e("WebViewStartup", "Failed to initialize WebView", error);
}
}
);
}
Как устранять проблемы с асинхронным запуском
Если startUpWebView не дает ожидаемого повышения производительности, часто это связано с тем, что WebView инициализируется неявно в другом месте вашего приложения до выполнения вызова. Это может быть связано со следующими причинами:
Сторонние библиотеки или SDK, инициализируемые на ранних этапах жизненного цикла приложения.
ContentProviders, внедренный в APK-файл, который активирует WebView API при запуске приложения.Развертывание макетов или программные вызовы (например, получение строк агента пользователя), которые происходят неожиданно рано.
Чтобы вам было проще понять, где и почему происходят такие инициализации, в объекте WebViewStartUpResult предусмотрены встроенные возможности аудита.
getUiThreadBlockingStartUpLocations()– возвращает список объектовStartUpLocation, представляющих места, где задачи запуска WebView заблокировали основной поток пользовательского интерфейса.getNonUiThreadBlockingStartUpLocations()– возвращает определенные сайты вызовов, на которых выполнение задач запуска блокировало фоновые потоки.
Каждый объект StartUpLocation содержит трассировку стека, которую можно зарегистрировать или проверить, чтобы найти класс и метод, запустившие инициализацию.
Пример реализации
Вы можете проверить эти места в обратном вызове onResult, чтобы проанализировать путь запуска:
override fun onResult(result: WebViewStartUpResult) {
// Check if WebView startup was blocked on the UI thread prior to or during initialization
val uiBlockingLocations = result.getUiThreadBlockingStartUpLocations()
if (!uiBlockingLocations.isNullOrEmpty()) {
for (location in uiBlockingLocations) {
// Log the stack trace of the call site that triggered the UI-blocking startup
Log.w("WebViewDebug", "WebView startup blocked the UI thread here:", location.getStack())
}
} else {
Log.i("WebViewDebug", "Excellent! No UI-blocking WebView startup detected.")
}
// Check where background initialization tasks were executed
val backgroundLocations = result.getNonUiThreadBlockingStartUpLocations()
backgroundLocations?.forEach { location ->
Log.d("WebViewDebug", "WebView background startup occurred at: ${location.getStack()}")
}
setupWebView()
}
Как использовать эти данные во время аудита
При аудите запуска WebView в приложении используйте следующие стратегии для анализа диагностических данных и устранения проблем с производительностью:
Проверьте, нет ли неожиданных трассировок стека. Если
getUiThreadBlockingStartUpLocations()не пуст, посмотрите на трассировки стека. Если вы видите классы, принадлежащие сторонним SDK или неожиданным компонентам, вы нашли узкое место неявной инициализации.Проверьте порядок вызовов. Если в журнале указано, что неявная инициализация произошла до вызова
startUpWebView, перенесите инициализациюstartUpWebViewна более ранний этап или настройте SDK так, чтобы задачи, зависящие от WebView, выполнялись позже.
Как перейти с предыдущих обходных решений
Раньше вы могли использовать явные обходные пути, чтобы принудительно инициализировать WebView в фоновом потоке, например получать строку агента пользователя.
Эти обходные пути не поддерживаются, и их поведение может измениться в будущих выпусках. Если в вашем приложении используются явные недокументированные обходные решения для запуска WebView или управления им, рекомендуем вместо них использовать API startUpWebView. API startUpWebView работает во всех версиях Android и WebView, которые поддерживаются библиотекой Jetpack Webkit.
Обеспечьте стабильность и отказоустойчивость приложения
Использование реализации Jetpack Webkit помогает обеспечить согласованное поведение во всей экосистеме Android. Ключевое преимущество этого API – его устойчивость: на устаревших устройствах, где новые оптимизации недоступны, API поддерживает производительность на уровне ручных обходных решений. Это позволяет использовать преимущества современного запуска на новых устройствах без снижения производительности на старых.
Оптимизация запуска WebView снижает риск ошибок ANR при запуске приложения, но вам также следует защитить приложение от сбоев рендеринга во время выполнения и возврата системной памяти. Чтобы обеспечить стабильность приложения после запуска WebView, ознакомьтесь с разделом Обработка завершения работы WebView. Кроме того, сохраняйте пользовательские данные при завершении фоновых процессов с помощью WebView.saveState() и следуйте рекомендациям по безопасности памяти в статье Эффективное управление состоянием WebView.
Если у вас возникли проблемы или вы хотите оставить отзыв об API startUpWebView, сообщите об этом в общедоступной системе отслеживания ошибок.