پیمایش پیشرفته صفحه با 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: :) و سفارشی پشتیبانی می‌شوند. طرحواره javascript: پشتیبانی نمی‌شود.

  • محدودیت اندازه URL: حداکثر طول رشته URL پشتیبانی شده ۲ مگابایت است.

  • بررسی ویژگی: همیشه قبل از فراخوانی API، با استفاده از WebViewFeature.isFeatureSupported در دسترس بودن ویژگی را بررسی کنید تا سازگاری بین نسخه‌های مختلف APK WebView حفظ شود.

شروع ناوبری و پیگیری چرخه حیات

برای پیکربندی ناوبری و پیگیری چرخه حیات آن، موارد زیر را انجام دهید:

  1. هنگام راه‌اندازی WebView با استفاده از WebViewCompat.addNavigationListener یک پیاده‌سازی NavigationListener ثبت کنید تا فراخوانی‌های چرخه عمر ساختاریافته را دریافت کنید. شنونده را یک بار (به جای هر فراخوانی ناوبری) ثبت کنید تا از نشت حافظه و اجرای تکراری فراخوانی‌ها جلوگیری شود.
  2. با استفاده از NavigationParameters.Builder یک نمونه NavigationParameters بسازید تا رفتارهای اختیاری، مانند جایگزینی تاریخچه یا هدرهای HTTP سفارشی، را مشخص کنید.
  3. با ارسال نمونه WebView ، URL مقصد و پارامترها، WebViewCompat.navigate را فراخوانی کنید.

WebViewCompat.navigate یک شیء Navigation برمی‌گرداند که به طور منحصر به فرد درخواست را شناسایی می‌کند. در فراخوانی‌های NavigationListener خود، این شیء را با پارامتر Navigation ورودی مقایسه کنید تا آن ناوبری خاص را ردیابی کنید.

مثال پیاده‌سازی

مثال زیر نحوه پیکربندی پارامترهای ناوبری، فراخوانی WebViewCompat.navigate و گوش دادن به رویدادهای چرخه عمر ناوبری را نشان می‌دهد:

کاتلین

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

حالت‌های خرابی و مدیریت خطا

API WebViewCompat.navigate مکانیزم‌های متمایزی برای مدیریت خطاهای پیکربندی و خطاهای ناوبری زمان اجرا ارائه می‌دهد:

استثنائات آرگومان نامعتبر

ارسال آرگومان‌های نامعتبر باعث ایجاد خطای IllegalArgumentException همزمان می‌شود. دلایل رایج آن عبارتند از:

  • ارسال null برای پارامترهای غیر null مورد نیاز ( webView ، url یا params ).
  • ارائه یک طرح URL پشتیبانی نشده، مانند javascript: .
  • ارسال کلیدهای هدر HTTP ناقص یا مقادیری که با مشخصات RFC 2616 مطابقت ندارند.

اگر در طول درخواست شبکه یا بارگذاری صفحه، خطایی رخ دهد (مانند کد وضعیت HTTP 404، خطای DNS resolution یا خطای SSL)، WebViewCompat.navigate همچنان یک شیء Navigation معتبر را برمی‌گرداند.

وقتی پیمایش تمام شد، متدهای زیر را در نمونه‌ی Navigation درون فراخوانی onNavigationCompleted خود بررسی کنید تا مشکل را تشخیص دهید:

  • getStatusCode : کد وضعیت پاسخ HTTP (مثلاً 404 یا 500 ) را برمی‌گرداند.
  • getWebResourceError : یک شیء WebResourceErrorCompat را برمی‌گرداند که جزئیات خطاهای شبکه، مانند زمان‌های قطع اتصال یا خرابی‌های جستجوی میزبان را شرح می‌دهد.
  • didCommitErrorPage : نشان می‌دهد که آیا WebView یک صفحه خطا را ثبت و به کاربر نمایش داده است یا خیر.
  • didCommit : نشان می‌دهد که آیا ناوبری با موفقیت و بدون لغو به صفحه هدف منتقل شده است یا خیر.

مدیریت بسته نرم افزاری وضعیت را ذخیره کنید

وقتی هدرهای اضافی را با NavigationParameters ارسال می‌کنید، WebView این هدرها را در بسته وضعیت ذخیره‌شده خود ذخیره می‌کند تا بتوان پس از بازیابی وضعیت، دوباره از آنها استفاده کرد. با این حال، مجموعه‌های بزرگ هدرها می‌توانند اندازه Bundle وضعیت ذخیره‌شده را به میزان قابل توجهی افزایش دهند.

اگر نیاز دارید که اندازه بسته را محدود کنید تا از TransactionTooLargeException در حین ذخیره وضعیت اندروید جلوگیری شود، از WebViewCompat.saveState استفاده کنید. این روش به شما امکان می‌دهد حداکثر اندازه بسته را بر حسب بایت تعیین کنید و به صورت اختیاری موارد مربوط به تاریخچه رو به جلو را حذف کنید:

کاتلین

// 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 به جای جایگزینی یک شنونده موجود، یک شنونده اضافه می‌کند، NavigationListener خود را یک بار در طول راه‌اندازی WebView ثبت کنید تا از نشت حافظه و تکرار اجراهای callback در پیمایش‌های بعدی جلوگیری شود.

  • نظارت بر اندازه وضعیت ذخیره: هنگام ارسال بارهای هدر بزرگ، از WebViewCompat.saveState با مرزهای اندازه صریح استفاده کنید تا از ذخیره داده‌های وضعیت بیش از حد جلوگیری شود.

منابع اضافی

برای کسب اطلاعات بیشتر در مورد قابلیت‌های وب تعبیه‌شده و بهینه‌سازی عملکرد، به راهنماهای زیر مراجعه کنید: