Android Backup Service を使用して Key-Value ペアをバックアップする

Android Backup Service は、Android アプリ内の Key-Value データについて、クラウド ストレージによるバックアップと復元を提供します。Key-Value バックアップ操作時、アプリのバックアップ データはデバイスのバックアップ トランスポートに渡されます。デバイスがデフォルトの Google バックアップ トランスポートを使用している場合、データは Android Backup Service に渡されてアーカイブされます。

データの量はアプリのユーザー 1 人あたり 5 MB に制限されており、バックアップ データは無料で保存できます。

Android のバックアップ オプションの概要と、バックアップおよび復元する必要があるデータに関するガイダンスについては、データのバックアップの概要をご覧ください。

注: ほとんどのアプリでは、自動バックアップを使用してバックアップと復元を実装する必要があります。自動バックアップと Key-Value バックアップの両方をアプリで実装することはできません。自動バックアップはコード不要で、ファイル全体をバックアップします。一方、Key-Value バックアップでは、Key-Value ペアの形式でバックアップ コンテンツを明示的に定義するコードを作成する必要があります。

Key-Value バックアップを実装する

アプリデータをバックアップするには、バックアップ エージェントを実装する必要があります。バックアップ エージェントは、バックアップと復元の両方でバックアップ マネージャーによって呼び出されます。

バックアップ エージェントを実装するには、次のことを行う必要があります。

  1. マニフェスト ファイルで android:backupAgent 属性を使用してバックアップ エージェントを宣言する。
  2. アプリを Android Backup Service に登録する。
  3. 次のいずれかによりバックアップ エージェントを定義する。
    1. BackupAgent の拡張

      BackupAgent クラスにより、アプリがバックアップ マネージャーと通信するための中央インターフェースを提供します。このクラスを直接拡張する場合、onBackup()onRestore() をオーバーライドしてデータのバックアップと復元のオペレーションを処理する必要があります。

    2. または

    3. BackupAgentHelper の拡張

      BackupAgentHelper クラスは、BackupAgent クラスを使いやすくするラッパーを提供します。これにより、記述する必要があるコードの量を最小限に抑えることができます。onBackup()onRestore() を実装する必要がないように、特定のタイプのデータを自動的にバックアップおよび復元する 1 つ以上の「ヘルパー」オブジェクトを BackupAgentHelper で使用する必要があります。アプリのバックアップを完全に制御する必要がない場合は、BackupAgentHelper を使用してアプリのバックアップを処理することをおすすめします。

      現在、Android は SharedPreferences内部ストレージからファイル一式をバックアップして復元するバックアップ ヘルパーを提供しています。

マニフェストでバックアップ エージェントを宣言する

これが最も簡単な手順で、バックアップ エージェントのクラス名を決めたら、マニフェストで <application> タグ内に android:backupAgent 属性を使用して宣言します。

次に例を示します。

    <manifest ... >
        ...
        <application android:label="MyApplication"
                     android:backupAgent="MyBackupAgent">
            <activity ... >
                ...
            </activity>
        </application>
    </manifest>
    

他に、android:restoreAnyVersion 属性を使用できます。この属性は、現在のアプリのバージョンとバックアップ データを生成したバージョンと対応にかかわらず、アプリデータを復元するかどうかを示すブール値です(デフォルト値は「false」です)。詳しくは、復元データのバージョンを確認するをご覧ください。

Android Backup Service への登録

Google は、Android 2.2 以降を実行するほとんどの Android デバイス向けに Android Backup Service を使用したバックアップ トランスポートを提供します。

Android Backup Service を使用してアプリをバックアップするには、アプリをサービスに登録して Backup Service キーを受け取り、Android マニフェストで Backup Service キーを宣言します。

Backup Service キーを取得するには、Android Backup Service に登録します。登録すると、Android マニフェスト ファイルで使用する Backup Service キーと適切な <meta-data> XML コードが提供されます。これを <application> 要素の子として含めます。次に例を示します。

    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        ...
        <meta-data android:name="com.google.android.backup.api_key"
            android:value="AEdPqrEAAAAIDaYEVgU6DJnyJdBmU7KLH3kszDXLv_4DIsEIyQ" />
    </application>
    

