Android: Android 10 からの対象範囲別ストレージ (Scoped Storage) とメディアファイルアクセスのまとめ

Android 10 から、対象範囲別ストレージ (Scoped Storage) が導入され、アプリ固有のストレージと、アプリ外部のストレージが厳密に区別され扱われるようになりました。

アプリ外部の画像を選択したり、外部のアプリと共有可能なメディア領域へ画像を保存したりする場合には対象範囲別ストレージを正しく扱う必要があります。

Andorid 11 以上の環境では、Android 11 対応 (targetSdkVersion = 30) でアプリをビルドすると対象範囲別ストレージが強制的に有効となります。`requestLegacyExternalStorage` 設定も無視されるようになります。対象範囲別ストレージに正しく対応しましょう。

https://developer.android.com/about/versions/11/privacy/storage?hl=ja#scoped-storage

この記事では Android 10 以上の対象範囲別ストレージ環境を前提としています。Android 9 以下の違いを考慮する必要がある箇所では都度補足しています。

## 対象範囲別ストレージのまとめ

対象範囲別ストレージは 3 種類のストレージがあります。

* アプリ固有ストレージ
* メディア領域
* その他のストレージ

https://developer.android.com/training/data-storage?hl=ja

### アプリ固有ストレージ

アプリ固有のストレージへは特別な権限なくアクセスが可能です。アプリ固有のストレージはアプリアンインストール時にデータ削除されます。

アプリ固有のストレージ一覧

| API | 領域 |
| --- | --- |
| Context.filesDir | アプリ固有内部ストレージ File ディレクトリ |
| Context.cacheDir | アプリ固有内部ストレージ Cache ディレクトリ |
| Context.getExternalFilesDir() | アプリ固有外部ストレージ File ディレクトリ |
| Context.externalCacheDir | アプリ固有外部ストレージ Cache ディレクトリ |

### メディア領域

外部のアプリから共有可能なメディア領域は `MediaStoreAPI` 経由でアクセスします。MediaStoreAPI では、OS によってファイルがデータベース管理されており、Content Provider 経由でデータベースやファイルへアクセスします。以下の種類のファイルとメタデータを扱えます。

* 画像 (`MediaStore.Images`テーブル)
* 動画 (`MediaStore.Video`テーブル)
* 音声 (`MediaStore.Audio`テーブル)
* ダウンロードファイル (`MediaStore.Downloads`テーブル)

メディア領域へ保存したファイルはアプリをアンインストールしても削除されません。

メディア領域へのファイルの **書き込みに権限は不要** です。MediaStoreAPI 経由でファイルを書き込めば、そのファイルは他のアプリから見えるようになります。

メディア領域からファイルを読み込む場合は、権限によって以下の動作となります。

| 権限の状態 | MediaStore の読み取り |
| --- | --- |
| READ_EXTERNAL_STORAGE 権限なし | 他のアプリが保存したメディアファイルは見えず、自アプリが保存したメディアファイルだけを取得できる。<br/>アプリを再インストールすると、アンインストール前に自アプリが保存したメディアファイルも取得できなくなる。 |
| READ_EXTERNAL_STORAGE 権限あり | 他のアプリが保存したメディアファイルも含めて、すべてのメディアファイルが取得できる。 |

### その他のストレージ

