Android ストレージのユースケースとおすすめの方法

ユーザーがファイルをより細かく制御して整理できるように、Android 10 では対象範囲別ストレージという新しいアプリ用ストレージ パラダイムが導入されました。対象範囲別ストレージは、アプリがデバイスの外部ストレージにファイルを保存しアクセスする方法を刷新します。対象範囲別ストレージをサポートするようにアプリを移行するには、このガイドで概説する一般的なストレージ ユースケースのおすすめの方法に従ってください。ユースケースは、メディア ファイルの処理メディア以外のファイルの処理という 2 つのカテゴリに分かれています。

多くの場合、アプリが作成するファイルは、他のアプリがアクセスする必要のないファイルまたはアクセスすべきでないファイルです。システムには、このようなファイルを管理するためのアプリ固有の保存場所が用意されています。

Android でファイルを保存しアクセスする方法について詳しくは、ストレージのトレーニング ガイドをご覧ください。

メディア ファイルを処理する

このセクションでは、メディア ファイル(動画、画像、音声ファイル)を扱う一般的なユースケースと、アプリで使用できる大まかな方法について説明します。そうした各ユースケースと、詳細を含む各セクションへのリンクを次の表にまとめます。

ユースケース 概要
すべての画像ファイルまたは動画ファイルを表示する Android のすべてのバージョンで同じ方法を使用します。
特定のフォルダの画像または動画を表示する Android のすべてのバージョンで同じ方法を使用します。
写真から位置情報にアクセスする アプリで対象範囲別ストレージを使用する場合は、1 つの方法を使用します。アプリで対象範囲別ストレージをオプトアウトしている場合は、別の方法を使用します。
新規ダウンロードの保存先を定義する アプリで対象範囲別ストレージを使用する場合は、1 つの方法を使用します。アプリで対象範囲別ストレージをオプトアウトしている場合は、別の方法を使用します。
ユーザーのメディア ファイルをデバイスにエクスポートする Android のすべてのバージョンで同じ方法を使用します。
1 回の操作で複数のメディア ファイルを変更または削除する Android 11 では 1 つの方法を使用します。Android 10 の場合は、対象範囲別ストレージをオプトアウトし、代わりに Android 9 以前の方法を使用します。
既存の単一の画像を読み込む Android のすべてのバージョンで同じ方法を使用します。
単一の画像をキャプチャする Android のすべてのバージョンで同じ方法を使用します。
メディア ファイルを他のアプリと共有する Android のすべてのバージョンで同じ方法を使用します。
メディア ファイルを特定のアプリと共有する Android のすべてのバージョンで同じ方法を使用します。
直接ファイルパスを使用するコードまたはライブラリからファイルにアクセスする Android 11 では 1 つの方法を使用します。Android 10 の場合は、対象範囲別ストレージをオプトアウトし、代わりに Android 9 以前の方法を使用します。

複数のフォルダの画像ファイルまたは動画ファイルを表示する

query() API を使用してメディア コレクションをクエリします。メディア ファイルをフィルタするか並べ替えるには、projectionselectionselectionArgssortOrder パラメータを調整します。

特定のフォルダの画像または動画を表示する

次の方法を使用します。

  1. アプリの権限をリクエストするで概説されているおすすめの方法に沿って、READ_EXTERNAL_STORAGE 権限をリクエストします。
  2. ディスク上のメディア アイテムへの絶対ファイルシステム パスを含む MediaColumns.DATA の値に基づいてメディア ファイルを取得します。

注: 既存のメディア ファイルにアクセスする場合は、アプリのロジックで DATA 列の値を使用できます。これは、この値に有効なファイルパスが含まれているためです。ただし、ファイルが常に使用可能であるとは限りません。ファイルベースの I/O エラーが発生した場合の処理を準備してください。

一方、メディア ファイルを作成または更新する場合は、DATA 列を使用しないでください。代わりに、DISPLAY_NAME 列と RELATIVE_PATH 列を使用します。

写真から位置情報にアクセスする

対象範囲別ストレージを使用しているアプリでは、メディア ストレージ ガイドの写真の位置情報セクションの手順を実施します。

新規ダウンロードの保存先を定義する

対象範囲別ストレージを使用するアプリの場合、ダウンロードしたメディア ファイルを保存する場所にも注意してください。

他のアプリがファイルにアクセスする必要がある場合は、ダウンロード用に明確に定義されたメディア コレクション、またはドキュメント コレクションの使用を検討してください。

Android 11 以降では、ファイルを取得するために DownloadManager を使用しても、外部のアプリ固有のディレクトリ内にあるファイルにはアクセスできません。

