Quản lý trạng thái WebView một cách hiệu quả

Khi quản lý vòng đời của một ứng dụng Android, việc duy trì trạng thái người dùng trong quá trình thu hồi tài nguyên ở chế độ nền là một thành phần cốt lõi của trải nghiệm liền mạch cho người dùng. Đối với các ứng dụng kết hợp quy trình công việc trên web, WebView.saveState(Bundle) cho phép bạn chuyển đổi tuần tự nhật ký điều hướng và trạng thái của WebView thành một Bundle. Sau đó, bạn có thể khôi phục dữ liệu này bằng cách dùng WebView.restoreState(Bundle).

Tuy nhiên, các hoạt động triển khai tiêu chuẩn có thể gặp phải giới hạn về kích thước giao dịch trong các phiên duyệt web có lưu lượng truy cập lớn. Trang này mô tả những hạn chế về cấu trúc này và cung cấp các chiến lược để ngăn chặn các trường hợp ngoại lệ liên quan đến bộ nhớ trong khi vẫn duy trì nhật ký điều hướng.

Giới hạn giao dịch 1 MB và việc xoá trạng thái

Android áp đặt giới hạn nghiêm ngặt là 1 MB đối với tổng dung lượng dữ liệu có thể được lưu trữ trong savedInstanceState. Ngân sách 1 MB này được chia sẻ trên toàn bộ quy trình ứng dụng. Nếu một ứng dụng kết hợp nhiều thực thể WebView, thì trạng thái và nhật ký điều hướng chung của các thực thể đó phải nằm trong một phân bổ dùng chung duy nhất này. Nếu vượt quá ranh giới này, TransactionTooLargeException sẽ kích hoạt, dẫn đến sự cố ứng dụng.

Một chiến lược giảm thiểu phổ biến nhưng có vấn đề là theo dõi kích thước của gói trạng thái WebView và xoá hoàn toàn nhật ký WebView nếu kích thước này vượt quá một ngưỡng an toàn tuỳ ý (chẳng hạn như 300 KB). Mặc dù điều này ngăn chặn sự cố, nhưng nó lại gây ra những hồi quy nghiêm trọng cho trải nghiệm người dùng:

  • Mất khả năng điều hướng ngược: Android thường chấm dứt các quy trình ứng dụng ở chế độ nền để giải phóng bộ nhớ cho các tác vụ khác. Bạn có thể dùng saveState(Bundle) trong phương thức gọi lại trong vòng đời onSaveInstanceState() để duy trì nhật ký điều hướng. Nếu bạn xoá nhật ký này để tránh giới hạn giao dịch 1 MB, thì toàn bộ ngăn xếp điều hướng sẽ bị mất. Khi người dùng quay lại ứng dụng, nút quay lại của hệ thống sẽ lập tức thoát khỏi thành phần hoặc ứng dụng vì không còn ngữ cảnh lịch sử nào để hỗ trợ thao tác điều hướng ngược, bất kể có xảy ra quá trình khởi động lại hay không.

  • Làm mới BFCache: Việc xoá nhật ký sẽ ngăn ứng dụng sử dụng Bộ nhớ đệm cho thao tác tiến/lùi (BFCache), loại bỏ khả năng hiển thị ngay lập tức các trang đã truy cập trước đó.

  • Tăng độ trễ: Người dùng mất trạng thái hiện tại trong WebView, yêu cầu điều hướng lại và khởi động lại hoàn toàn. Quá trình này làm tăng đáng kể mức hao tổn mạng và độ trễ giao dịch.

Chiến lược giảm thiểu kiến trúc

Để ngăn TransactionTooLargeException gặp sự cố mà không làm giảm trải nghiệm người dùng thông qua việc xoá hoàn toàn nhật ký, bạn phải duy trì sự cân bằng chặt chẽ giữa việc giữ lại trạng thái và hiệu quả sử dụng bộ nhớ. Bằng cách triển khai các chiến lược tối ưu hoá sau đây, bạn có thể quản lý an toàn ngân sách giao dịch 1 MB trong khi vẫn giữ được nhật ký điều hướng và tính toàn vẹn của phiên.

Thực thi giới hạn kích thước đối với việc chuyển đổi trạng thái thành chuỗi

