Navigasi halaman yang ditingkatkan dengan WebViewCompat.navigate

WebViewCompat.navigate adalah alternatif yang ditingkatkan untuk WebView.loadUrl yang memberikan kontrol terperinci atas pemuatan halaman web, pengelolaan histori, dan pelacakan siklus proses navigasi di WebView.

Sebelumnya, memulai navigasi halaman menggunakan loadUrl memiliki batasan yang signifikan:

  • Tidak ada penggantian entri histori: Anda tidak dapat mengganti entri histori saat ini, sehingga tidak mungkin membuka halaman baru tanpa menambahkan entri ke stack kembali.
  • Callback yang tidak terikat: Tidak ada mekanisme langsung untuk mengorelasikan panggilan loadUrl tertentu dengan peristiwa callback berikutnya di WebViewClient.
  • Header tambahan tidak disimpan: Header kustom yang diteruskan ke loadUrl tidak disimpan sebagai bagian dari status WebView, sehingga hilang saat memulihkan status.

API WebViewCompat.navigate menyelesaikan masalah ini dengan memperkenalkan fitur berikut:

  • Penggantian entri histori navigasi: Memungkinkan Anda mengganti halaman saat ini di stack histori WebView.
  • Pelacakan callback yang dikorelasikan: Menampilkan objek Navigation yang berfungsi sebagai ID unik di semua tahap siklus proses navigasi.
  • Dukungan header status tersimpan: Header tambahan disimpan dengan andal dalam paket status WebView sehingga dapat digunakan kembali saat pemulihan status.

Kemampuan dan batasan utama

Sebelum mengadopsi WebViewCompat.navigate, pertimbangkan aturan dan batasan operasional berikut:

  • Keamanan thread: Anda harus memanggil WebViewCompat.navigate di thread UI (utama).

  • Pembatalan dan prioritas: Navigasi dalam proses tidak dapat dibatalkan secara eksplisit. Namun, memulai panggilan navigate baru pada WebView yang sama akan menggantikan navigasi aktif.

  • Dukungan skema URI: Skema URI standar (seperti https: dan http:) dan kustom didukung. Skema javascript: tidak didukung.

  • Batas ukuran URL: Panjang string URL maksimum yang didukung adalah 2 MB.

  • Pemeriksaan fitur: Selalu periksa ketersediaan fitur menggunakan WebViewFeature.isFeatureSupported sebelum memanggil API untuk mempertahankan kompatibilitas di berbagai versi APK WebView.

Mulai navigasi dan siklus proses pelacakan

Untuk mengonfigurasi navigasi dan melacak siklus prosesnya, lakukan hal berikut:

  1. Daftarkan implementasi NavigationListener menggunakan WebViewCompat.addNavigationListener selama penyiapan WebView untuk menerima callback siklus proses terstruktur. Daftarkan pemroses satu kali (bukan pada setiap panggilan navigasi) untuk mencegah kebocoran memori dan eksekusi callback duplikat.
  2. Buat instance NavigationParameters menggunakan NavigationParameters.Builder untuk menentukan perilaku opsional, seperti penggantian histori atau header HTTP kustom.
  3. Panggil WebViewCompat.navigate, lalu teruskan instance WebView Anda, URL tujuan, dan parameter.

WebViewCompat.navigate menampilkan objek Navigation yang mengidentifikasi permintaan secara unik. Di callback NavigationListener, bandingkan objek ini dengan parameter Navigation yang masuk untuk melacak navigasi tertentu tersebut.

Contoh penerapan

Contoh berikut menunjukkan cara mengonfigurasi parameter navigasi, memanggil WebViewCompat.navigate, dan memproses peristiwa siklus proses navigasi:

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)
    }
}

Java

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);
    }
}

Menyebarkan status aplikasi menggunakan header HTTP

Aplikasi web sering kali memerlukan konteks dari aplikasi Android host untuk mengoordinasikan logika backend atau menyesuaikan konten web. Menambahkan parameter kueri ke URL untuk meneruskan informasi ini dapat membuat URL berantakan, mengganggu penyiapan cache, dan mengekspos status aplikasi internal.

Sebagai gantinya, sebaiknya teruskan konteks aplikasi menggunakan header HTTP kustom. Dengan menggunakan WebViewCompat.navigate dan NavigationParameters, Anda dapat mengirim data ini ke server Anda dengan aman. Selain itu, WebView mempertahankan header ini selama pemulihan status, yang memastikan bahwa konten web tetap konsisten di seluruh perubahan konfigurasi. Perhatikan bahwa persistensi ini hanya berlaku saat menggunakan WebViewCompat.navigate. Jika Anda menggunakan WebView.loadUrl, header kustom tidak disimpan dalam paket status WebView dan akan hilang saat pemulihan.