ユーザーのメディア ファイルをデバイスにエクスポートする

ユーザーのメディア ファイルを保存するために、適切なデフォルトの場所を定義します。

1 回の操作で複数のメディア ファイルを変更または削除する

アプリが動作する Android のバージョンに基づいてロジックを組み込みます。

Android 11 で動作するアプリ

次の方法を使用します。

  1. MediaStore.createWriteRequest() または MediaStore.createTrashRequest() を使用してアプリの書き込みまたは削除リクエストのペンディング インテントを作成してから、そのインテントを取得することで一連のファイルを編集する権限をユーザーに求めます。
  2. ユーザーのレスポンスを評価します。

    • 権限が付与されている場合は、変更または削除の操作を行います。
    • 権限が付与されていない場合は、アプリの機能に権限が必要な理由をユーザーに説明します。

Android 11 以降で利用可能なこれらのメソッドを使用してメディア ファイルのグループを管理する方法についてご確認ください。

Android 10 で動作するアプリ

Android 10(API レベル 29)をターゲットとするアプリの場合は、対象範囲別ストレージをオプトアウトし、Android 9 以前の方法を使用してこの操作を行います。

Android 9 以前で動作するアプリ

次の方法を使用します。

  1. アプリの権限をリクエストするで概説されているおすすめの方法に沿って、WRITE_EXTERNAL_STORAGE 権限をリクエストします。
  2. MediaStore API を使用してメディア ファイルを変更または削除します。

既存の単一の画像を読み込む

既存の単一の画像を読み込む場合(ユーザーのプロフィール写真として使用するなど)、アプリは操作に独自の UI を使用するか、システムの選択ツールを使用できます。

独自のユーザー インターフェースを表示する

次の方法を使用します。

  1. アプリの権限をリクエストするで概説されているおすすめの方法に沿って、READ_EXTERNAL_STORAGE 権限をリクエストします。
  2. query() API を使用してメディア コレクションをクエリします
  3. 結果をアプリのカスタム UI に表示します。

システムの選択ツールを使用する

読み込む画像を選択するようユーザーに求める ACTION_GET_CONTENT インテントを使用します。

システムの選択ツールでユーザーに提示して選択させる画像の種類をフィルタする場合は、setType() または EXTRA_MIME_TYPES を使用できます。

単一の画像をキャプチャする

アプリで使用する単一の画像をキャプチャする場合(ユーザーのプロフィール写真として使用するなど)、ACTION_IMAGE_CAPTURE インテントを使用して、ユーザーに対し、デバイスのカメラを使用して写真を撮影するよう求めます。キャプチャされた写真は MediaStore.Images テーブルに保存されます。

メディア ファイルを他のアプリと共有する

insert() メソッドを使用して、MediaStore にレコードを直接追加します。詳細については、メディア ストレージ ガイドのアイテムを追加するをご覧ください。

メディア ファイルを特定のアプリと共有する

ファイル共有の設定ガイドで説明されているように、Android FileProvider コンポーネントを使用します。

直接ファイルパスを使用するコードまたはライブラリからファイルにアクセスする

アプリが動作する Android のバージョンに基づいてロジックを組み込みます。

Android 11 で動作するアプリ

次の方法を使用します。

  1. アプリの権限をリクエストするで概説されているおすすめの方法に沿って、READ_EXTERNAL_STORAGE 権限をリクエストします。
  2. 直接ファイルパスを使用してファイルにアクセスします。

詳しくは、直接ファイルパスを使用してメディア ファイルを開く方法についてのセクションをご覧ください。

Android 10 で動作するアプリ

Android 10(API レベル 29)をターゲットとするアプリの場合は、対象範囲別ストレージをオプトアウトし、Android 9 以前の方法を使用してこの操作を行います。

Android 9 以前で動作するアプリ

次の方法を使用します。

  1. アプリの権限をリクエストするで概説されているおすすめの方法に沿って、WRITE_EXTERNAL_STORAGE 権限をリクエストします。
  2. 直接ファイルパスを使用してファイルにアクセスします。

メディア以外のファイルを処理する

このセクションでは、メディア以外のファイルを処理する一般的なユースケースと、アプリで使用できる大まかな方法について説明します。そうした各ユースケースと、詳細を含む各セクションへのリンクを次の表にまとめます。