Thay vì xoá hoàn toàn ngăn xếp điều hướng khi ngăn xếp này phát triển quá lớn, một mẫu hiệu quả hơn là cắt bớt dữ liệu trong quá khứ:

  • Chính sách loại bỏ có mục tiêu: Sử dụng WebViewCompat.saveState() để chuyển đổi trạng thái tuần tự trong khi thực thi một giới hạn byte cụ thể (ví dụ: WebViewCompat.saveState(webView, outState, maxSizeBytes)). API này sẽ tự động loại bỏ các mục nhập điều hướng cũ theo trình tự cho đến khi tổng tải trọng nằm trong phạm vi phân bổ mà bạn xác định. Điều quan trọng là thao tác này chỉ rút gọn Bundle được chuyển đổi tuần tự mà không sửa đổi hoặc xoá nhật ký trực tiếp của WebView đang hoạt động, đảm bảo thao tác điều hướng ngược lại ngay lập tức vẫn hoàn toàn nguyên vẹn.

  • Xoá mục nhập chuyển tiếp: Nếu giao diện ứng dụng cung cấp nút quay lại nhưng thiếu nút điều hướng tiến lên chuyên dụng, bạn có thể loại bỏ tất cả các mục nhập điều hướng chuyển tiếp bằng cách đặt tham số includeForwardState của API saveState thành false. Điều này giúp giảm đáng kể kích thước tải trọng mà không ảnh hưởng đến các đường dẫn điều hướng có sẵn của người dùng.

Quản lý độ trễ tài nguyên bằng HTTP Cache Quota API

Mặc dù saveState quản lý giới hạn 1 MB Bundle cho nhật ký điều hướng tạm thời, nhưng HTTP Cache Quota API cung cấp chế độ kiểm soát thủ công đối với các tài nguyên web được duy trì (bộ nhớ đệm của ổ đĩa) trên cơ sở mỗi hồ sơ. Điều này tạo ra sự khác biệt rõ ràng giữa bối cảnh điều hướng ngắn hạn và nội dung được lưu vào bộ nhớ đệm dài hạn.

Việc chọn một hạn mức phù hợp sẽ dẫn đến sự đánh đổi về hiệu suất:

  • Hạn mức cao hơn giúp cải thiện khả năng cung cấp ngoại tuyến và độ trễ tải tài nguyên bằng cách lưu trữ nhiều tài sản hơn trên đĩa.
  • Giảm hạn mức để giảm thiểu dung lượng ổ đĩa của ứng dụng và ngăn hệ điều hành xoá bộ nhớ đệm của các dữ liệu ứng dụng quan trọng khác.

Các chế độ cài đặt này vẫn được duy trì khi ứng dụng khởi động lại và phải được định cấu hình từ luồng chính.

Quy trình triển khai sau đây minh hoạ cách định cấu hình hạn mức bộ nhớ đệm của ổ đĩa cho hồ sơ mặc định:

Kotlin

if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
    WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
    val defaultProfile = ProfileStore.getInstance()
        .getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME)
    val httpCache = defaultProfile.httpCache

    // Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
    httpCache.setQuotaBytes(50L * 1024 * 1024)
}

Java

if (WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE) &&
    WebViewFeature.isFeatureSupported(WebViewFeature.HTTP_CACHE)) {
    Profile defaultProfile = ProfileStore.getInstance()
        .getOrCreateProfile(Profile.DEFAULT_PROFILE_NAME);
    HttpCache httpCache = defaultProfile.getHttpCache();

    // Set explicit cache size to 50MB (50 * 1024 * 1024 bytes)
    httpCache.setQuotaBytes(50L * 1024 * 1024);
}

Để biết thêm thông tin về các chiến lược định cỡ hạn mức, quản lý vòng đời và ranh giới hồ sơ, hãy xem bài viết Quản lý hạn mức bộ nhớ đệm HTTP trong WebView.

Những điểm chính cần cân nhắc về hiệu suất

Các điểm sau đây nêu bật những hạn chế về kỹ thuật và hành vi dữ liệu nội bộ chi phối hành vi trạng thái WebView:

  • Blob PageState mờ: Khoảng 70% dữ liệu do saveState lưu trữ bao gồm các blob PageState nội bộ từ công cụ kết xuất. Dữ liệu này ghi lại các trạng thái phiên chi tiết, bao gồm cả dữ liệu đầu vào của biểu mẫu và vị trí cuộn iframe. Tránh cố gắng phân tích cú pháp hoặc loại bỏ các phân đoạn riêng lẻ khỏi những blob này theo cách thủ công, vì việc này gây ra rủi ro bảo mật nghiêm trọng và làm hỏng tính toàn vẹn của việc khôi phục phiên.

  • Quản lý nhật ký chi tiết: API WebBackForwardList tiêu chuẩn không hỗ trợ việc xoá tuỳ ý các phần tử riêng lẻ trong nhật ký. Để quản lý trạng thái nghiêm ngặt, bạn phải triển khai các chiến lược cắt bớt bằng cách sử dụng các tham số maxSizeBytesincludeForwardState trong WebViewCompat.saveState() để đảm bảo tính an toàn về cấu trúc.