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입니다.
기능 확인: 다양한 WebView APK 버전 간의 호환성을 유지하려면 API를 호출하기 전에 항상
WebViewFeature.isFeatureSupported을 사용하여 기능 사용 가능 여부를 확인하세요.
내비게이션 시작 및 수명 주기 추적
탐색을 구성하고 수명 주기를 추적하려면 다음 단계를 따르세요.
WebView설정 중에WebViewCompat.addNavigationListener를 사용하여NavigationListener구현을 등록하여 구조화된 수명 주기 콜백을 수신합니다. 메모리 누수와 중복 콜백 실행을 방지하려면 모든 탐색 호출에서가 아닌 한 번만 리스너를 등록하세요.NavigationParameters.Builder를 사용하여NavigationParameters인스턴스를 구성하여 기록 대체 또는 맞춤 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);
}
}
HTTP 헤더를 사용하여 앱 상태 전파
웹 앱은 백엔드 로직을 조정하거나 웹 콘텐츠를 맞춤설정하기 위해 호스트 Android 앱의 컨텍스트가 필요한 경우가 많습니다. 이 정보를 전달하기 위해 URL에 쿼리 매개변수를 추가하면 URL이 복잡해지고, 캐싱이 방해되며, 내부 앱 상태가 노출될 수 있습니다.
대신 맞춤 HTTP 헤더를 사용하여 앱 컨텍스트를 전달하는 것이 좋습니다. WebViewCompat.navigate 및 NavigationParameters를 사용하면 이 데이터를 서버로 안전하게 전송할 수 있습니다. 또한 WebView는 상태 복원 중에 이러한 헤더를 유지하므로 구성 변경 전반에서 웹 콘텐츠가 일관되게 유지됩니다. 이 지속성은 WebViewCompat.navigate를 사용하는 경우에만 적용됩니다. WebView.loadUrl를 사용하는 경우 맞춤 헤더가 WebView 상태 번들에 저장되지 않으며 복원 시 손실됩니다.
일반적인 사용 사례
호스트 앱 컨텍스트 전달의 일반적인 사용 사례는 다음과 같습니다.
- 앱 버전 (
X-App-Version): 호스트 앱의 출시 버전(예:BuildConfig.VERSION_NAME)을 전달하면 백엔드 서버에서 네이티브 JavaScript 브리지 호환성을 확인하거나, 기능을 제한하거나, 사용자에게 이전 앱을 업데이트하라는 메시지를 표시할 수 있습니다. - 클라이언트 플랫폼 (
X-Client-Platform): 호스트 환경을 Android로 명시적으로 식별하면 서버가User-Agent문자열 파싱에 의존하지 않고 플랫폼에 맞게 조정된 UI를 제공하거나 스토어 링크를 라우팅할 수 있습니다.
구현 예시
다음 예에서는 애플리케이션 버전과 클라이언트 플랫폼을 웹 서버에 전달하는 방법을 보여줍니다.
Kotlin
// Attach host app metadata so the server can verify compatibility and tailor content
val params = NavigationParameters.Builder()
.addAdditionalHeaders(
mapOf(
"X-App-Version" to BuildConfig.VERSION_NAME,
"X-Client-Platform" to "Android"
)
)
.build()
// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params)
자바
// Attach host app metadata so the server can verify compatibility and tailor content
Map<String, String> headers = new HashMap<>();
headers.put("X-App-Version", BuildConfig.VERSION_NAME);
headers.put("X-Client-Platform", "Android");
NavigationParameters params = new NavigationParameters.Builder()
.addAdditionalHeaders(headers)
.build();
// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params);
실패 모드 및 오류 처리
WebViewCompat.navigate API는 구성 오류와 런타임 탐색 실패를 처리하는 별도의 메커니즘을 제공합니다.
잘못된 인수 예외
잘못된 인수를 전달하면 동기 IllegalArgumentException가 트리거됩니다.
일반적인 원인은 다음과 같습니다.
- 필수 null이 아닌 매개변수 (
webView,url또는params)에null을 전달합니다. - 지원되지 않는 URL 스키마(예:
javascript:)를 제공합니다. - RFC 2616 사양을 준수하지 않는 잘못된 HTTP 헤더 키 또는 값을 전달합니다.
탐색 프로세스 오류
네트워크 요청 또는 페이지 로드 중에 오류가 발생하면 (예: HTTP 404 상태 코드, DNS 변환 실패, SSL 오류) WebViewCompat.navigate는 여전히 유효한 Navigation 객체를 반환합니다.
탐색이 완료되면 onNavigationCompleted 콜백 내의 Navigation 인스턴스에서 다음 메서드를 검사하여 실패를 진단합니다.
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를 사용하여 과도한 상태 데이터가 저장되지 않도록 합니다.
추가 리소스
삽입된 웹 기능 및 성능 최적화에 대해 자세히 알아보려면 다음 가이드를 참고하세요.