Kasus penggunaan umum

Kasus penggunaan umum untuk meneruskan konteks aplikasi host mencakup:

  • Versi aplikasi (X-App-Version): Meneruskan versi rilis aplikasi host (seperti BuildConfig.VERSION_NAME) membantu server backend Anda memverifikasi kompatibilitas jembatan JavaScript native, fitur gating, atau meminta pengguna untuk mengupdate aplikasi yang lebih lama.
  • Platform klien (X-Client-Platform): Mengidentifikasi lingkungan host secara eksplisit sebagai Android memungkinkan server mengirimkan UI yang disesuaikan dengan platform atau merutekan link Play Store tanpa mengandalkan parsing string User-Agent.

Contoh penerapan

Contoh berikut menunjukkan cara meneruskan versi aplikasi dan platform klien ke server web:

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)

Java

// 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);

Mode kegagalan dan penanganan error

API WebViewCompat.navigate menyediakan mekanisme berbeda untuk menangani kesalahan konfigurasi dan kegagalan navigasi runtime:

Pengecualian argumen tidak valid

Meneruskan argumen yang tidak valid akan memicu IllegalArgumentException sinkron. Penyebab umumnya meliputi:

  • Meneruskan null untuk parameter non-null yang diperlukan (webView, url, atau params).
  • Menyediakan skema URL yang tidak didukung, seperti javascript:.
  • Meneruskan kunci atau nilai header HTTP yang salah format dan tidak sesuai dengan spesifikasi RFC 2616.

Jika terjadi kegagalan selama permintaan jaringan atau pemuatan halaman (seperti kode status HTTP 404, kegagalan resolusi DNS, atau error SSL), WebViewCompat.navigate tetap menampilkan objek Navigation yang valid.

Saat navigasi selesai, periksa metode berikut pada instance Navigation di dalam callback onNavigationCompleted untuk mendiagnosis kegagalan:

  • getStatusCode: Menampilkan kode status respons HTTP (misalnya, 404 atau 500).
  • getWebResourceError: Menampilkan objek WebResourceErrorCompat yang menjelaskan error jaringan, seperti waktu tunggu koneksi atau kegagalan pencarian host.
  • didCommitErrorPage: Menunjukkan apakah WebView melakukan dan menampilkan halaman error kepada pengguna.
  • didCommit: Menunjukkan apakah navigasi berhasil dilakukan ke halaman target tanpa dibatalkan.

Pengelolaan paket status tersimpan

Saat Anda meneruskan header tambahan dengan NavigationParameters, WebView menyimpan header ini dalam paket status tersimpannya sehingga dapat digunakan kembali saat pemulihan status. Namun, kumpulan header yang besar dapat meningkatkan ukuran Bundle status tersimpan secara signifikan.

Jika Anda perlu membatasi ukuran paket untuk mencegah TransactionTooLargeException selama penyimpanan status Android, gunakan WebViewCompat.saveState. Metode ini memungkinkan Anda menetapkan batas ukuran paket maksimum dalam byte dan secara opsional mengecualikan item histori penerusan:

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)

Java

// 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);

Paket yang dihasilkan tetap kompatibel dengan metode WebView.restoreState standar.

Rekomendasi migrasi dan penerapan

Untuk memastikan performa dan stabilitas yang optimal saat menjelajah di WebView, ikuti rekomendasi berikut:

  • Migrasikan dari loadUrl ke navigate: Migrasikan semua panggilan WebView.loadUrl lama ke WebViewCompat.navigate. Hal ini memastikan pengelolaan histori yang seragam dan memastikan header selalu disimpan sebagai bagian dari status tersimpan.

  • Selalu verifikasi dukungan fitur: Sebelum memanggil API, konfirmasi dukungan runtime dengan WebViewFeature.isFeatureSupported untuk melindungi terhadap versi WebView yang lebih lama.

  • Korelasikan instance navigasi: Gunakan objek Navigation yang ditampilkan untuk membedakan navigasi serentak atau memfilter callback saat mengelola beberapa instance WebView.

  • Daftarkan pemroses sekali selama inisialisasi: Karena WebViewCompat.addNavigationListener menambahkan pemroses, bukan mengganti pemroses yang ada, daftarkan NavigationListener Anda sekali selama penyiapan WebView untuk menghindari kebocoran memori dan eksekusi callback duplikat di navigasi berikutnya.

  • Pantau ukuran status penyimpanan: Saat meneruskan payload header besar, gunakan WebViewCompat.saveState dengan batas ukuran eksplisit untuk menghindari penyimpanan data status yang berlebihan.

Referensi lainnya

Untuk mempelajari lebih lanjut kemampuan web sematan dan pengoptimalan performa, lihat panduan berikut: