앱이 WebView를 처음 사용하는 경우 시스템은 특정 시작 작업을 실행합니다.
이 시작 프로세스는 무겁습니다. 기본적으로 애플리케이션이 android.webkit 또는 androidx.webkit 패키지 내에서 많은 API를 처음 호출하거나 WebView 태그가 포함된 레이아웃을 확장할 때 UI 스레드에서 암시적으로 발생합니다.
중요한 이유
이 암시적 시작은 기본 스레드에서 완전히 발생하므로 앱이 사용자 입력을 처리하지 못하게 되고 애플리케이션 응답 없음 (ANR) 오류의 위험이 크게 증가합니다. Android에서 단일 스레드 실행 모델을 처리하는 방법에 관한 자세한 내용은 프로세스 및 스레드 개요를 참고하세요.
암시적 시작 트리거
암시적 시작은 다음과 같은 방식으로 트리거될 수 있습니다.
- 프로그래매틱 방식:
WebSettings.getUserAgentString()와 같은 API 호출 - 레이아웃 사용:
<WebView>가 포함된 XML 리소스에서setContentView()또는layoutInflater.inflate()를 호출합니다.
암시적 시작은 앱 시작 시간, 최초 표시 시간과 같은 비즈니스 측정항목에도 부정적인 영향을 미칠 수 있습니다. 앱에 암시적 초기화가 최적화되어 있지 않다면 대신 startUpWebView를 사용하세요.
이 페이지에서는 startUpWebView API를 사용하여 WebView 시작 성능을 최적화하는 방법을 설명합니다.
WebView 시작 제어
성능을 개선하고 ANR을 최소화하려면 Jetpack Webkit 라이브러리에서 제공되는 startUpWebView API를 사용하세요. 이 API를 사용하면 WebView가 시작되는 시점을 명시적으로 제어할 수 있습니다. 시작 워크로드의 상당 부분을 백그라운드 스레드로 이동하고 UI 스레드에서 발생해야 하는 모든 작업을 하나의 큰 모놀리식 블록이 아닌 청크로 실행할 수 있습니다. 이렇게 하면 UI 스레드가 다른 중요한 앱 작업을 병렬로 처리할 수 있으므로 사용자 환경이 차단될 가능성이 줄어듭니다.
API는 androidx.webkit.WebViewOutcomeReceiver 콜백을 사용하여 성공적인 초기화를 추적할 수 있습니다.
이 API를 사용하려면 build.gradle 파일에 Jetpack Webkit 라이브러리를 추가하세요.
버전 1.16.0 이상을 사용해야 합니다.
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
startUpWebView API 사용
시작 흐름을 최적화하는 방법은 앱에서 실제로 WebView를 표시해야 하는 시점에 따라 다릅니다.
WebView가 중요 경로에 있지 않은 경우
앱에서 WebView를 즉시 로드할 필요가 없는 경우 초기화 비용을 완전히 숨길 수 있습니다. 앱의 수명 주기 초기에 startUpWebView을 호출하고 성공 콜백이 실행될 때까지 기다립니다.
다른 WebView API를 호출하기 전에 콜백을 기다리는 것이 좋습니다. startUpWebView를 트리거했지만 다른 WebView 구성요소를 터치하기 전에 완료될 때까지 기다리지 않으면 초기화가 완료될 때까지 시스템에서 UI 스레드를 차단합니다. 앱은 이미 완료된 백그라운드 작업에서 약간의 성능 이점을 얻을 수 있지만 최대 이점은 얻을 수 없습니다.
WebView가 중요 경로에 있는 경우
앱의 핵심 사용자 여정에 WebView가 즉시 필요한 경우 WebView 시작이 완료될 때까지 기다릴 수 없을 것입니다. 이 시나리오에서는 앱 수명 주기(예: Application.onCreate)에서 최대한 빨리 startUpWebView를 호출해야 하지만 콜백이 트리거될 때까지 기다리지 마세요. 대신 필요한 경우 WebView API를 직접 사용하세요.
비동기 시작을 최대한 활용하려면 실행할 다른 중요 경로 UI 스레드 작업 (예: 레이아웃 계층 구조 확장, 다른 SDK 초기화, 초기 프레임 그리기)이 남지 않을 때까지 WebView를 인스턴스화하거나 WebView API를 호출하는 것을 지연해야 합니다.
startUpWebView를 호출하고 기본 스레드에서 즉시 WebView API를 호출하면 초기화가 따라잡기를 기다리면서 UI 스레드가 차단됩니다. 이 시나리오에서는 성능 이점이 없습니다.
WebView 사용이 중요한 경로가 될 수 있지만 WebView를 완전히 시작하고 싶지 않은 경우 백그라운드 스레드에서 실행할 수 있는 WebView 시작 작업을 선택적으로 실행하여 다른 앱 중요 작업에 UI 스레드를 확보할 수 있습니다. 이를 위해 shouldRunUiThreadStartUpTasks(false)을 사용할 수 있습니다.
앱의 수명 주기 후반에 shouldRunUiThreadStartUpTasks(true)와 함께 startUpWebView를 다시 호출하여 UI 스레드에서 나머지 시작 작업을 완료할 수 있습니다. 이때 콜백을 기다릴지 여부는 WebView 사용이 중요한 경로에 있는지에 따라 다릅니다.
구현 예시
API는 androidx.webkit.WebViewOutcomeReceiver 콜백을 사용하여 성공적인 초기화를 추적하거나 진단 실패를 처리할 수 있습니다.
앱의 여러 부분에서 startUpWebView를 여러 번 호출해도 안전합니다. 단순한 재시도 루프를 구현하지 않는 것이 좋습니다.
다음 코드 샘플은 비동기 초기화를 위해 WebViewCompat.startUpWebView API를 사용하는 방법을 보여줍니다.
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)
}
}
)
}
자바
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
앱 시작 중에 WebView API를 트리거하는 APK에 삽입된
ContentProviders예상보다 일찍 발생하는 레이아웃 인플레이션 또는 프로그래매틱 호출 (예: 사용자 에이전트 문자열 가져오기)
이러한 예기치 않은 초기화가 발생하는 위치와 이유를 진단할 수 있도록 WebViewStartUpResult 객체는 다음과 같은 기본 제공 감사 기능을 제공합니다.
getUiThreadBlockingStartUpLocations(): WebView 시작 작업이 기본 UI 스레드를 차단한 위치를 나타내는StartUpLocation객체 목록을 반환합니다.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 시작을 트리거하거나 관리하기 위해 명시적이고 문서화되지 않은 해결 방법을 사용하는 경우 대신 startUpWebView API를 사용하는 것이 좋습니다. startUpWebView API는 Jetpack Webkit 라이브러리에서 지원하는 모든 버전의 Android 및 WebView에서 작동합니다.
Jetpack Webkit 구현을 사용하면 전체 Android 생태계에서 일관된 동작을 보장할 수 있습니다. 이 API의 주요 장점은 복원력입니다. 최신 최적화가 제공되지 않는 기존 기기에서 API는 수동 해결 방법과 성능 패리티를 유지합니다. 이를 통해 이전 기기에서 성능 저하 없이 최신 기기에서 최신 시작 혜택을 채택할 수 있습니다.
startUpWebView API에 문제가 발생하거나 의견이 있는 경우 공개 문제 추적기에 버그를 신고하세요.