WebViewCompat.navigate는 WebView에서 웹페이지 로드,
기록 관리, 탐색 수명 주기 추적을 세부적으로 제어할 수 있는
WebView.loadUrl의 향상된 대안입니다.
이전에는 loadUrl을 사용하여 페이지 탐색을 시작하는 데 다음과 같은 주목할 만한 제한사항이 있었습니다.
- 기록 항목 대체 없음: 현재 기록 항목을 대체할 수 없으므로 백 스택에 항목을 추가하지 않고는 새 페이지로 이동할 수 없었습니다.
- 분리된 콜백: 특정
loadUrl호출을WebViewClient의 후속 콜백 이벤트와 연결하는 직접적인 메커니즘이 없었습니다. - 추가 헤더가 저장되지 않음:
loadUrl에 전달된 커스텀 헤더는WebView상태의 일부로 저장되지 않았으므로 상태를 복원할 때 손실되었습니다.
WebViewCompat.navigate API는 다음과 같은 기능을 도입하여 이러한 문제를 해결합니다.
- 탐색 기록 항목 대체:
WebView기록 스택에서 현재 페이지를 대체할 수 있습니다. - 상관된 콜백 추적: 탐색 수명 주기의 모든 단계에서 고유 식별자 역할을 하는
Navigation객체를 반환합니다. - 저장된 상태 헤더 지원: 추가 헤더는 상태 복원 시 재사용할 수 있도록
WebView상태 번들에 안정적으로 저장됩니다.
주요 기능 및 제한사항
WebViewCompat.navigate를 채택하기 전에 다음 운영 규칙 및 제약조건을 고려하세요.
스레드 안전: UI(기본) 스레드에서
WebViewCompat.navigate를 호출해야 합니다.취소 및 우선순위: 진행 중인 탐색은 명시적으로 취소할 수 없습니다. 하지만 동일한
WebView에서 새navigate호출을 시작하면 활성 탐색이 대체됩니다.URI 스키마 지원: 표준 (
https:및http:등) 및 커스텀 URI 스키마가 지원됩니다.javascript:스키마는 지원되지 않습니다.URL 크기 제한: 지원되는 최대 URL 문자열 길이는 2MB입니다.
기능 확인: API를 호출하기 전에 항상
WebViewFeature.isFeatureSupported를 사용하여 기능 가용성을 확인하여 다양한 WebView APK 버전 간의 호환성을 유지하세요.
탐색 시작 및 수명 주기 추적
탐색을 구성하고 수명 주기를 추적하려면 다음 단계를 따르세요.
WebView설정 중에WebViewCompat.addNavigationListener를 사용하여NavigationListener구현을 등록하여 구조화된 수명 주기 콜백을 수신합니다. 메모리 누수 및 중복 콜백 실행을 방지하려면 모든 탐색 호출에서가 아니라 한 번만 리스너를 등록하세요.NavigationParameters인스턴스를 구성하여NavigationParameters.Builder기록 대체 또는 커스텀 HTTP 헤더와 같은 선택적 동작을 지정합니다.WebViewCompat.navigate를 호출하여WebView인스턴스, 도착 URL, 매개변수를 전달합니다.
WebViewCompat.navigate는 요청을 고유하게
식별하는 Navigation 객체를 반환합니다. NavigationListener 콜백에서 이 객체를 수신 Navigation 매개변수와 비교하여 특정
탐색을 추적합니다.
구현 예시
다음 예에서는 탐색 매개변수를 구성하고 WebViewCompat.navigate를 호출하며 탐색 수명 주기 이벤트를 수신 대기하는 방법을 보여줍니다.
Kotlin
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)
}
}
자바
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);
}
}
장애 모드 및 오류 처리
WebViewCompat.navigate API는 구성 오류 및 런타임 탐색 실패를 처리하기 위한 고유한 메커니즘을 제공합니다.
잘못된 인수 예외
잘못된 인수를 전달하면 동기식 IllegalArgumentException이 트리거됩니다.
일반적인 원인은 다음과 같습니다.
- 필수 null이 아닌 매개변수 (
webView,url또는params)에null을 전달합니다. javascript:와 같이 지원되지 않는 URL 스키마를 제공합니다.- RFC 2616 사양을 준수하지 않는 잘못된 형식의 HTTP 헤더 키 또는 값을 전달합니다.
탐색 프로세스 오류
네트워크 요청 또는 페이지 로드 중에 실패가 발생하더라도 (예: HTTP 404 상태 코드, DNS 변환 실패 또는 SSL 오류) WebViewCompat.navigate 는 여전히 유효한 Navigation 객체를 반환합니다.
탐색이 완료되면 Navigation
인스턴스 내의 onNavigationCompleted 콜백에서 다음 메서드를 검사하여
실패를 진단합니다.
getStatusCode: HTTP 응답 상태 코드 (예:404또는500)를 반환합니다.getWebResourceError: 연결 시간 초과 또는 호스트 조회 실패와 같은 네트워크 오류를 자세히 설명하는WebResourceErrorCompat객체를 반환합니다.didCommitErrorPage:WebView가 사용자에게 오류 페이지를 커밋하고 표시했는지 여부를 나타냅니다.didCommit: 탐색이 중단되지 않고 대상 페이지에 성공적으로 커밋되었는지 여부를 나타냅니다.
상태 번들 관리 저장
NavigationParameters로 추가 헤더를 전달하면 WebView는 상태 복원 시 재사용할 수 있도록 이러한 헤더를 저장된 상태 번들에 저장합니다. 하지만 헤더 컬렉션이 크면 저장된 상태 Bundle의 크기가 크게 늘어날 수 있습니다.
Android 상태 저장 중에 TransactionTooLargeException
을 방지하기 위해 번들 크기를 제한해야 하는 경우 WebViewCompat.saveState을 사용하세요. 이 메서드를 사용하면 최대 번들 크기 제한을 바이트 단위로 설정하고 선택적으로 전달 기록 항목을 제외할 수 있습니다.
Kotlin
// 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)
자바
// 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는 기존 리스너를 대체하는 것이 아니라 리스너를 추가하므로 후속 탐색에서 메모리 누수 및 중복 콜백 실행을 방지하려면WebView설정 중에NavigationListener를 한 번 등록하세요.상태 크기 저장 모니터링: 큰 헤더 페이로드를 전달할 때는 명시적 크기 경계가 있는
WebViewCompat.saveState를 사용하여 과도한 상태 데이터가 저장되지 않도록 합니다.
추가 리소스
삽입된 웹 기능 및 성능 최적화에 관해 자세히 알아보려면 다음 가이드를 참고하세요.