ユースケース 概要
ドキュメント ファイルを開く Android のすべてのバージョンで同じ方法を使用します。
セカンダリ ストレージ ボリューム上のファイルに書き込む Android 11 では 1 つの方法を使用します。Android の以前のバージョンでは別の方法を使用します。
以前の保存先から既存のファイルを移行する 可能であれば、ファイルを対象範囲別ストレージに移行します。必要に応じて、Android 10 の対象範囲別ストレージをオプトアウトします。
コンテンツを他のアプリと共有する Android のすべてのバージョンで同じ方法を使用します。
メディア以外のファイルをキャッシュに保存する Android のすべてのバージョンで同じ方法を使用します。
メディア以外のファイルをデバイスにエクスポートする アプリで対象範囲別ストレージを使用する場合は、1 つの方法を使用します。アプリで対象範囲別ストレージをオプトアウトしている場合は、別の方法を使用します。

ドキュメント ファイルを開く

ACTION_OPEN_DOCUMENT インテントを使用して、システムの選択ツールで開くファイルを選択するようユーザーに求めます。システムの選択ツールでユーザーに提示して選択させるファイルの種類をフィルタする場合は、setType() または EXTRA_MIME_TYPES を使用できます。

たとえば、次のコードを使用してすべての PDF、ODT、TXT ファイルを検索できます。

Kotlin

startActivityForResult(
        Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            type = "*/*"
            putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(
                    "application/pdf", // .pdf
                    "application/vnd.oasis.opendocument.text", // .odt
                    "text/plain" // .txt
            ))
        },
        REQUEST_CODE
      )

Java

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
                "application/pdf", // .pdf
                "application/vnd.oasis.opendocument.text", // .odt
                "text/plain" // .txt
        });
        startActivityForResult(intent, REQUEST_CODE);

セカンダリ ストレージ ボリューム上のファイルに書き込む

セカンダリ ストレージ ボリュームには、SD カードが含まれます。StorageVolume クラスを使用すると、特定のストレージ ボリュームに関する情報にアクセスできます。

アプリが動作する Android のバージョンに基づいてロジックを組み込みます。

Android 11 で動作するアプリ

次の方法を使用します。

  1. 対象範囲別ストレージ モデルを使用します。
  2. Android 10(API レベル 29)以前をターゲットとします。
  3. WRITE_EXTERNAL_STORAGE 権限を宣言します。
  4. 次のいずれかのアクセスを行います。
    • MediaStore API を使用したファイル アクセス。
    • Filefopen() などの API を使用した直接ファイルパス アクセス。

以前のバージョンで動作するアプリ

ストレージ アクセス フレームワークを使用すると、ユーザーは、アプリがファイルを書き込めるセカンダリ ストレージ ボリューム上の場所を選択できます。

以前の保存先から既存のファイルを移行する

ディレクトリは、アプリ固有のディレクトリや公開共有ディレクトリでない場合に、以前の保存先と見なされます。アプリが以前の保存先でファイルを作成または使用する場合は、アプリのファイルを対象範囲別ストレージでアクセスできる場所に移行し、対象範囲別ストレージ内のファイルを扱うために必要な変更をアプリに加えることをおすすめします。

データ移行のために以前の保存先へのアクセス権を維持する

アプリのファイルを対象範囲別ストレージでアクセスできる場所に移行するには、以前の保存先へのアクセス権を維持する必要があります。使用する方法は、アプリのターゲット API レベルによって異なります。

Android 11 をターゲットとするアプリの場合
  1. preserveLegacyExternalStorage フラグを true に設定して以前のストレージ モデルを保持し、ユーザーが Android 11 をターゲットとするアプリの新しいバージョンにアップグレードする際に、ユーザーのデータを移行できるようにします。

  2. 対象範囲別ストレージをオプトアウトするに進み、Android 10 デバイスの以前の保存先にあるファイルにアプリで引き続きアクセスできるようにします。

Android 10 をターゲットとするアプリの場合

対象範囲別ストレージをオプトアウトして、Android バージョン間でアプリの動作を維持しやすくします。

アプリデータを移行する

アプリで移行の準備が整ったら、次の方法を使用します。

  1. Android 10 以前をターゲットとします。
  2. 対象範囲別ストレージをオプトアウトして、移行する必要のあるファイルにアプリがアクセスできるようにします。
  3. File API を使用して、ファイルを /sdcard/ 内の現在の場所から対象範囲別ストレージでアクセスできる場所に移動するコードをデプロイします。

    1. プライベート アプリ ファイルを、getExternalFilesDir() メソッドで返されるディレクトリに移動します。
    2. メディア以外の共有ファイルを、Downloads/ ディレクトリのアプリ専用サブディレクトリに移動します。
  4. アプリの以前のストレージ ディレクトリを /sdcard/ ディレクトリから削除します。