android:name には "com.google.android.backup.api_key" を、android:value には Android Backup Service 登録で受け取った Backup Service キーを指定する必要があります。

複数のアプリがある場合は、それぞれのパッケージ名を使用して各アプリを登録する必要があります。

注: Android Backup Service が提供するバックアップ トランスポートでは、バックアップをサポートするすべての Android デバイスでの使用が保証されていません。デバイスによって、異なるトランスポートを使用したバックアップをサポートしていたり、バックアップをまったくサポートしていなかったりする場合があり、デバイスで使用されているトランスポートをアプリで把握する方法がありません。ただし、アプリのバックアップを実装する場合、常に Android Backup Service の Backup Service キーを含める必要があります。含めることで、デバイスが Android Backup Service トランスポートを使用するときにアプリがバックアップを実行できるようになります。デバイスが Android Backup Service を使用しない場合、Backup Service キーを含んだ <meta-data> 要素は無視されます。

BackupAgentHelper を拡張する

ファイル全体(SharedPreferences または内部ストレージ)をバックアップする場合、BackupAgentHelper を使用してバックアップ エージェントをビルドする必要があります。BackupAgentHelper を使用してバックアップ エージェントをビルドする場合、onBackup()onRestore() を実装する必要がないため、BackupAgent を拡張するよりもはるかに少ないコードで済みます。

BackupAgentHelper を実装する場合、バックアップ ヘルパーを 1 つ以上使用する必要があります。バックアップ ヘルパーは、BackupAgentHelper が特定の種類のデータのバックアップおよび復元処理を実行するために呼び出す特殊なコンポーネントです。Android フレームワークでは現在、以下の 2 つのヘルパーが提供されています。

BackupAgentHelper に複数のヘルパーを含めることができますが、各データタイプに必要なヘルパーは 1 つのみです。つまり、複数の SharedPreferences ファイルがある場合、必要となるのは SharedPreferencesBackupHelper 1 つだけです。

BackupAgentHelper に追加するヘルパーごとに、onCreate() メソッドで次の処理を行う必要があります。

  1. 目的のヘルパークラスのインスタンスをインスタンス化する。クラス コンストラクタで、適切なバックアップ対象ファイルを指定する必要があります。
  2. addHelper() を呼び出して BackupAgentHelper にヘルパーを追加する。

以下のセクションでは、使用可能な各ヘルパーを使用してバックアップ エージェントを作成する方法について説明します。

SharedPreferences をバックアップする

SharedPreferencesBackupHelper をインスタンス化する際、1 つ以上の SharedPreferences ファイルの名前を含める必要があります。

たとえば、user_preferences という名の SharedPreferences ファイルをバックアップする場合、BackupAgentHelper を使用する完全なバックアップ エージェントは次のようになります。

Kotlin

    // The name of the SharedPreferences file
    const val PREFS = "user_preferences"

    // A key to uniquely identify the set of backup data
    const val PREFS_BACKUP_KEY = "prefs"

    class MyPrefsBackupAgent : BackupAgentHelper() {
        override fun onCreate() {
            // Allocate a helper and add it to the backup agent
            SharedPreferencesBackupHelper(this, PREFS).also {
                addHelper(PREFS_BACKUP_KEY, it)
            }
        }
    }

    

Java

    public class MyPrefsBackupAgent extends BackupAgentHelper {
        // The name of the SharedPreferences file
        static final String PREFS = "user_preferences";

        // A key to uniquely identify the set of backup data
        static final String PREFS_BACKUP_KEY = "prefs";

        // Allocate a helper and add it to the backup agent
        @Override
        public void onCreate() {
            SharedPreferencesBackupHelper helper =
                    new SharedPreferencesBackupHelper(this, PREFS);
            addHelper(PREFS_BACKUP_KEY, helper);
        }
    }

    

これで完了です。SharedPreferencesBackupHelper には SharedPreferences ファイルのバックアップとリストアに必要なコードがすべて含まれています。

