Когда ваше приложение впервые использует WebView, система выполняет определенные задачи при запуске. Этот процесс запуска является ресурсоемким. По умолчанию он происходит неявно в потоке пользовательского интерфейса при первом вызове приложением многих API из пакетов android.webkit или androidx.webkit , или при создании макета, содержащего тег WebView.
Почему это важно
Поскольку этот неявный запуск происходит полностью в основном потоке, он блокирует обработку пользовательского ввода вашим приложением и значительно увеличивает риск ошибок "Приложение не отвечает" (ANR). Для получения дополнительной информации о том, как Android обрабатывает однопоточную модель выполнения, см. раздел "Обзор процессов и потоков" .
Триггеры для неявного запуска
Неявный запуск может быть инициирован следующими способами:
- Программно : Вызов API, таких как
WebSettings.getUserAgentString(). - Использование макетов : вызов
setContentView()илиlayoutInflater.inflate()для XML-ресурса, содержащего элемент<WebView>.
Неявная инициализация также может негативно повлиять на ваши бизнес-показатели, такие как время запуска приложения и время до первого отображения. Если неявная инициализация не оптимальна для вашего приложения, используйте вместо неё startUpWebView .
На этой странице обсуждается, как оптимизировать производительность запуска WebView с помощью API startUpWebView .
Возьмите под контроль запуск WebView.
Для повышения производительности и минимизации ошибок ANR используйте API startUpWebView , доступный в библиотеке Jetpack Webkit. Этот API предоставляет вам явный контроль над моментом запуска WebView. Он переносит значительную часть нагрузки по запуску в фоновый поток и позволяет выполнять любую работу, которая должна выполняться в потоке пользовательского интерфейса, поэтапно, а не одним большим монолитным блоком. Это освобождает поток пользовательского интерфейса для параллельной обработки других важных задач приложения, снижая вероятность блокировки пользовательского интерфейса.
API использует функцию обратного вызова androidx.webkit.WebViewOutcomeReceiver , позволяющую отслеживать успешную инициализацию.
Для использования этого API добавьте библиотеку Jetpack Webkit в файл build.gradle . Убедитесь, что вы используете версию 1.16.0 или выше:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
Используйте API startUpWebView
Способ оптимизации процесса запуска зависит от того, когда вашему приложению действительно необходимо отобразить WebView.
Когда WebView не находится на критическом пути
Если вашему приложению не требуется немедленная загрузка WebView, вы можете полностью скрыть затраты на инициализацию. Вызовите startUpWebView в начале жизненного цикла приложения и дождитесь срабатывания обратного вызова при успешном завершении процесса.
В идеале, перед вызовом других API WebView следует дождаться завершения обратного вызова. Если вы запускаете startUpWebView , но не дожидаетсяе его завершения, прежде чем обращаться к другим компонентам WebView, система блокирует поток пользовательского интерфейса, ожидая завершения инициализации. Ваше приложение может получить некоторую выгоду в производительности от уже выполненной фоновой работы, но не максимальную.
Когда WebView находится на критическом пути
Если основной пользовательский сценарий вашего приложения требует немедленного доступа к WebView, вы, вероятно, не можете позволить себе ждать завершения запуска WebView. В этом случае вам все равно следует вызывать startUpWebView как можно раньше в жизненном цикле приложения (например, в Application.onCreate ), но не ждать срабатывания обратного вызова. Вместо этого используйте API WebView напрямую, когда это необходимо.
Чтобы получить максимальную выгоду от асинхронного запуска, крайне важно отложить создание экземпляра WebView или вызов API WebView до тех пор, пока не останется других критически важных операций в потоке пользовательского интерфейса (таких как создание иерархий макета, инициализация других SDK или отрисовка начального кадра).
Если вызвать startUpWebView и сразу же после этого вызвать API WebView в основном потоке, поток пользовательского интерфейса заблокируется, ожидая завершения инициализации. В этом случае повышения производительности не будет.
Если использование WebView становится критически важным, но вы не хотите запускать WebView полностью, вы можете выборочно запускать задачи запуска WebView, которые могут выполняться в фоновом потоке, освобождая поток пользовательского интерфейса для других критически важных задач приложения. Для этого можно использовать shouldRunUiThreadStartUpTasks(false) .
Позже, на более позднем этапе жизненного цикла вашего приложения, вы можете снова вызвать startUpWebView с параметром shouldRunUiThreadStartUpTasks(true) чтобы завершить оставшиеся задачи запуска в потоке пользовательского интерфейса. Необходимость ожидания обратного вызова в этот момент зависит от того, находится ли использование WebView на критическом пути.
Пример реализации
API использует функцию обратного вызова androidx.webkit.WebViewOutcomeReceiver , позволяющую отслеживать успешную инициализацию или обрабатывать диагностические ошибки.
Вызов функции startUpWebView несколько раз из разных частей приложения безопасен. Мы рекомендуем избегать реализации наивного цикла повторных попыток.
Приведенный ниже пример кода демонстрирует, как использовать API WebViewCompat.startUpWebView для асинхронной инициализации.
Котлин
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 инициализируются на ранних этапах жизненного цикла приложения.
В ваш APK-файл внедряются
ContentProviders, которые запускают API WebView во время запуска приложения.Неожиданно рано происходят изменения в макете или программные вызовы (например, получение строк пользовательского агента).
Для того чтобы помочь вам определить, где и почему происходят эти неожиданные инициализации, объект 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» .
Если у вас возникли проблемы или есть замечания по API startUpWebView , сообщите об ошибке в общедоступном трекере проблем .