When Do We Back Up the Database?
Databases do not get backed up automatically — we have to do that work. That in turn implies that we know when to do that work.
When the User Asks
The simple possibility is to back up the database when the user asks, via an action bar item or some similar option.
However, copying a database file will take some time. Exactly how long it will take depends on a lot of factors, particularly the size of the file and the speed of the device. We need to make sure that the user is not doing things in our app that need the database while the backup is going on. One approach is to have the backup be performed while some dedicated UI — such as an activity with a ProgressBar
— is in the foreground.
When You Feel Like It
In principle, you could try backing up the database at odd times, when you think that the user is not going to be using the app. This has the advantage of being automatic, so you can offer to restore a backup that the user did not request manually. Plus, in theory, the backup will not disturb the user if you are doing it, say, in the middle of the night.
However:
- Some users might want to use your app in the middle of the night
- If you have your own scheduled tasks (e.g.,
WorkManager
periodic work), that might run in the middle of your backup - If the user tries to get into your app while the backup is running, you would need smarts in all of your app’s entry points (e.g., launcher activity) to detect that a backup is going on and take steps to not use the database
You might think that your app has only one entry point: the launcher activity. However, suppose that the user had been using your app 10 minutes ago, and you decide to run the automatic backup. While that backup is ongoing, the user tries returning to your task (intentionally or accidentally) via the overview screen. The user will be returned to whatever activity they were on last, which may or may not be the launcher activity.
One way to help reduce the likelihood of problems is to have some sort of a usage timer, perhaps using ProcessLifecycleOwner
, where you do not do the backup until at least 30 minutes has elapsed since your UI was last in the foreground. This should cause the overview screen to return the user to your launcher activity, rather than to some other point based on an outstanding task.
When You Think Room Is Not Using It
As mentioned previously, part of the struggle is in ensuring that Room is not using the database. Keeping the user out of database-driven UI and stopping any database-related background tasks are key steps… but that may not be quite enough.
A Simple Close
RoomDatabase
has a close()
method. In principle, it should close the underlying SupportSQLiteDatabase
and prevent further use of the RoomDatabase
.
However, Room itself has its own background threads, particularly in a class named InvalidationTracker
. This code monitors your database I/O and arranges to re-deliver updated data to reactive queries (e.g., where your DAO returns an Observable
or LiveData
). It is unclear whether InvalidationTracker
will shut down when you close()
the RoomDatabase
, and InvalidationTracker
does not have any API of its own to shut it down.
The Nuclear Option
The safest way to ensure that nothing is using your Room database is a two-step process:
- Put your backup and restore logic in a separate process
- When it comes time to actually perform a backup or restore operation, you fork that separate process… then kill your main app’s process
In the dedicated backup/restore process, you do not use Room and you do not open the SQLite database. You just copy files around.
Terminating your own process in Android is easy enough. The android.os.Process
class has a killProcess()
method that will kill a process given its process ID (“PID”). You get your own process’ ID by calling myPid()
on android.os.Process
. Note that the class here is android.os.Process
— be careful, as Java has its own Process
class, one that is always visible owing to it being in the java.lang
package.
(pro tip: do not name your own classes the same as classes in java.lang
)
Terminating your own process itself is somewhat risky. For example, you should not assume that onDestroy()
of anything gets called. This is why we rarely do this and certainly do not do it as a part of normal app operations. But, there are extraordinary cases where it may be useful: backup and restore operations are two such cases.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.