Backup Manager が onBackup()onRestore() を呼び出すとき、BackupAgentHelper がバックアップ ヘルパーを呼び出して指定したファイルのバックアップと復元を実行します。

注: SharedPreferences のメソッドはスレッドセーフであるため、バックアップ エージェントやその他のアクティビティから共有設定ファイルを安全に読み書きできます。

他のファイルをバックアップする

FileBackupHelper をインスタンス化する際、アプリの内部ストレージgetFilesDir() で指定。openFileOutput() がファイルを書き込む場所と同じ)に保存されている 1 つ以上のファイルの名前を含める必要があります。

たとえば、scoresstats という名前の 2 つのファイルをバックアップする場合、BackupAgentHelper を使用するバックアップ エージェントは次のようになります。

Kotlin

    // The name of the file
    const val TOP_SCORES = "scores"
    const val PLAYER_STATS = "stats"
    // A key to uniquely identify the set of backup data
    const val FILES_BACKUP_KEY = "myfiles"

    class MyFileBackupAgent : BackupAgentHelper() {
        override fun onCreate() {
            // Allocate a helper and add it to the backup agent
            FileBackupHelper(this, TOP_SCORES, PLAYER_STATS).also {
                addHelper(FILES_BACKUP_KEY, it)
            }
        }
    }

    

Java

    public class MyFileBackupAgent extends BackupAgentHelper {
        // The name of the file
        static final String TOP_SCORES = "scores";
        static final String PLAYER_STATS = "stats";

        // A key to uniquely identify the set of backup data
        static final String FILES_BACKUP_KEY = "myfiles";

        // Allocate a helper and add it to the backup agent
        @Override
        public void onCreate() {
            FileBackupHelper helper = new FileBackupHelper(this,
                    TOP_SCORES, PLAYER_STATS);
            addHelper(FILES_BACKUP_KEY, helper);
        }
    }

    

FileBackupHelper には、アプリの内部ストレージに保存されているファイルをバックアップおよび復元するために必要なすべてのコードが含まれています。

しかし、内部ストレージ上のファイルの読み書きはスレッドセーフではありません。バックアップ エージェントがアクティビティと同時にファイルを読み書きしないようにするには、読み取りや書き込みを実行するたびに同期ステートメントを使用する必要があります。たとえば、ファイルを読み書きするアクティビティの場合、同期ステートメントの組み込みロックとして使用するオブジェクトが必要です。

Kotlin

    // Object for intrinsic lock
    companion object {
        val sDataLock = Any()
    }

    

Java

    // Object for intrinsic lock
    static final Object sDataLock = new Object();

    

次に、ファイルを読み書きするたびに、このロックを使用して同期ステートメントを作成します。たとえば、ゲームの最新スコアをファイルに書き込むための同期ステートメントは次のとおりです。

Kotlin

    try {
        synchronized(MyActivity.sDataLock) {
            val dataFile = File(filesDir, TOP_SCORES)
            RandomAccessFile(dataFile, "rw").apply {
                writeInt(score)
            }
        }
    } catch (e: IOException) {
        Log.e(TAG, "Unable to write to file")
    }

    

Java

    try {
        synchronized (MyActivity.sDataLock) {
            File dataFile = new File(getFilesDir(), TOP_SCORES);
            RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
            raFile.writeInt(score);
        }
    } catch (IOException e) {
        Log.e(TAG, "Unable to write to file");
    }

    

同じロックを使用して読み取りステートメントを同期する必要があります。

次に、BackupAgentHelperonBackup()onRestore() をオーバーライドし、同じ組み込みロックを使用してバックアップと復元の処理を同期します。たとえば、上記の MyFileBackupAgent の例には次のメソッドが必要です。

