LeOS-Ice-browser/android-components/docs/_posts/2019-02-19-saving-state.md

110 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
layout: post
title: "💾 Saving and restoring browser session state"
date: 2019-02-18 14:35:00 +0200
author: sebastian
---
Losing open tabs in a browser can be a painful experience for the user. By itself [SessionManager](https://mozac.org/api/mozilla.components.browser.session/-session-manager/) keeps its state only in memory. This means that something as simple as switching to a different app can cause Android to [kill the browser app's process and reclaim its resources](https://developer.android.com/guide/components/activities/process-lifecycle). The result of that: The next time the user switches back to the browser app they start with a fresh browser without any tabs open.
[Mozilla's Android Components](https://mozac.org/) come with two implementations of session storage and helpers to write your own easily.
## SessionStorage
The [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) class that comes with the [browser-session](https://github.com/mozilla-mobile/android-components/tree/main/components/browser/session) component saves the state as a file on disk (using [AtomicFile](https://developer.android.com/reference/android/util/AtomicFile) under the hood). It can be used for a browser that wants to have a single state that gets saved and restored (like Fennec or Chrome).
```kotlin
val sessionStorage SessionStorage(applicationContext, engine)
val sessionManager = sessionManager(engine).apply {
sessionStorage.restore()?.let { snapshot -> restore(snapshot) }
}
```
Since restoring the last state requires a disk read, it is recommended to perform it off the main thread. This requires the app to gracefully handle the situation where the app starts without any sessions at first. [SessionManager](https://mozac.org/api/mozilla.components.browser.session/-session-manager/) will invoke [onSessionsRestored()](https://mozac.org/api/mozilla.components.browser.session/-session-manager/-observer/on-sessions-restored.html) on a registered [SessionManager.Observer](https://mozac.org/api/mozilla.components.browser.session/-session-manager/-observer/) after restoring has completed.
## SessionBundleStorage
Other than [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) the [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/) implementation can save and restore from multiple states. State is saved as a Bundle in a database.
The storage is set up with a bundle lifetime. [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/) will only restore the last bundle if its lifetime has not expired. If there's no active bundle then a new empty bundle will be created to save the state.
```kotlin
val engine: Engine = ...
val sessionStorage = SessionBundleStorage(
applicationContext,
bundleLifetime = Pair(1, TimeUnit.HOURS)
val sessionManager = sessionManager(engine).apply {
// We launch a coroutine on the main thread. Once a snapshot has been restored
// we want to continue with it on the main thread.
GlobalScope.launch(Dispatchers.Main) {
// We restore on the IO dispatcher to not block the main thread:
val snapshot = async(Dispatchers.IO) {
val bundle = sessionStorage.restore()
// If we got a bundle then restore the snapshot from it
bundle.restoreSnapshot(engine)
}
// If we got a snapshot then restore it now:
snapshot.await()?.let { sessionManager.restore(it) }
}
}
```
The storage comes with an [API](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/#functions) that allows apps to build UIs to list, restore, save and remove bundles.
![](/assets/images/blog/session-bundles.png)
## AutoSave
Knowing when to save state, by calling [SessionStorage.save()](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/save.html) or [SessionBundleStorage.save()](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/save.html), can be complicated. Restoring an outdated state can be an as bad a user experience as restoring no state at all.
The [AutoSave](https://mozac.org/api/mozilla.components.browser.session.storage/-auto-save/) class is a helper for configuring automatic saving of the browser state - and you can use it with [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) as well as [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/).
```kotlin
sessionStorage.autoSave(sessionManager)
// Automatically save the state every 30 seconds:
.periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS)
// Save the state when the app goes to the background:
.whenGoingToBackground()
// Save the state whenever sessions change (e.g. a new tab got added or a website
// has completed loading).
.whenSessionsChange()
```
## Implementing your own SessionStorage
If neither [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) nor [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/) satisfy the requirements of your app (e.g. you want to save the state in your favorite database or in a cloud-enabled storage) then it is possible to implement a custom storage.
The [AutoSave.Storage](https://mozac.org/api/mozilla.components.browser.session.storage/-auto-save/-storage/) interface from the [browser-session](https://github.com/mozilla-mobile/android-components/tree/main/components/browser/session) component defines the methods that are expected from a session storage. Technically it is not required to implement the interface if your app code is the only one interacting with the session store; but implementing the interface makes your implementation compatible with other components code. Specifically you can use [AutoSave](https://mozac.org/api/mozilla.components.browser.session.storage/-auto-save/) with any class implementing SessionStorage without any additional code.
The [SnapshotSerializer](https://mozac.org/api/mozilla.components.browser.session.storage/-snapshot-serializer/) class is helpful when translating snapshots from and to JSON.
```kotlin
class MyCustomStorage(
private val engine: Engine
) : AutoSave.Storage {
private val serializer = SnapshotSerializer()
override fun save(snapshot: SessionManager.Snapshot): Boolean {
val json = serializer.toJSON(snapshot)
// TODO: Save JSON custom storage.
// Signal that save operation was successful:
return true
}
fun restore(): SessionManager.Snapshot {
// TODO: Get JSON from custom storage.
val json = ...
return serializer.fromJSON(engine, json)
}
}
```
For simplicity the implementation above does not handle [JSONException](ad https://developer.android.com/reference/org/json/JSONException.html) which can be thrown by [SnapshotSerializer](https://mozac.org/api/mozilla.components.browser.session.storage/-snapshot-serializer/).