Android Room のバックアップを PC でとる
Android で動作するアプリ開発をしている時、データを端末に保存するためにデータベースとして Room を利用することがよくあります。
わけあって端末に保存している Room のデータベースのバックアップを PC でとりたいことがあったのですが、手順をいろいろ調べることになったので備忘録残しておきます。
TL;DR
バックアップ取得時は下記のようなスクリプトを実行。PACKAGE_NAME は対象のアプリのパッケージ名、DB_NAME はアプリ内で Room に設定したデータベースの名前。
1 2 3 4 5 6 7 8 9 |
#!/bin/sh PACKAGE_NAME=com.ryuta46.myapp DB_NAME=myapp-db adb exec-out "run-as $PACKAGE_NAME cat databases/${DB_NAME}" > ${DB_NAME} adb exec-out "run-as $PACKAGE_NAME cat databases/${DB_NAME}-wal" > ${DB_NAME}-wal adb exec-out "run-as $PACKAGE_NAME cat databases/${DB_NAME}-shm" > ${DB_NAME}-shm |
バックアップからの復元時は下記のようなスクリプトを実行
1 2 3 4 5 6 7 8 9 |
#!/bin/sh PACKAGE_NAME=com.ryuta46.myapp DB_NAME=myapp-db cat ${DB_NAME} | adb exec-in "run-as $PACKAGE_NAME sh -c \"cat > databases/${DB_NAME}\"" cat ${DB_NAME}-wal | adb exec-in "run-as $PACKAGE_NAME sh -c \"cat > databases/${DB_NAME}-wal\"" cat ${DB_NAME}-shm | adb exec-in "run-as $PACKAGE_NAME sh -c \"cat > databases/${DB_NAME}-shm\"" |
以下、詳細。
確認環境
- Room 2.5.2
- adb
- Android Debug Bridge version 1.0.41
- Version 33.0.3-8952118
- Android 13
- macOS Ventura 13.4.1(c)
動機
そもそもなんでバックアップを取ろうと思ったのかですが、これはユーザー目線の話ではなく、開発者目線の話になります。
アプリを Google Play Store に出す際にスクリーンショットを撮る必要があります。
アプリにデータをいろいろ登録し、無事スクリーンショットを撮ることができたのですが、その後にバグが見つかってコードを修正する必要があることに気がつきました。
しかし、デバッグのために一旦データを全削除するとまたスクリーンショットを撮るためにデータを再登録する必要がでてきます。
それが面倒だなと思い、スクリーンショットを撮る用のデータセットを一旦バックアップとして保存しておいて、またスクリーンショットを撮るときに簡単に復元しようと思い立ったわけです。
手順
というわけで、手順です。
Context#databaseList()
とContext#getDatabasePath()
でデータベースファイルの保存場所を調べる- adb コマンドで Android 端末から PC にデータベースファイルを転送
- 復元時は、adb コマンドで PC から Android 端末へデータベースファイルを転送
以下、各手順について詳細に書いていきます。
1.データベースファイルの保存場所を調べる
Room は内部で SQLite というデータベースを使い、データベースをファイルとして保存しています。
まずはこのファイルが端末内のどこにあるか調べます。
それには、下記のようなコードを対象のアプリのどこかで実行し、アプリで使っているデータベースファイルがどこに保存されているかを出力します。
1 2 3 4 |
context.databaseList().forEach { Log.d("DBLocation", "DB: ${context.getDatabasePath(it)}") } |
Context#databaseList()
でアプリが使用しているデータベースファイルのファイル名が取得できます。 Context#getDatabasePath(name)
で name のデータベースファイルのパスを出力できます。
私の場合は下記のような出力になりました。
1 2 3 4 |
DB: /data/user/0/com.ryuta46.myapp/databases/myapp-db DB: /data/user/0/com.ryuta46.myapp/databases/myapp-db-wal DB: /data/user/0/com.ryuta46.myapp/databases/myapp-db-shm |
com.ryuta46.myapp
のところはアプリのパッケージ名に、myapp-db
は Room で設定したデータベースの名前にそれぞれなっているはずです。
とりあえずこの出力先をメモっておきます。
ちなみに、-wal と -shm のついたファイルは、どちらも一時ファイルという扱いのようなのですが、アプリを終了した状態でも普通に残ったままだったので、今回は 3ファイルともバックアップするようにしました。
2.adb コマンドで Android 端末から PC にデータベースファイルを転送
バックアップを取るべきファイルがわかったので、そのファイルを PC の方に転送するのですが、普通に adb pull
で転送しようとすると Permission denied
でエラーになります。
1 2 3 |
% adb pull /data/user/0/com.ryuta46.myapp/databases/myapp-db adb: error: failed to stat remote object '/data/user/0/com.ryuta46.myapp/databases/myapp-db': Permission denied |
保存場所がアプリ固有の領域なので、アクセスが制限されているようです。このままだとファイルにアクセスできませんが、Android 端末上のターミナルで run-as
コマンドを実行することで、com.ryuta46.myapp パッケージとしてアクセスすることは可能です。
しかし、run-as
実行後にファイルをコピーしようとしても、今度はその状態だと/sdcard/
などの領域へのコピーは Permission denied
で拒否されてしまいます。
そこで、cat コマンドを使ってファイルの内容を標準出力にダンプし、それをリダイレクトして PC 側のファイルに保存するようにしました。
こういう用途で使えるのが adb exec-out
コマンドです。adb exec-out
の後に続くコマンドを Android ターミナル上で実行し、結果を標準出力にそのままだしてくれますので、それを PC 側でリダイレクトしてファイル保存します。
まとめると、myapp-db ファイルを PC 側に保存するには下記のコマンドを PC 側のターミナルで実行します。
1 2 |
$ adb exec-out "run-as com.ryuta46.myapp cat databases/myapp-db" > myapp-dp |
-wal と -shm も同様にコピーできます。
下記のようなスクリプト pull-db.sh を書いておくと便利かと思います。
1 2 3 4 5 6 7 8 9 |
#!/bin/sh PACKAGE_NAME=com.ryuta46.myapp DB_NAME=myapp-db adb exec-out "run-as $PACKAGE_NAME cat databases/${DB_NAME}" > ${DB_NAME} adb exec-out "run-as $PACKAGE_NAME cat databases/${DB_NAME}-wal" > ${DB_NAME}-wal adb exec-out "run-as $PACKAGE_NAME cat databases/${DB_NAME}-shm" > ${DB_NAME}-shm |
3. adb コマンドで PC から Android 端末へデータベースファイルを転送
今度は、PCに保存したバックアップファイルを Android 端末にコピーする方法です。
2.と逆のことをすればよく、それに適した adb exec-in
コマンドもあるのですが、単純に逆の書き方をしてもリダイレクトの解釈が期待通りに行われず、うまくいきません。
そこで、明示的に run-as の対象コマンドを記述するために、sh -c
コマンドで実行対象を囲み、意図を明確にするようにします。 というわけで、以下のようなスクリプト push-db.sh を書いて実行します。
1 2 3 4 5 6 7 8 9 |
#!/bin/sh PACKAGE_NAME=com.ryuta46.myapp DB_NAME=myapp-db cat ${DB_NAME} | adb exec-in "run-as $PACKAGE_NAME sh -c \"cat > databases/${DB_NAME}\"" cat ${DB_NAME}-wal | adb exec-in "run-as $PACKAGE_NAME sh -c \"cat > databases/${DB_NAME}-wal\"" cat ${DB_NAME}-shm | adb exec-in "run-as $PACKAGE_NAME sh -c \"cat > databases/${DB_NAME}-shm\"" |
これでバックアップをとった時の DB の状態が復元できているかと思います。
さいごに
意外と一筋縄ではコピーできず苦労しましたが、今回調べたことで初めて知ったコマンドとかあって勉強になりました。
実はもっと簡単にできるよとかあったら教えてください。
最後まで読んでいただきありがとうございます。 このブログを「いいな」と感じていただけましたら、Twiter にてフォローいただけるとうれしいです。ブログ更新情報などもお届けします。
Follow @ryuta461
この記事をシェアする