Android SDKのサンプルを読む:BackupRestoreActivity

Android SDKに入っているサンプルを読む。今回はBackupRestoreActivity。
BackupRestoreActivityはAndroidのBackup Serviceを利用するサンプル。Backup ServiceはAPI Level 8から追加された機能でアプリケーションの持つデータを“クラウド”上に保管するサービス。この機能を使うことで、アプリケーションを一時的に削除(アンインストール)した後、再インストールしても以前のデータを利用できる。これは、端末を買い換えてアプリケーションを再インストールした場合にも利用できる。
http://developer.android.com/intl/ja/guide/topics/data/backup.html
開発ガイドのサンプルでは、ゲームの最高得点のデータを保存するサンプルを挙げている。
SDKのサンプル、BackupRestoreActivityには4つのJavaファイルがあるが、BackupRestoreActivity.javaがバックアップサービスを利用するアプリケーションの本体。他の3つ、ExampleAgent.java、FileHelperExampleAgent.java、MultiRecordExampleAgent.javaはバックアップサービスを利用する際に必要となる“Agent”で、同時には3つは必要ない。エージェントの実装方法のバリエーションを示してるだけで、どれか1つのエージェントを選択して利用する。(マニフェストの“android:backupAgent”属性で指定する。)
BackupRestoreActivityはAndroidのバックアップサービスを利用するが、このサンプルではデータを“クラウド”に保存することは出来ない。クラウドを使った保存にはアプリケーションをGoogleに登録してBackup Service Keyを取得・設定する必要がある。BackupRestoreActivityでは、その代わりにデバイス(AVDであれシミュレータ)が提供する場所にデータを保存している(/data/backup/com.android.internal.backup.LocalTransport というディレクトリにアクティビティのパッケージ名を使ったファイルが作られ、そこに保存される)。

バックアップサービスの概要

バックアップサービスの利用方法は簡単だ。バックアップサービスを利用したいアプリケーションに、BackupAgentクラスもしくはBackupAgentHelperクラスを継承したサブクラスを作成し、その抽象メソッドであるonBackupとonRestoreを実装する。onBackupにはそのアプリケーション固有のデータを集めてバックアップサービスへ渡す機能を実装する。onRestoreはその逆。アプリケーション本体からバックアップのサービスを呼び出すにはBackupManagerクラスのdataChangedメソッドを呼び出す。ただし、dataChangedメソッドを呼び出しても、直ぐにはバックアップが実行されない。バックアップの“要求”を出すだけで、実際のバックアップは“あるタイミングで”バックアップサービスを要求している他のアプリケーションとまとめて実行されるとある。が、どういうタイミングかは上記のページには見当たらなかった。そのタイミングで実際にバックアップが行われる時にエージェントのonBackupが呼び出される。

dataChangedで直ちにバックアップを開始しないのには合理的な理由がある。アプリケーションがバックアップすべきデータを頻繁に更新する場合(基本的にはデータが更新されるとdataChangedを呼び出すのだが)、その度にクラウドにアクセスしてデータを保存しているでは負担が大き過ぎる。そこで、データの更新がある度にdataChangedで複数の更新の要求を出すが、実際にバックアップが実行される時点での最新のデータだけをクラウドに保存するようになっている。

リストアに関しては、基本的にはアプリケーションが何かメソッドを呼び出すような必要はない。アプリケーションのインストール時にシステムが判断して、初回の実行時にonRestoreを呼び出してくれる。(アプリケーションが主体的にリストアを要求する場合は、バックアップマネージャのrequestRestoreメソッドを呼び出す。)

BackupRestoreActivityについて

サンプルとしてはSDKのサンプル(BackupRestoreActivity)よりも、開発ページのサンプルの方が分かりやすいと思う。なぜならBackupRestoreActivityのエージェントはちょっと凝りすぎているので。
エージェント(BackupAgentのサブクラス)のonBackupで実際に保存したいデータをファイルなどから読み込んできてバックアップサービスへ渡している。しかし、単純にファイルからデータを持ってくるだけでなく“以前のバックアップに比べて内容が新しくなっていた場合のみ”データを保存する。ここで“以前のバックアップに比べて内容が新しくなっていた場合”をどう判断するか、だ。Webの開発ページのサンプルは、バックアップしたいファイルの更新時間が、以前のバックアップより新しくなっていれば更新する、という方法だ。解りやすい。
しかし、SDKのBackupRestoreActivityはバックアップサービスで(クラウドに)保存するデータと同じデータをローカルに保存して、そのローカルに保存した値に比べてデータの値が変更している場合は(クラウドに)バックアップする、という方法。つまり、クラウドとローカルに同じデータを保存しようとしているので、最初読むとなんだかよく解らない。とくに“バックアップって何”というのを勉強しようとしている人が読むと解らなくなってしまう。私も最初読んで、このコードは一体何をやっているのだろう、と理解に苦しんだ。Webのサンプルを見てスッキリした感じだ。

onBackupの引数

onBackupは次のような引数になっている。
onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)
第1引数と第3引数が“ParcelFileDescriptor”となっている。これは何?
リファレンスを見るとParcel#readFileDescriptor()というメソッドで返されるファイル・ディスクプリタということ。では、Parcelは? ParcelはIBinderでプロセス間を受け渡しされるオブジェクト。IBinderはAndroidのプロセス間通信でデータの受け渡しに使われるインタフェース。
つまり、oldStateやnewStateは“ファイルディスクプリタ”なので任意のデータを読み書きできる。(基本的にoldStateは読みだし専用だが)newStateに書かれたデータはAndroidのIPCを通して別のプロセス(多分、バックアップサービスのプロセス)が読んでいるということ。onBackupではこのoldState、newStateを使ってバックアップしているデータの“状態”をデバイスで管理している、らしい。oldState、newStateには任意の情報を書き込むことが出来きるが、その内容の意味解釈はアプリケーションに任される。開発ページのサンプルではバックアップするデータのあるローカルファイルの“更新日時”を書き込んでいる。SDKのサンプルではバックアップするデータと同じデータを書き込んでいる。
真ん中の第2引数 dataを通して実際に保存するデータをバックアップサービスへ渡す。書き込むデータはGoogle得意のkey/valueペアからなるエンティティの形式となる。まず、writeEntityHeaderでキーを書き込み、writeEntityDataでバリュー(もしくはその一部)を書き込む。エンティティのバリュー部分は複数のwriteEntityDataで複数の塊に分けて書き込むことができる(ただし、その合計の大きさはwriteEntityHeaderで指定した大きさでなければならない)。