Kotlin

    @Throws(IOException::class)
    override fun onBackup(
            oldState: ParcelFileDescriptor,
            data: BackupDataOutput,
            newState: ParcelFileDescriptor
    ) {
        // Hold the lock while the FileBackupHelper performs backup
        synchronized(MyActivity.sDataLock) {
            super.onBackup(oldState, data, newState)
        }
    }

    @Throws(IOException::class)
    override fun onRestore(
            data: BackupDataInput,
            appVersionCode: Int,
            newState: ParcelFileDescriptor
    ) {
        // Hold the lock while the FileBackupHelper restores the file
        synchronized(MyActivity.sDataLock) {
            super.onRestore(data, appVersionCode, newState)
        }
    }

    

Java

    @Override
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
              ParcelFileDescriptor newState) throws IOException {
        // Hold the lock while the FileBackupHelper performs backup
        synchronized (MyActivity.sDataLock) {
            super.onBackup(oldState, data, newState);
        }
    }

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
            ParcelFileDescriptor newState) throws IOException {
        // Hold the lock while the FileBackupHelper restores the file
        synchronized (MyActivity.sDataLock) {
            super.onRestore(data, appVersionCode, newState);
        }
    }

    

BackupAgentHelper を使用したデータのバックアップと復元に必要なものは以上です。

BackupAgent を拡張する

ほとんどのアプリでは、BackupAgent クラスを直接拡張する必要はありませんが、代わりに BackupAgentHelper を拡張してファイルを自動的にバックアップおよび復元する組み込みヘルパークラスを利用する必要があります。ただし、以下を行う必要がある場合は BackupAgent を直接拡張することもできます。

  • データ形式を変更する。たとえば、アプリデータの書式を変更する必要がある場合、バックアップ エージェントをビルドして復元処理時にアプリのバージョンをクロスチェックし、デバイスのバージョンがバックアップ データと異なる場合に互換性を保つうえで必要な作業を行います。詳細については、復元データのバージョンを確認するをご覧ください。
  • ファイル全体をバックアップする代わりに、バックアップするデータの部分と各部分の復元方法を指定できます(これは、ファイル一式ではなく、一意のエンティティとしてデータを読み書きするため、さまざまなバージョンの管理にも役立ちます)。
  • データベースのデータをバックアップする。ユーザーがアプリを再インストールしたときに復元する SQLite データベースがある場合、バックアップ処理中に適切なデータを読み取るカスタム BackupAgent をビルドし、復元処理中にテーブルを作成してデータを挿入する必要があります。

上記のいずれかのタスクを実行せずに、SharedPreferences または内部ストレージからファイル一式をバックアップする必要がある場合は、BackupAgentHelper の拡張をご覧ください。

必須メソッド

BackupAgent の作成時に以下のコールバック メソッドを実装する必要があります。

onBackup()
バックアップをリクエストすると、バックアップ マネージャーがこのメソッドを呼び出します。このメソッドでは、以下のバックアップを実行するで説明するように、デバイスからアプリデータを読み取り、バックアップするデータをバックアップ マネージャーに渡します。
onRestore()
バックアップ マネージャーは、復元処理時にこのメソッドを呼び出します。このメソッドによってバックアップ データが配信されます。バックアップ データは、以下の復元の実行で説明するように、以前の状態を復元するためにアプリで使用できます。

ユーザーがアプリを再インストールすると、システムはこのメソッドを呼び出してバックアップ データを復元しますが、アプリで復元をリクエストすることもできます。

バックアップの実行

アプリデータをバックアップするとき、バックアップ マネージャーは onBackup() メソッドを呼び出します。ここで、クラウド ストレージに保存できるように、バックアップ マネージャーにアプリデータを渡す必要があります。

バックアップ マネージャーのみがバックアップ エージェントの onBackup() メソッドを呼び出すことができます。アプリデータが変更され、バックアップを実行するたびに、dataChanged() を呼び出してバックアップ処理をリクエストする必要があります(詳しくは、バックアップをリクエストするをご覧ください)。バックアップ リクエストによって onBackup() メソッドがすぐに呼び出されるわけではありません。バックアップ マネージャーは適切な時間だけ待機し、最後にバックアップが実行された以降にバックアップを要求したすべてのアプリのバックアップを実行します。

