STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

Android版 ブランドアプリでアプリデータのバックアップ・リストアに対応しました

こんにちは @tomorrowkey です。
これまでブランドアプリではバックアップ機能が無効になっていましたが、ユーザーの利便性向上のために有効にしました。

データバックアップ機能はファイルを逐一指定もしくは除外する必要があり、面倒なので敬遠されがちな機能かもしれませんが、機種変更の際に正しいデータの移行ができたらユーザーの負担をグッと減らすことができ、ユーザー体験の向上を図ることができます。

というわけで、今回ブランドアプリで有効化されたバックアップ機能について紹介していきます。

バックアップの注意点

いきなり注意点となるのですが、むやみにバックアップを有効化することはおすすめできません。 デバイスによって値が変わる内容をバックアップの対象にしてしまうと、不具合につながる可能性があります。 

特に注意しないといけないのは、プッシュ通知についてです。プッシュ通知のフレームワークとしてFCM(Firebase Cloud Messaging)を使われている方がほとんどではないかと思いますが、このライブラリでは設定値をSharedPreferenceに保存しています。 ただ単にバックアップを有効にしてしまうとFCMの設定値もバックアップ・リストアされてしまい、プッシュ通知が送られないことがあります。

面倒かもしれませんが、機種変更したときにリストアされてほしいファイルを明示的に指定しなければなりません。

バックアップの概要

Android Developersの公式のガイドラインは次のページです。

developer.android.com

バックアップ機能を有効にするには android:allowBackuptrue にするだけで済みます。

<manifest ... >
    ...
    <application android:allowBackup="true" ... >
        ...
    </application>
</manifest>

この設定だけではアプリのデータディレクトリがすべてバックアップされてしまいます。前述のとおり、アプリの機能次第では不具合に繋がりかねないので、どのファイルをバックアップ対象とするのか指定が必要です。

その方法としては、Androidのバージョンによって異なります。公式のガイドラインでは、次のような記述があります。

  • 「アプリの自動バックアップ」を使用すると、Android 6.0(API レベル 23)以降をターゲットとして実行するアプリから、ユーザーのデータを自動的にバックアップできます。

  • Android 9 以降を搭載したデバイスでのバックアップは、デバイスの PIN、パターン、またはパスワードを使用してエンドツーエンドで暗号化されます。

  • アプリが Android 12(API レベル 31)以上をターゲットにしている場合、このセクションで説明しているとおり XML バックアップ ルールの追加セットを指定して、これらの Android のバージョンを搭載しているデバイスで導入されたバックアップと復元の変更点に対応する必要があります。

バージョンによる振る舞いの違いがバラバラにかかれており、少々読み取りづらいですが、バージョンによって次のような項目の違いがあります。(それぞれの詳細は後述します)

  • バックアップ対象ファイルの指定方法 (dataExtractionRules, fullBackupContent)
  • E2E暗号化できるか
  • D2D転送の制御できるか

Androidバージョンを踏まえてまとめると次の表のようになります。

バックアップ対象ファイルの指定方法

バックアップ対象となるファイルの指定をAndroidManifestに指定します。そのときの方法がAndroidのバージョンによって変わります。

fullBackupContent

Android 6.0からAndroid 10まで使える指定方法です。

<!-- AndroidManifest.xml -->

<application ...
  android:fullBackupContent="@xml/backup_rules">
</application>
<!-- backup_rules.xml -->

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
  <include domain="sharedpref" path="."/>
  <exclude domain="sharedpref" path="device.xml"/>
</full-backup-content>

この設定ファイルでは device.xml ファイルを除きすべてのファイルをバックアップの対象としています。

dataExtractionRules

Android 12以降で使える指定方法です。

<!-- AndroidManifest.xml -->

<application ...
  android:dataExtractionRules="data_extraction_rules.xml">
</application>
<!-- data_extraction_rules.xml -->

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
  <cloud-backup>
    <include domain="sharedpref" path="."/>
    <exclude domain="sharedpref" path="device.xml"/>
  </cloud-backup>
</data-extraction-rules>

この設定ファイルでも device.xml ファイルを除きすべてのファイルをバックアップの対象としています。

E2E暗号化の指定

Android 9.0以降で端末がバックアップファイルの暗号化に対応している場合*1、ファイルは暗号化されたうえでクラウドに保存されます。 この暗号化に対応していないケースではバックアップしないように指定できます。

fullBackupContent

fullBackupContent でのバックアップ指定はAndroid 6.0以降で可能ですが、暗号化の条件については、Android 9以降で指定が可能です。 requireFlags というフラグで設定します。

<!-- backup_rules.xml -->

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
  <include domain="sharedpref" path="." requireFlags="clientSideEncryption"/>
  <exclude domain="sharedpref" path="device.xml"/>
</full-backup-content>

この設定ファイルでは device.xml を除くファイルをバックアップ対象としていますが、そのバックアップは暗号化が有効な場合のみ動くようになっています。

dataExtractionRules

Android 12以降は disableIfNoEncryptionCapabilities というフラグで設定します。

<!-- data_extraction_rules.xml -->

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
  <cloud-backup disableIfNoEncryptionCapabilities="true">
    <include domain="sharedpref" path="."/>
    <exclude domain="sharedpref" path="device.xml"/>
  </cloud-backup>
