پیمایش صفحه بهبودیافته با WebViewCompat.navigate

WebViewCompat.navigate جایگزینی بهبودیافته برای WebView.loadUrl است که کنترل دقیقی بر بار کردن صفحه وب، مدیریت سابقه، و ردیابی چرخه حیات پیمایش در WebView ارائه می‌دهد.

قبلاً، آغاز کردن پیمایش‌های صفحه بااستفاده از loadUrl محدودیت‌های قابل‌توجهی داشت:

  • بدون جایگزینی ورودی سابقه: نتوانستید ورودی سابقه فعلی را جایگزین کنید، بنابراین پیمایش به صفحه جدید بدون افزودن ورودی به پشته قبلی‌ها غیرممکن شد.
  • فراخوانی‌های جداشده: هیچ سازوکار مستقیمی برای مرتبط کردن یک تماس loadUrl خاص با رویدادهای فراخوانی بعدی در WebViewClient وجود نداشت.
  • سرایندهای اضافی ذخیره نشدند: سرایندهای سفارشی ارسال‌شده به loadUrl به‌عنوان بخشی از وضعیت WebView ذخیره نشدند، بنابراین هنگام بازیابی وضعیت ازبین رفتند.

WebViewCompat.navigate API این مشکلات را با معرفی ویژگی‌های زیر برطرف می‌کند:

  • جایگزینی ورودی سابقه پیمایش: به شما امکان می‌دهد صفحه فعلی را در پشته سابقه WebView جایگزین کنید.
  • ردگیری فراخوانی همبسته: شیء Navigation را برمی‌گرداند که به‌عنوان شناسه یکتا در همه مراحل چرخه حیات پیمایش عمل می‌کند.
  • پشتیبانی از سرصفحه حالت ذخیره‌شده: سرصفحه‌های اضافی به‌طور مطمئن در WebView بسته حالت ذخیره می‌شوند تا بتوان از آن‌ها در بازگرداندن حالت استفاده مجدد کرد.

قابلیت‌ها و محدودیت‌های کلیدی

قبل‌از پذیرش WebViewCompat.navigate، قوانین و محدودیت‌های عملیاتی زیر را درنظر بگیرید:

  • ایمنی رشته: باید WebViewCompat.navigate را در رشته (اصلی) میانای کاربر فراخوانی کنید.

  • لغو و تقدم: ناوبری‌های درحال اجرا را نمی‌توان به‌طور صریح لغو کرد. بااین‌حال، شروع تماس navigate جدید در همان WebView جایگزین هرگونه ناوبری فعال می‌شود.

  • پشتیبانی از طرح‌واره URI: طرح‌واره‌های URI استاندارد (مانند https: و http:) و سفارشی پشتیبانی می‌شوند. از طرح javascript: پشتیبانی نمی‌شود.

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

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

شروع ناوبری و چرخه حیات قطعه

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

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

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

انتشار وضعیت برنامه بااستفاده از سرصفحه‌های HTTP

برنامه‌های وب اغلب به بافت برنامه Android میزبان نیاز دارند تا منطق پس‌زمینه را هماهنگ کنند یا محتوای وب را سفارشی‌سازی کنند. افزودن پارامترهای پُرسمان به نشانی وب برای ارسال این اطلاعات می‌تواند نشانی‌های وب را به‌هم بریزد، در ذخیره‌سازی تداخل ایجاد کند، و وضعیت داخلی برنامه را افشا کند.

درعوض، توصیه می‌کنیم بااستفاده از سرایندهای HTTP سفارشی، بافت برنامه را منتقل کنید. بااستفاده از WebViewCompat.navigate و NavigationParameters، می‌توانید این داده‌ها را به‌طور ایمن به سرورتان ارسال کنید. علاوه‌براین، WebView این سرصفحه‌ها را درطول بازیابی وضعیت حفظ می‌کند، که تضمین می‌کند محتوای وب در سراسر تغییرات پیکربندی ثابت باقی می‌ماند. توجه داشته باشید که این پایداری فقط هنگام استفاده از WebViewCompat.navigate اعمال می‌شود. اگر از WebView.loadUrl استفاده می‌کنید، سرصفحه‌های سفارشی در بسته حالت WebView ذخیره نمی‌شوند و پس‌از بازیابی ازبین می‌روند.

موارد استفاده رایج

موارد استفاده رایج برای ارسال بافت برنامه میزبان شامل موارد زیر است:

  • نسخه برنامه (X-App-Version): ارسال نسخه پخش برنامه میزبان (مانند BuildConfig.VERSION_NAME) به سرور زیرینه شما کمک می‌کند سازگاری پل جاوا اسکریپت بومی را تأیید کند، ویژگی‌ها را کنترل کند، یا از کاربران بخواهد برنامه‌های قدیمی‌تر را به‌روز کنند.
  • پلاتفرم کارخواه (X-Client-Platform): شناسایی صریح محیط میزبان به‌عنوان Android به سرور امکان می‌دهد تا میانای کاربری سفارشی‌شده برای پلاتفرم را ارائه دهد یا پیوندهای فروشگاه را بدون تکیه بر تجزیه رشته User-Agent مسیریابی کند.

نمونه پیاده‌سازی

مثال زیر نشان می‌دهد که چگونه نسخه برنامه و پلاتفرم مشتری را به سرور وب منتقل کنید:

کاتلین

// 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).
  • ارائه طرح‌واره نشانی وب پشتیبانی‌نشده، مثل javascript:.
  • ارسال کلیدها یا مقادیر سرایند HTTP بدشکل که با مشخصات RFC 2616 مطابقت ندارند.

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

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

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

مدیریت بسته حالت ذخیره

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

اگر لازم است اندازه بسته را محدود کنید تا از TransactionTooLargeException درطول ذخیره وضعیت Android جلوگیری کنید، از 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. این کار مدیریت یکنواخت سابقه را تضمین می‌کند و اطمینان می‌دهد که سرصفحه‌ها همیشه به‌عنوان بخشی از وضعیت ذخیره‌شده ذخیره می‌شوند.

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

  • مرتبط کردن نمونه‌های پیمایش: از شیء Navigation برگردانده‌شده برای متمایز کردن پیمایش‌های هم‌زمان یا فیلتر کردن فراخوانی‌های برگشتی هنگام مدیریت چند نمونه WebView استفاده کنید.

  • شنونده را یک بار درطول مقداردهی اولیه ثبت کنید: چون WebViewCompat.addNavigationListener شنونده اضافه می‌کند و شنونده موجود را جایگزین نمی‌کند، NavigationListener را یک بار درطول راه‌اندازی WebView ثبت کنید تا از نشت حافظه و اجرای تکراری فراخوانی برگشتی در پیمایش‌های بعدی جلوگیری شود.

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

منابع بیشتر

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