ヒント: アプリの開発中に、bmgr ツールを使用して、バックアップ マネージャーからすぐにバックアップ処理を開始できます。

バックアップ マネージャーは onBackup() メソッドを呼び出す際、3 つのパラメータを渡します。

oldState
アプリが提供する最後のバックアップ状態を指す、オープンされている読み取り専用の ParcelFileDescriptor。これはクラウド ストレージのバックアップ データではなく、onBackup() が最後に呼び出されたときにバックアップされたデータのローカル表現です(以下の newState で定義されるか、onRestore() からのデータ。詳しくは、次のセクションをご覧ください)。onBackup() ではクラウド ストレージ内の既存のバックアップ データを読み取ることができないため、このローカル表現を使用して、前回のバックアップ以降にデータが変更されたかどうかを判別します。
data
BackupDataOutput オブジェクト。バックアップ マネージャーにバックアップ データを渡すために使用します。
newState
オープンで、読み書き可能な ParcelFileDescriptordata に配信したデータの表現を書き込むファイルを指します(ファイルの最終変更タイムスタンプと同じくらいシンプルな表現にすることができます)。このオブジェクトは、次回バックアップ マネージャーが onBackup() メソッドを呼び出すときに oldState として返されます。バックアップ データを newState に書き込まない場合、oldState はバックアップ マネージャーが次に onBackup() 呼び出すときに空のファイルを指すようになります。

上記のパラメータを使用し、onBackup() メソッドを実装して次のことを行う必要があります。

  1. oldState と現在のデータを比較して、最後のバックアップ以降にデータが変更されたかどうかを確認します。oldState でデータを読み取る方法は、newState に最初に書き込んだ方法によって異なります(ステップ 3 を参照)。ファイルの状態を記録する最も簡単な方法は、最後に変更されたタイムスタンプを使用することです。oldState からタイムスタンプを読み取って比較する方法の例を以下に示します。

    Kotlin

        val instream = FileInputStream(oldState.fileDescriptor)
        val dataInputStream = DataInputStream(instream)
        try {
            // Get the last modified timestamp from the state file and data file
            val stateModified = dataInputStream.readLong()
            val fileModified: Long = dataFile.lastModified()
            if (stateModified != fileModified) {
                // The file has been modified, so do a backup
                // Or the time on the device changed, so be safe and do a backup
            } else {
                // Don't back up because the file hasn't changed
                return
            }
        } catch (e: IOException) {
            // Unable to read state file... be safe and do a backup
        }
    
        

    Java

        // Get the oldState input stream
        FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
        DataInputStream in = new DataInputStream(instream);
    
        try {
            // Get the last modified timestamp from the state file and data file
            long stateModified = in.readLong();
            long fileModified = dataFile.lastModified();
    
            if (stateModified != fileModified) {
                // The file has been modified, so do a backup
                // Or the time on the device changed, so be safe and do a backup
            } else {
                // Don't back up because the file hasn't changed
                return;
            }
        } catch (IOException e) {
            // Unable to read state file... be safe and do a backup
        }
    
        

    何も変更されておらず、バックアップする必要がない場合は、ステップ 3 に進みます。

  2. oldState と比較してデータが変更されている場合、現在のデータを data に書き込んで、クラウド ストレージにバックアップします。

    BackupDataOutput にデータの各チャンクを「エンティティ」として書き込む必要があります。エンティティは、一意のキー文字列で識別されるフラット化されたバイナリデータ レコードです。したがって、バックアップするデータセットは、概念的には Key-Value ペアのセットになります。

    エンティティをバックアップ データセットに追加するには:

    1. writeEntityHeader() を呼び出して、書き込むデータの一意の文字列キーとデータサイズを渡します。
    2. writeEntityData() を呼び出して、データを含むバイトバッファとバッファから書き込むバイト数(writeEntityHeader() に渡されるサイズと一致する必要があります)を渡します。

    次のコードは、一部のデータをバイト ストリームに変換し、1 つのエンティティに書き込む例を示します。

    Kotlin

        val buffer: ByteArray = ByteArrayOutputStream().run {
            DataOutputStream(this).apply {
                writeInt(playerName)
                writeInt(playerScore)
            }
            toByteArray()
        }
        val len: Int = buffer.size
        data.apply {
            writeEntityHeader(TOPSCORE_BACKUP_KEY, len)
            writeEntityData(buffer, len)
        }
    
        

    Java

        // Create buffer stream and data output stream for our data
        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
        DataOutputStream outWriter = new DataOutputStream(bufStream);
        // Write structured data
        outWriter.writeUTF(playerName);
        outWriter.writeInt(playerScore);
        // Send the data to the Backup Manager via the BackupDataOutput
        byte[] buffer = bufStream.toByteArray();
        int len = buffer.length;
        data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
        data.writeEntityData(buffer, len);
    
        

    これは、バックアップするデータごとに実行します。データをエンティティに分割する方法は自由に決めることができます(エンティティを 1 つだけ使用することもできます)。

  3. バックアップ(ステップ 2)を実行するかどうかにかかわらず、現在のデータの表現を newState ParcelFileDescriptor に書き込みます。バックアップ マネージャーは、このオブジェクトを、現在バックアップされているデータの表現としてローカルで保持します。次に onBackup() を呼び出すときに、これを oldState として返します。これにより、別のバックアップが必要かどうかを判断できます(ステップ 1 で処理)。現在のデータ状態をこのファイルに書き込まない場合、次のコールバック時に oldState は空になります。

    次の例では、ファイルの最終変更タイムスタンプを使用して、現在のデータの表現を newState ファイルに保存します。

    Kotlin

        val modified = dataFile.lastModified()
        FileOutputStream(newState.fileDescriptor).also {
            DataOutputStream(it).apply {
                writeLong(modified)
            }
        }
    
        

    Java

        FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
        DataOutputStream out = new DataOutputStream(outstream);
    
        long modified = dataFile.lastModified();
        out.writeLong(modified);
    
        

