在管理 Android 应用的生命周期时,在后台资源回收期间保留用户状态是实现顺畅用户体验的核心组件。对于包含 Web 工作流的应用,WebView.saveState(Bundle) 可让您将 WebView 的浏览历史记录和状态序列化为 Bundle。随后,您可以使用 WebView.restoreState(Bundle) 恢复这些数据。
不过,在浏览会话量较大时,标准实现可能会遇到事务大小限制。本页介绍了这些架构限制,并提供了一些策略,可在保持导航历史记录的同时防止出现与内存相关的异常。
1MB 交易限额和状态清除
Android 对可在 savedInstanceState 中存储的数据总量施加了严格的 1MB 限制。此 1MB 预算在整个应用进程中共享。如果应用包含多个 WebView 实例,它们的集体导航状态和历史记录必须适合此单个共享分配。超出此边界会触发 TransactionTooLargeException,导致应用崩溃。
一种常见但有问题的缓解策略是监控 WebView 状态 bundle 的大小,并在其超过任意安全阈值(例如 300KB)时完全清除 WebView 历史记录。虽然这可以防止崩溃,但会严重影响用户体验:
丢失后向导航:Android 通常会终止后台应用进程,以便为其他任务回收内存。您可以在
onSaveInstanceState()生命周期回调中使用saveState(Bundle)来保留导航历史记录。如果您清除此历史记录以避免 1MB 交易限额,整个导航堆栈都会丢失。当用户返回应用时,系统返回按钮会立即退出组件或应用,因为无论是否发生了进程重启,都没有历史上下文来支持向后导航。BFCache 失效:清除历史记录会阻止应用使用往返缓存 (BFCache),从而无法立即呈现之前访问过的网页。
延迟时间增加:用户在 WebView 中丢失当前状态,需要重新进行完整导航和重新初始化。此过程会显著增加网络开销和交易延迟时间。
架构缓解策略
为了防止 TransactionTooLargeException 崩溃,同时避免因完全删除历史记录而降低用户体验,您必须在状态保留和内存效率之间保持严格的平衡。通过实施以下优化策略,您可以安全地管理 1MB 的事务预算,同时保留必要的浏览历史记录和会话完整性。
强制执行状态序列化的大小限制
当导航堆栈过大时,更有效的模式是截断历史数据,而不是完全清除导航堆栈:
有针对性的丢弃政策:使用
WebViewCompat.saveState()可在强制执行特定字节限制(例如WebViewCompat.saveState(webView, outState, maxSizeBytes))的同时序列化状态。此 API 会自动按顺序丢弃较旧的导航条目,直到总载荷符合您定义的分配为止。关键在于,此操作只会截断序列化的Bundle,而不会修改或清除有效WebView的实时历史记录,从而确保立即向后导航的功能完全不受影响。移除前进条目:如果应用界面提供了一个“返回”按钮,但缺少专用的“前进”导航按钮,您可以通过将
saveStateAPI 的includeForwardState参数设置为false来舍弃所有前进导航条目。这样可以显著减小载荷大小,而不会影响用户可用的导航路径。
使用 HTTP 缓存配额 API 管理资源延迟时间
虽然 saveState 会管理临时导航历史记录的 1MB Bundle 限制,但 HTTP Cache Quota API 可按个人资料手动控制持久性 Web 资源(磁盘缓存)。这样一来,短期导航上下文与长期缓存的资源之间便有了明确的区别。
选择合适的配额需要在性能方面进行权衡:
- 更高的配额可通过在磁盘上保留更多资源来提高离线可用性和资源加载延迟时间。
- 较低的配额可最大限度地减少应用的磁盘占用空间,并防止操作系统主导的其他关键应用数据缓存逐出。
这些设置会在应用重启后保留,并且必须从主线程进行配置。
以下实现演示了如何为默认配置文件配置磁盘缓存配额:
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);
}
如需详细了解配额大小调整策略、生命周期管理和配置文件边界,请参阅 在 WebView 中管理 HTTP 缓存配额。
关键性能考虑因素
以下几点突出了控制 WebView 状态行为的技术限制和内部数据行为:
不透明的
PageStateblob:saveState存储的数据中约有 70% 是来自渲染引擎的内部PageStateblob。此数据可捕获精细的会话状态,包括表单输入和 iframe 滚动位置。请避免尝试手动解析或剥离这些 blob 中的各个段,因为这样做会带来严重的安全风险,并破坏会话恢复完整性。精细的历史记录管理:标准
WebBackForwardListAPI 本身不支持任意移除单个历史记录元素。对于严格的状态管理,您必须使用WebViewCompat.saveState()中的maxSizeBytes和includeForwardState参数来实现截断策略,以确保架构安全。