</data-extraction-rules>

この設定ファイルでも device.xml を除くファイルをバックアップ対象としていますが、そのバックアップは暗号化が有効な場合のみ動くようになっています。

D2D転送の制御

Android 12以降ではデバイス間転送の際のファイル指定もできます。

<!-- data_extraction_rules.xml -->

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
  <cloud-backup disableIfNoEncryptionCapabilities="true">
    <include domain="sharedpref" path="."/>
    <exclude domain="sharedpref" path="device.xml"/>
  </cloud-backup>
  <device-transfer>
    <include domain="sharedpref" path="."/>
    <exclude domain="sharedpref" path="device.xml"/>
    <exclude domain="sharedpref" path="unique.xml"/>
  </device-transfer>
</data-extraction-rules>

この設定ファイルでは、デバイス間転送の際に、device.xmlunique.xml を除くファイルを対象としています。
なお、デバイス間転送の場合はE2E暗号化は必要ないので、disableIfNoEncryptionCapabilities を指定できません。

バックアップの検証

バックアップは端末の利用者の手を煩わせず自動でおこなわれますが、変更がリアルタイムにバックアップされるわけではなく、タイミングにいくつか条件があります。
なかには「デバイスがアイドル状態である」や「前回バックアップから24時間以上経過している」といったタイミングの特定が困難な条件もあり、無手で立ち向かうには検証に大きなコストを払わなければなりません。

Androidではバックアップ機能の検証のためにいくつかのツールが用意されているので、そちらを使えばかんたんに検証できます。
公式のガイドラインは次のページです。

developer.android.com

検証には物理デバイスも使用可能ですが、私は端末の初期化も含めて検証したかったので、エミュレータを使って検証をすすめました。
原理的にはAOSPイメージでもバックアップ・リストアの検証が可能なはずですが、私の試した際はGoogle APIsのエミュレータイメージでしか検証できませんでした。
例えば Android 12(API Level 32) のエミュレータイメージは次のような名前です。

system-images;android-32;google_apis;arm64-v8a

検証のために、Googleアカウントでログインしたうえで端末を再起動しておくと、不安定な挙動に遭遇することもありませんでした。

検証に使ったコマンド

検証に使ったコマンドは次のとおりです。

  • バックアップを有効にする
  • transports一覧を取得する
  • バックアップする
  • リストアする
  • バックアップの削除
  • ログを取得する

バックアップを有効にする

$ adb shell bmgr enabled

標準で有効になっていますが、念のため有効にするコマンドを実行して、状態を確認しておくと安心です。

transports一覧を取得する

$ adb shell bmgr list transports
    com.android.localtransport/.LocalTransport
    com.google.android.gms/.backup.migrate.service.D2dTransport
  * com.google.android.gms/.backup.BackupTransportService

transports はデータを転送するサービスです。バックアップ先や方法によって種類がかわります。  上記では com.google.android.gms/.backup.BackupTransportService がバックアップ先として設定されています。

バックアップ先の変更

$ adb shell bmgr transport com.android.localtransport/.LocalTransport

バックアップ先を変更できます。 com.android.localtransport/.LocalTransportクラウドではなくローカルにバックアップするtransportsです。 検証にはこのtransportsを使うと便利です。

バックアップ

$ adb shell bmgr backupnow --monitor-verbose com.example.app

意図的にバックアップを実行できます。

リストア

$ adb shell bmgr restore TOKEN com.example.app

意図的にバックアップからリストアできます。 リストアには TOKEN が必要です。これはデバイスを特定するためのもので、次のコマンドで取得できます。

$ adb shell bmgr list sets
  xxxxxxxxxxxxxxxx : Google Pixel 7
  xxxxxxxxxxxxxxxx : Google Pixel 7a
  xxxxxxxxxxxxxxxx : Google Pixel 5
  xxxxxxxxxxxxxxxx : Google Pixel 3
  xxxxxxxxxxxxxxxx : sdk_gphone64_arm64

xxxxxxxxxxxxxxxx 部分がトークンです。

バックアップの削除

$ adb shell bmgr wipe com.android.localtransport/.LocalTransport com.example.app

バックアップされたデータを削除できます。

バックアップに関するログを絞る

$ adb logcat | grep -e 'Transport rejected backup of' \
  -e 'Transport quota exceeded for' \
  -e 'BackupManagerService' \
  -e 'PFTBT'

検証中にはバックアップ・リストアがうまくいかないことがあります。その際はlogcatにログが出力されていることがありますが、ただ眺めているだけでは流量が多く、そもそもログに気づくことができません。 そこで、手元では関連するキーワードだけ絞りこめるようにコマンドを作って活用していました。

おわりに

私はこれまで煩わしさからバックアップを無効化して開発することが多かったのですが、今回改めてその便利さを実感できました。
例えばログイン情報を端末間で維持することができれば、ログインできないというサポートのコストを削ることができるし、何よりユーザーの負担が少なくサービスを利用しつづけることができます。
もし、みなさんが関わっているプロダクトでバックアップが無効化されたままなアプリがありましたら、これを機会に有効にしてみてはいかがでしょうか。

*1:端末の設定で画面ロック(PIN、パターン、パスワード)を設定していること