注意: アプリデータをファイルに保存する場合、ファイルのアクセス中に同期ステートメントを使用して、アプリのアクティビティがファイルに書き込んでいる間にバックアップ エージェントがファイルを読み取らないようにしてください。

復元を実行する

アプリデータを復元するときは、バックアップ マネージャーがバックアップ エージェントの onRestore() メソッドを呼び出します。このメソッドを呼び出すと、バックアップ マネージャーによりバックアップ データが提供され、デバイスに復元できます。

バックアップ マネージャーのみが onRestore() を呼び出すことができます。これは、アプリがインストールされ、既存のバックアップ データが検出されたときに自動的に行われます。

注: アプリの開発中に、bmgr ツールを使用して復元処理をリクエストすることもできます。

バックアップ マネージャーは onRestore() メソッドを呼び出す際、3 つのパラメータを渡します。

data
BackupDataInput。バックアップ データを読み取ることができます。
appVersionCode
アプリの android:versionCode マニフェスト属性の値を表す整数。バックアップ時と同じデータになります。これを使用して、現在のアプリのバージョンをクロスチェックし、データ形式に互換性があるかどうかを判断できます。異なるバージョンの復元データを処理する方法については、以下の復元データのバージョンを確認するのセクションをご覧ください。
newState
オープンされている、読み書き可能な ParcelFileDescriptordata で提供された最終的なバックアップ状態を書き込む必要のあるファイルを指します。このオブジェクトは、次回 onBackup() が呼び出されたときに oldState として返されます。また、onBackup() コールバックで同じ newState オブジェクトを書き込む必要があります。書き込むことで、onBackup() に指定された oldState オブジェクトは、デバイスの復元後に初めて onBackup() が呼び出された場合でも有効になります。

