When your app first uses a WebView, the system performs specific startup tasks.
This startup process is heavy. By default, it happens implicitly on the UI
thread the first time the application calls many APIs within the
android.webkit or androidx.webkit packages, or inflates a layout that
contains a WebView tag.
Why this matters
Because this implicit startup happens entirely on the main thread, it blocks your app from processing user input and drastically increases the risk of Application Not Responding (ANR) errors. For more information about how Android handles the single-threaded execution model, see Processes and threads overview.
Triggers for implicit startup
Implicit startup can be triggered in the following ways:
- Programmatically: Calling APIs like
WebSettings.getUserAgentString(). - Using layouts: Calling
setContentView()orlayoutInflater.inflate()on an XML resource that includes a<WebView>.
Implicit startup can also negatively affect your business metrics, such as app
startup time and time-to-first-display. If implicit initialization isn't
optimal for your app, use startUpWebView instead.
This page discusses how to optimize WebView startup performance using the
startUpWebView API.
Take control of WebView startup
To improve performance and minimize ANRs, use the startUpWebView API available
in the Jetpack Webkit library. This API gives you explicit control over when
WebView starts up. It shifts a significant amount of the startup workload to a
background thread and enables any work that must happen on the UI thread to be
done in chunks, rather than one big monolithic block. This frees up your UI
thread to handle other critical app tasks in parallel, reducing the chance of
blocking the user experience.
The API uses the androidx.webkit.WebViewOutcomeReceiver callback, letting you
track successful initializations.
To use this API, add the Jetpack Webkit library to your build.gradle file.
Ensure you use version 1.16.0 or higher:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
Use the startUpWebView API
How you optimize your startup flow depends on when your app actually needs to display the WebView.
When WebView isn't on the critical path
If your app doesn't need to load a WebView immediately, you can hide the
initialization cost completely. Call startUpWebView early in your app's
lifecycle and wait for the success callback to fire.
Ideally, you should wait for the callback before calling other WebView APIs. If
you trigger startUpWebView but don't wait for it to finish before touching
other WebView components, the system blocks the UI thread while waiting for the
initialization to complete. Your app might get some performance benefit from
the background work already completed, but not the maximum benefit.
When WebView is on the critical path
If your app's core user journey requires a WebView immediately, you probably
can't afford to wait for WebView startup to complete. In this scenario, you
should still call startUpWebView as early as possible in the app lifecycle
(such as in Application.onCreate), but don't wait for the callback to
trigger. Instead, use WebView APIs directly when they are required.
To get the maximum benefit from asynchronous startup, crucially defer instantiating a WebView or calling WebView APIs until there are no other critical-path UI thread operations left to run (such as inflating layout hierarchies, initializing other SDKs, or drawing the initial frame).
If you call startUpWebView and immediately invoke WebView APIs afterward on
the main thread, the UI thread blocks waiting for the initialization to catch
up. In this scenario, there is no performance benefit.
If WebView usage can become on the critical path but you don't want to startup
WebView entirely, you can choose to selectively run the WebView startup tasks
that are capable of running on a background thread, freeing up the UI thread for
other app critical tasks. For this, you can use
shouldRunUiThreadStartUpTasks(false).
Later in your app's lifecycle, you can call startUpWebView again with
shouldRunUiThreadStartUpTasks(true) to finish the remaining startup tasks on
the UI thread. Whether you wait for the callback at that point depends on
whether WebView usage is on the critical path.
Implementation example
The API uses the androidx.webkit.WebViewOutcomeReceiver callback, letting you
track successful initializations or handle diagnostic failures.
It is safe to call startUpWebView multiple times from different parts of your
app. We recommend that you avoid implementing a naive retry loop.
The following code sample demonstrates how to use the
WebViewCompat.startUpWebView API for asynchronous initialization.
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);
}
}
);
}
Debug asynchronous startup issues
If startUpWebView doesn't yield expected performance benefits, it's often
because WebView is being initialized implicitly elsewhere in your app before
your call executes. This could be due to the following reasons:
Third-party libraries or SDKs initialized early in the app lifecycle.
ContentProvidersinjected into your APK that trigger WebView APIs during app startup.Layout inflations or programmatic calls (like fetching user agent strings) that occur unexpectedly early.
To help you diagnose where and why these unexpected initializations are
happening, the WebViewStartUpResult object provides built-in auditing
capabilities:
getUiThreadBlockingStartUpLocations(): Returns a list ofStartUpLocationobjects representing locations where WebView startup tasks blocked the main UI thread.getNonUiThreadBlockingStartUpLocations(): Returns specific call sites where running startup tasks blocked background threads.
Each StartUpLocation contains a stack trace that you can log or inspect to
find the exact class and method that triggered the initialization.
Implementation example
You can inspect these locations inside your onResult callback to audit your
startup path:
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()
}
How to use this data during an audit
When auditing your app's WebView startup, use the following strategies to analyze the diagnostic data and address performance bottlenecks:
Look for unexpected stack traces: If
getUiThreadBlockingStartUpLocations()isn't empty, look at the printed stack traces. If you see classes belonging to third-party SDKs or unexpected components, you have found an implicit initialization bottleneck.Verify call order: If your log outputs show that an implicit initialization occurred before your manual
startUpWebViewcall, you should move yourstartUpWebViewinitialization earlier in your app or configure the offending SDK to delay its WebView-dependent tasks.
Migrate from previous workarounds
In the past, you might have used explicit workarounds to force WebView initialization on a background thread, such as fetching the user agent string.
These workarounds are considered unsupported practices, and their underlying
behavior can change in upcoming releases. If your app relies on any explicit,
undocumented workarounds to trigger or manage WebView startup, we recommend
using the startUpWebView API instead. The startUpWebView API works on all
versions of Android and WebView that are supported by the Jetpack Webkit
library.
Using the Jetpack Webkit implementation helps ensure consistent behavior across the entire Android ecosystem. A key advantage of this API is its resilience: on legacy devices where newer optimizations aren't available, the API maintains performance parity with manual workarounds. This lets you adopt modern startup benefits on newer devices without incurring a performance penalty on older ones.
If you encounter issues or have feedback about the startUpWebView API, file a
bug on the public issue tracker.