ドキュメントなど、ファイルやディレクトリを直接扱いたい場合は [ストレージアクセスフレームワーク](https://developer.android.com/training/data-storage/shared/documents-files?hl=ja) を利用できます。
この記事ではストレージアクセスフレームワークについては説明しません。

## 要件ごとの解説

対象範囲別ストレージ環境で、実現したい要件ごとに解説します。

公式ドキュメントにも、ユースケースと　Android バージョンを考慮した実装の使い分けがまとまっています。こちらも参照してみてください。

https://developer.android.com/training/data-storage/use-cases?hl=ja

### 外部のアプリからアクセスできるように画像を保存したい

Android 10 以上でも、Android 9 以下でも共通の実装となりますが、Android 9 以下では権限が必要になります。

Android 10 以上では、MediaStoreAPI 経由で画像ファイルを保存します。**WRITE_EXTERNAL_STORAGE 権限は不要です**。

Android 9 以下では、**WRITE_EXTERNAL_STORAGE 権限を得て**から MediaStoreAPI 経由で画像ファイルを保存します。

詳細は以下の記事を参照してください。

https://qiita.com/irgaly/items/70a0f0599d78eafc1136

### アプリ外の画像を選択させて、アプリから読み取りたい

Android 10 以上でも、Android 9 以下でも共通の実装となります。

アプリ外の画像選択は外部の画像選択アプリを Intent で呼び出します。
ユーザーが外部の画像選択アプリ経由で画像を選択するため、 **READ_EXTERNAL_STORAGE 権限は不要です**。

https://qiita.com/irgaly/items/b9bbef4de73669113ddf

### アプリ外の画像一覧を取得して、アプリ内で画像を表示したい

Android 10 以上でも、Android 9 以下でも共通の実装になります。(Android 9 以下では MediaStoreAPI の扱いが少しだけ異なるところはあります)

画像選択アプリを経由せずに、直接アプリ内で画像を表示したい場合は MediaStoreAPI 経由でファイルを取得します。アプリ外の画像一覧を取得するためには **READ_EXTERNAL_STORAGE 権限が必要です**。

詳細は以下の記事を参照してください。

https://qiita.com/irgaly/items/910815eb21f63b2c157b

### カメラアプリで写真を撮影し、その画像を取得したい

Android 10 以上でも、Android 9 以下でも共通の実装になります。

外部のカメラアプリを Intent で呼び出し、自アプリの **アプリ固有ストレージ** に画像ファイルを保存させます。
カメラ機能に直接アクセスするわけではないため **CAMERA 権限は不要です**。アプリ固有ストレージへファイルを書き込むため、**WRITE_EXTERNAL_STORAGE 権限は不要です**。
ただし、カメラアプリからアプリ固有ストレージへ画像ファイルを書き込ませるために [FileProvider](https://developer.android.com/training/secure-file-sharing/setup-sharing?hl=ja) の設定が必要になります。

このとき、撮影された画像ファイルはアプリ固有ストレージに存在するため、メディア領域へは記録されず、外部のアプリからはアクセスできません。また、アプリをアンインストールすると画像は削除されます。OCR 画像認識や身分証写真撮影など、そのアプリ内でしか使わず、画像が消えてもユーザーが困ることはないと断言できる用途であればこの対応でも構いません。

詳細は以下の記事を参照してください。

https://qiita.com/irgaly/items/e9a245f73b83b23ed3e4

### カメラアプリで写真を撮影し、その写真を外部のアプリからもアクセスできるようにしたい

Android 10 以上でも、Android 9 以下でも共通の実装となりますが、Android 9 以下では権限が必要になります。

Android 10 以上では、外部のカメラアプリを Intent で呼び出し、自アプリの **アプリ固有ストレージ** に画像ファイルを保存させ、その後 MediaStoreAPI 経由で **メディア領域** へファイルを書き込みます。**CAMERA 権限は不要**。**WRITE_EXTERNAL_STORAGE 権限も不要**。[FileProvider](https://developer.android.com/training/secure-file-sharing/setup-sharing?hl=ja) の設定だけが必要です。

Android 9 以下でも、上記手順通りですが、MediaStoreAPI でメディア領域へファイルを書き込むために **WRITE_EXTERNAL_STORAGE 権限が必要**になります。

アプリ内ストレージに保存してしまうとアプリアンインストールで写真も消えてしまいます。ライフログやブログ記事へ添付する写真など、写真そのものが重要なデータである場合は、メディア領域へ保存することでアプリをアンインストールしても写真が消えないようにしてあげると親切です。

詳細は以下の記事を参照してください。

https://qiita.com/irgaly/items/81d0b2bba726531486c6

### カメラアプリで撮影するか、画像選択アプリから画像を選択するかをユーザーに選ばせたい

カメラアプリ連携 Intent と画像選択 Intent を組み合わせて、[Intent Chooser](https://developer.android.com/training/basics/intents/sending?hl=ja) を表示します。

必要な権限や実装の詳細は組み合わせる Intent によります。

詳細は以下の記事を参照してください。

https://qiita.com/irgaly/items/7b55c7f9e5996d5df924