การนำทางหน้าเว็บที่ปรับปรุงแล้วด้วย WebViewCompat.navigate

WebViewCompat.navigate เป็นตัวเลือกที่ได้รับการปรับปรุงให้ดีขึ้นแทน WebView.loadUrl ซึ่งช่วยให้คุณควบคุมการโหลดหน้าเว็บ การจัดการประวัติ และการติดตามวงจรการนำทางใน WebView ได้อย่างละเอียด

ก่อนหน้านี้ การเริ่มการนำทางหน้าเว็บโดยใช้ loadUrl มีข้อจำกัดที่สำคัญดังนี้

  • ไม่มีการแทนที่รายการประวัติ: คุณไม่สามารถแทนที่รายการประวัติปัจจุบันได้ ทำให้ไม่สามารถไปยังหน้าใหม่ได้โดยไม่ต้องเพิ่มรายการลงในสแต็กย้อนกลับ
  • การเรียกกลับที่แยกกัน: ไม่มีกลไกโดยตรงในการเชื่อมโยงการเรียก loadUrl ที่เฉพาะเจาะจงกับเหตุการณ์การเรียกกลับที่ตามมาใน WebViewClient
  • ไม่ได้บันทึกส่วนหัวเพิ่มเติม: ระบบไม่ได้บันทึกส่วนหัวที่กำหนดเองที่ส่งไปยัง loadUrl เป็นส่วนหนึ่งของสถานะ WebView ดังนั้นส่วนหัวจึงหายไปเมื่อกู้คืนสถานะ

API WebViewCompat.navigate แก้ปัญหาเหล่านี้ด้วยการนำฟีเจอร์ต่อไปนี้มาใช้

  • การแทนที่รายการประวัติการนำทาง: ช่วยให้คุณแทนที่หน้าปัจจุบันในสแต็กประวัติของ WebView ได้
  • การติดตามการเรียกกลับที่เชื่อมโยง: ส่งคืนออบเจ็กต์ Navigation ซึ่ง ทำหน้าที่เป็นตัวระบุที่ไม่ซ้ำกันในทุกขั้นตอนของวงจรการนำทาง
  • การรองรับส่วนหัวของสถานะที่บันทึกไว้: ระบบจะบันทึกส่วนหัวเพิ่มเติมไว้ในบันเดิลสถานะของ WebView อย่างน่าเชื่อถือ เพื่อให้สามารถนำส่วนหัวเหล่านั้นกลับมาใช้ใหม่ได้เมื่อกู้คืนสถานะ

ความสามารถและข้อจำกัดที่สำคัญ

โปรดพิจารณากฎและการจำกัดการทำงานต่อไปนี้ก่อนนำ WebViewCompat.navigate ไปใช้

  • ความปลอดภัยของเธรด: คุณต้องเรียกใช้ WebViewCompat.navigate ในเธรด UI (หลัก)

  • การยกเลิกและการจัดลำดับความสำคัญ: คุณไม่สามารถยกเลิกการนำทางที่กำลังดำเนินการอยู่ได้อย่างชัดเจน อย่างไรก็ตาม การเริ่มการเรียก navigate ใหม่ใน WebView เดียวกันจะแทนที่การนำทางที่ใช้งานอยู่

  • การรองรับสคีม URI: ระบบรองรับสคีม URI มาตรฐาน (เช่น https: และ http:) และสคีม URI ที่กำหนดเอง แต่ไม่รองรับรูปแบบ javascript:

  • ขีดจำกัดขนาด URL: ความยาวสตริง URL สูงสุดที่รองรับคือ 2 MB

  • การตรวจสอบฟีเจอร์: ตรวจสอบความพร้อมใช้งานของฟีเจอร์โดยใช้ WebViewFeature.isFeatureSupported เสมอก่อนเรียกใช้ API เพื่อ รักษาความเข้ากันได้ใน WebView APK เวอร์ชันต่างๆ

เริ่มการนำทางและติดตามวงจร

หากต้องการกำหนดค่าการนำทางและติดตามวงจรการนำทาง ให้ทำดังนี้

  1. ลงทะเบียนการใช้งาน NavigationListener โดยใช้ WebViewCompat.addNavigationListener ระหว่างการตั้งค่า WebView เพื่อรับ การเรียกกลับวงจรที่มีโครงสร้าง ลงทะเบียน Listener เพียงครั้งเดียว (แทนที่จะลงทะเบียนทุกครั้งที่เรียกใช้การนำทาง) เพื่อป้องกันการรั่วไหลของหน่วยความจำและการเรียกใช้การเรียกกลับซ้ำ
  2. สร้างอินสแตนซ์ NavigationParameters โดยใช้ NavigationParameters.Builder เพื่อระบุลักษณะการทำงานที่ไม่บังคับ เช่น การแทนที่ประวัติหรือส่วนหัว HTTP ที่กำหนดเอง
  3. เรียกใช้ 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)
    }
}

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

โหมดความล้มเหลวและการจัดการข้อผิดพลาด

API WebViewCompat.navigate มีกลไกที่แตกต่างกันสำหรับการจัดการข้อผิดพลาดในการกำหนดค่าและความล้มเหลวในการนำทางขณะรันไทม์

ข้อยกเว้นของอาร์กิวเมนต์ไม่ถูกต้อง