ユーザーは、アプリの新しいバージョンをインストールした後、デバイスでデータ移行プロセスを完了します。分析イベントを作成することで、ユーザーベース全体の移行プロセスをモニタリングできます。

ユーザーがデータを移行した後、Android 11 をターゲットとするアプリの別のアップデートを公開します。

コンテンツを他のアプリと共有する

アプリのファイルを他の 1 つのアプリと共有するには、FileProvider を使用します。すべてのアプリ間でファイルを共有する必要がある場合は、アプリごとにコンテンツ プロバイダを使用して、アプリがコレクションに追加されたときにデータを同期することをおすすめします。

メディア以外のファイルをキャッシュに保存する

使用する方法は、キャッシュに保存する必要があるファイルの種類によって異なります。

メディア以外のファイルをデバイスにエクスポートする

メディア以外のファイルを保存するために、適切なデフォルトの場所を定義します。ユーザーがファイルをアプリ固有のディレクトリから一般的にアクセスしやすい場所にエクスポートできるようにします。MediaStore のダウンロードまたはドキュメントのコレクションを使用して、メディア以外のファイルをデバイスにエクスポートします。

アプリ固有のファイルを処理する

アプリが作成するファイルが、他のアプリがアクセスする必要のないファイルまたはアクセスすべきでないファイルである場合は、これらのファイルをアプリ固有のストレージ ロケーションに保存できます。

内部ストレージ ディレクトリ

他のアプリはこれらの場所にアクセスできません。Android 10(API レベル 29)以上では、これらの場所は暗号化されます。これらの場所は、アプリだけがアクセスできる機密データを保存するのに適しています。

外部ストレージ ディレクトリ

アプリ固有のファイルを保存するのに十分な容量が内部ストレージにない場合は、代わりに外部ストレージの使用を検討してください。別のアプリが適切な権限を持っている場合、そのアプリはこれらのディレクトリにアクセスできますが、これらのディレクトリに保存されているファイルは、アプリ専用に使用することを目的としています。

Android 4.4(API レベル 19)以降では、外部ストレージ内のアプリ固有のディレクトリにアクセスするために、ストレージに関する権限をリクエストする必要はありません。

ユーザーがアプリをアンインストールすると、アプリ固有のストレージに保存されているファイルは削除されます。そのため、このストレージを使用して、ユーザーがアプリとは別に保持することを想定しているものを保存しないでください。

対象範囲別ストレージを一時的にオプトアウトする

アプリが対象範囲別ストレージに完全に対応するまでは、テスト製品版アプリの両方で一時的にオプトアウトできます。

テストでオプトアウトする

Android 10(API レベル 29)以降では、アプリのテストはデフォルトでストレージ サンドボックスで実行されます。このサンドボックスにより、アプリはアプリ固有のディレクトリと一般公開ディレクトリの外にあるファイルにアクセスできなくなります。

テストでホスト用のファイル(スクリーンショット、デバッグデータ、カバレッジ データ、パフォーマンス指標など)が出力される場合、そうしたファイルをグローバル ディレクトリに書き込むことができます。そのためには、am instrument を呼び出す関連ハーネスに次のフラグを追加します。

-e no-isolated-storage 1

このフラグは、インストルメント化されたテストケースのすべての動作に影響し、呼び出されるすべてのテストコードに影響します。そのため、このフラグを使用した場合、アプリと対象範囲別ストレージの互換性を検証できません。テスト出力では代わりに、シェルで読み取り可能な、アプリを対象とするストレージに書き込むことをおすすめします。その後、アプリを対象とするディレクトリを pull できます。pull 元のディレクトリを判断するには、getExternalMediaDirs() を呼び出します。

製品版アプリでオプトアウトする

Android 10(API レベル 29)以前をターゲットとするアプリの場合、製品版アプリで対象範囲別ストレージを一時的にオプトアウトできます。ただし、Android 10 をターゲットとする場合は、アプリのマニフェスト ファイルで requestLegacyExternalStorage の値を true に設定する必要があります。

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting
       Android 10. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

Android 10 以前をターゲットとしているアプリが対象範囲別ストレージを使用したときにどのように動作するのかテストするには、requestLegacyExternalStorage の値を false に設定して、対象範囲別ストレージをオプトインします。Android 11 を搭載したデバイスでテストする場合は、アプリの互換性フラグを使用して、対象範囲別ストレージの有無にかかわらずアプリの動作をテストすることもできます。

参考情報

Android ストレージの詳細については、次の資料をご覧ください。

ブログ投稿