onRestore() の実装で、data に対して readNextHeader() を呼び出して、データセット内のすべてのエンティティを反復処理する必要があります。検出されたエンティティごとに、以下のことを行います。

  1. getKey() を使用してエンティティ キーを取得する。
  2. エンティティ キーを、BackupAgent クラス内で静的な最終文字列として宣言する必要がある既知の Key-Value リストと比較します。キーが既知のキー文字列のいずれかと一致したら、ステートメントを入力してエンティティ データを抽出し、デバイスに保存します。
    1. getDataSize() を使用してエンティティ データのサイズを取得し、そのサイズのバイト配列を作成します。
    2. readEntityData() を呼び出して、データの格納先となるバイト配列を渡し、開始オフセットと読み取るサイズを指定します。
    3. バイト配列がいっぱいになり、自由にデータを読み取って、デバイスに書き込むことができます。
  3. データを読み取り、デバイスに書き戻した後、onBackup() のときと同じように、データの状態を newState パラメータに書き込みます。

前のセクションの例でバックアップされたデータを復元する方法を以下に示します。

Kotlin

    @Throws(IOException::class)
    override fun onRestore(data: BackupDataInput, appVersionCode: Int,
                           newState: ParcelFileDescriptor) {
        with(data) {
            // There should be only one entity, but the safest
            // way to consume it is using a while loop
            while (readNextHeader()) {
                when(key) {
                    TOPSCORE_BACKUP_KEY -> {
                        val dataBuf = ByteArray(dataSize).also {
                            readEntityData(it, 0, dataSize)
                        }
                        ByteArrayInputStream(dataBuf).also {
                            DataInputStream(it).apply {
                                // Read the player name and score from the backup data
                                playerName = readUTF()
                                playerScore = readInt()
                            }
                            // Record the score on the device (to a file or something)
                            recordScore(playerName, playerScore)
                        }
                    }
                    else -> skipEntityData()
                }
            }
        }

        // Finally, write to the state blob (newState) that describes the restored data
        FileOutputStream(newState.fileDescriptor).also {
            DataOutputStream(it).apply {
                writeUTF(playerName)
                writeInt(mPlayerScore)
            }
        }
    }

    

Java

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode,
                          ParcelFileDescriptor newState) throws IOException {
        // There should be only one entity, but the safest
        // way to consume it is using a while loop
        while (data.readNextHeader()) {
            String key = data.getKey();
            int dataSize = data.getDataSize();

            // If the key is ours (for saving top score). Note this key was used when
            // we wrote the backup entity header
            if (TOPSCORE_BACKUP_KEY.equals(key)) {
                // Create an input stream for the BackupDataInput
                byte[] dataBuf = new byte[dataSize];
                data.readEntityData(dataBuf, 0, dataSize);
                ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
                DataInputStream in = new DataInputStream(baStream);

                // Read the player name and score from the backup data
                playerName = in.readUTF();
                playerScore = in.readInt();

                // Record the score on the device (to a file or something)
                recordScore(playerName, playerScore);
            } else {
                // We don't know this entity key. Skip it. (Shouldn't happen.)
                data.skipEntityData();
            }
        }

        // Finally, write to the state blob (newState) that describes the restored data
        FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
        DataOutputStream out = new DataOutputStream(outstream);
        out.writeUTF(playerName);
        out.writeInt(mPlayerScore);
    }

    

この例では、onRestore() に渡される appVersionCode パラメータは使用されません。ただし、ユーザーのアプリのバージョンが実際にダウングレードしたとき(ユーザーがアプリのバージョンを 1.5 から 1.0 に移行した場合など)に、バックアップを実行するように選択した場合に使用できます。詳しくは、復元データのバージョンを確認するのセクションをご覧ください。

復元データのバージョンを確認する

バックアップ マネージャーがデータをクラウド ストレージに保存すると、マニフェスト ファイルの android:versionCode 属性で定義されているように、アプリのバージョンが自動的に含まれます。バックアップ マネージャーは、バックアップ エージェントを呼び出してデータを復元する前に、インストールされているアプリの android:versionCode を確認し、復元データセットに記録されている値と比較します。復元データセットに記録されたバージョンがデバイス上のアプリのバージョンよりも新しい場合、ユーザーはアプリをダウングレードしています。この場合、バックアップ マネージャーはアプリの復元処理を中止し、onRestore() メソッドを呼び出しません。これは、復元セットが古いバージョンにとって意味がないと見なされるためです。