การส่งอาร์กิวเมนต์ไม่ถูกต้องจะทริกเกอร์ IllegalArgumentException แบบซิงโครนัส สาเหตุที่พบบ่อยมีดังนี้

  • การส่ง null สำหรับพารามิเตอร์ที่ไม่ใช่ Null ที่จำเป็น (webView, url หรือ params)
  • การระบุรูปแบบ URL ที่ไม่รองรับ เช่น javascript:
  • การส่งคีย์หรือค่าส่วนหัว HTTP ที่มีรูปแบบไม่ถูกต้องซึ่งไม่เป็นไปตามข้อกำหนด RFC 2616

หากเกิดความล้มเหลวระหว่างคำขอเครือข่ายหรือการโหลดหน้าเว็บ (เช่น รหัสสถานะ HTTP 404 ความล้มเหลวในการแปลง DNS หรือข้อผิดพลาด SSL) WebViewCompat.navigate จะยังคงส่งคืนออบเจ็กต์ Navigation ที่ถูกต้อง

เมื่อการนำทางเสร็จสิ้น ให้ตรวจสอบเมธอดต่อไปนี้ในอินสแตนซ์ Navigation ภายในฟังก์ชันการเรียกกลับ onNavigationCompleted เพื่อวินิจฉัยความล้มเหลว

  • getStatusCode: ส่งคืนรหัสสถานะการตอบกลับ HTTP (เช่น 404 หรือ 500)
  • getWebResourceError: ส่งคืนWebResourceErrorCompat ออบเจ็กต์ที่ให้รายละเอียดเกี่ยวกับข้อผิดพลาดของเครือข่าย เช่น การหมดเวลาการเชื่อมต่อหรือความล้มเหลวในการค้นหาโฮสต์
  • didCommitErrorPage: ระบุว่า WebView คอมมิตและแสดงหน้าข้อผิดพลาดแก่ผู้ใช้หรือไม่
  • didCommit: ระบุว่าการนำทางคอมมิตไปยังหน้าเป้าหมายได้สำเร็จโดยไม่ถูกยกเลิกหรือไม่

การจัดการบันเดิลสถานะที่บันทึกไว้

เมื่อคุณส่งส่วนหัวเพิ่มเติมด้วย NavigationParameters WebView จะบันทึกส่วนหัวเหล่านี้ไว้ในบันเดิลสถานะที่บันทึกไว้ เพื่อให้สามารถนำส่วนหัวเหล่านั้นกลับมาใช้ใหม่ได้เมื่อกู้คืนสถานะ อย่างไรก็ตาม คอลเล็กชันส่วนหัวขนาดใหญ่อาจเพิ่มขนาดของ Bundle สถานะที่บันทึกไว้อย่างมาก

หากต้องการจำกัดขนาดบันเดิลเพื่อป้องกัน TransactionTooLargeException ระหว่างการบันทึกสถานะ Android ให้ใช้ 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)

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

บันเดิลที่ได้จะยังคงเข้ากันได้กับเมธอด มาตรฐาน WebView.restoreState

คำแนะนำในการย้ายข้อมูลและการใช้งาน

ปฏิบัติตามคำแนะนำต่อไปนี้เพื่อให้มั่นใจถึงประสิทธิภาพและความเสถียรสูงสุดเมื่อนำทางใน WebView

  • ย้ายข้อมูลจาก loadUrl ไปเป็น navigate: ย้ายข้อมูลการเรียก WebView.loadUrl ทั้งหมดที่เลิกใช้งานแล้วไปเป็น WebViewCompat.navigate วิธีนี้จะช่วยให้การจัดการประวัติเป็นไปอย่างสม่ำเสมอและมั่นใจได้ว่าระบบจะบันทึกส่วนหัวเป็นส่วนหนึ่งของสถานะที่บันทึกไว้เสมอ

  • ตรวจสอบการรองรับฟีเจอร์เสมอ: ก่อนเรียกใช้ API ให้ยืนยันการรองรับขณะรันไทม์ด้วย WebViewFeature.isFeatureSupported เพื่อป้องกัน WebView เวอร์ชันเก่า

  • เชื่อมโยงอินสแตนซ์การนำทาง: ใช้ออบเจ็กต์ Navigation ที่ส่งคืนเพื่อแยกความแตกต่างของการนำทางพร้อมกันหรือกรองการเรียกกลับเมื่อจัดการอินสแตนซ์ WebView หลายรายการ

  • ลงทะเบียน Listener เพียงครั้งเดียวระหว่างการเริ่มต้น: เนื่องจาก WebViewCompat.addNavigationListener จะเพิ่ม Listener แทนที่จะแทนที่ Listener ที่มีอยู่ ให้ลงทะเบียน NavigationListener เพียงครั้งเดียวระหว่าง WebView การตั้งค่าเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำและการเรียกใช้การเรียกกลับซ้ำในการนำทางครั้งต่อๆ ไป

  • ตรวจสอบขนาดสถานะที่บันทึกไว้: เมื่อส่งเพย์โหลดส่วนหัวขนาดใหญ่ ให้ใช้ WebViewCompat.saveState ที่มีขอบเขตขนาดที่ชัดเจนเพื่อหลีกเลี่ยงการบันทึกข้อมูลสถานะมากเกินไป

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถของเว็บแบบฝังและการเพิ่มประสิทธิภาพได้ที่คำแนะนำต่อไปนี้