android:restoreAnyVersion 属性を使用して、この動作をオーバーライドできます。この属性は「true」または「false」のいずれかで、復元セットのバージョンに関係なくアプリを復元するかどうかを示します。デフォルト値は「false」です。これを「true」に定義すると、バックアップ マネージャーは android:versionCode を無視し、すべての場合に onRestore() メソッドを呼び出します。その際、onRestore() メソッドでバージョンの違いを手動で確認し、バージョンが競合する場合にデータの互換性を保つために必要な手順を踏むことができます。

復元処理時にさまざまなバージョンを処理できるように、onRestore() メソッドは、復元データセットに含まれているバージョン コードを appVersionCode パラメータとして渡します。これで、PackageInfo.versionCode フィールドを使用して、現在のアプリのバージョン コードに対してクエリを行うことができます。次に例を示します。

Kotlin

    val info: PackageInfo? = try {
        packageManager.getPackageInfo(packageName, 0)
    } catch (e: PackageManager.NameNotFoundException) {
        null
    }

    val version: Int = info?.versionCode ?: 0

    

Java

    PackageInfo info;
    try {
        String name = getPackageName();
        info = getPackageManager().getPackageInfo(name, 0);
    } catch (NameNotFoundException nnfe) {
        info = null;
    }

    int version;
    if (info != null) {
        version = info.versionCode;
    }

    

次に、PackageInfo から取得した versiononRestore() に渡された appVersionCode と単純比較します。

注意: アプリで android:restoreAnyVersion を「true」に設定した場合の結果について十分ふまえておくようにしてください。onRestore() のときに、バックアップをサポートしているアプリの各バージョンでデータ形式のバリエーションが適切に考慮されていない場合、デバイスに現在インストールされているバージョンと互換性のない形式でデバイスのデータが保存される可能性があります。

バックアップをリクエストする

dataChanged() を呼び出して、いつでもバックアップ処理をリクエストすることができます。このメソッドは、バックアップ エージェントを使用してデータをバックアップすることをバックアップ マネージャーに通知します。その後、バックアップ マネージャーは適切なタイミングでバックアップ エージェントの onBackup() メソッドを呼び出します。通常、データが変更される(バックアップするアプリの設定を変更した場合など)たびにバックアップをリクエストする必要があります。dataChanged() を連続して数回呼び出した場合、バックアップ マネージャーがエージェントからのバックアップをリクエストする前に、エージェントは onBackup() の呼び出しを 1 回だけ受け取ります。

注: アプリの開発中は、bmgr ツールを使用してバックアップをリクエストし、すぐにバックアップを開始できます。

復元をリクエストする

アプリの通常使用中は、復元処理をリクエストする必要はありません。システムで自動的にバックアップ データがをチェックされ、アプリのインストール時に復元が実行されます。

注: アプリの開発中に、bmgr ツールを使用して復元処理をリクエストすることができます。

自動バックアップに移行する

アプリをフルデータ バックアップに移行するには、マニフェスト ファイルの <application> 要素で android:fullBackupOnlytrue に設定します。Android 5.1(API レベル 22)以前のデバイスでは、マニフェストのこの値は無視され、Key-Value のバックアップが続行されます。デバイスで Android 6.0(API レベル 23)以降を実行している場合、Key-Value バックアップの代わりに自動バックアップがアプリで実行されます。

ユーザーのプライバシー

Google ではユーザーの皆様が信頼を寄せていただいていることを認識しており、ユーザーの皆様のプライバシーを保護する責任について厳粛に受け止めています。Google はバックアップと復元の機能を提供するために Google サーバーとの間でバックアップ データを安全に送信します。Google のプライバシー ポリシーに従い、上記データは個人情報として扱われます。

また、Android システムのプライバシー設定でデータのバックアップ機能を無効にすることもできます。ユーザーがバックアップを無効にすると、Android Backup Service は保存したバックアップ データをすべて削除します。デバイスのバックアップは再び有効にできますが、Android Backup Service が以前に削除したデータを復元することはありません。