概要
モバイルアプリにおいてはしばしば画像・動画・音声といったメディアファイルや、ドキュメントファイルを扱う場面が多く存在します。
ことAndroidにおいては比較的柔軟にファイルを扱うことができるものの、OSバージョンアップの度に大きく仕様変更が入ることが多々あり開発者泣かせになっている節があります。
本記事においてはそんな複雑化している Android アプリのファイルストレージに関する近年の仕様の変遷と、執筆時点でのベストプラクティスについて記述しています。
Androidのファイルストレージのユースケース
本記事執筆時点での最新OSをターゲットにする場合のファイル操作のベストプラクティスについてです。
参考:
Android アプリで扱うファイルには主に以下の2種があります
(ここでいう"ファイル"には構造化DBやKey-Value Preferenceのファイルは含まれません。)
- メディアファイル
- 画像、動画、オーディオ、ダウンロードファイル
- メディア以外のファイル
- 上記以外ですべてのファイル
- 構造化DBやPreferenceのファイルは除く
メディアファイル
Media Store API を利用します。
Media Store APIの使い方については@e10dokupさんの以下の資料が非常に分かりやすいのでこちらを参照ください🙇
メディア以外のファイル
Storage Access Framework を利用します。
例: ACTION_OPEN_DOCUMENT_TREE で任意のディレクトリ内のファイルやフォルダを操作する
val REQUEST_CODE = 100
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
prefSetting = PreferenceManager.getDefaultSharedPreferences(this)
path_button.setOnClickListener {
// Intent 起動
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_CODE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val uri = data?.data ?: return
// 権限の永続化
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
// ファイルの保存 or 読み出しの処理
// もしくは URI を SharedPreference 等に保存して他クラスで処理することも可能
}
}
ファイルストレージに関する仕様の変遷
公式ドキュメントの情報を基に、Android 4.4 以降におけるファイルストレージに関する仕様の変遷をまとめました。
(紙面の都合上、Android 4.4 以前についての情報は割愛させていただいております...)
なお、Androidには「内部ストレージ」と「外部ストレージ」という概念があります。
これはそれぞれ以下のような意味合いになります:
- 内部ストレージ(Internal storage): アプリ専用のファイルを保存する領域
- 外部ストレージ(External storage): 端末やSDカード内にある他アプリと共有されたファイルの保存領域
内部ストレージ/外部ストレージは、端末の内臓ストレージ/SDカードのような区分とは直接関係していません。
また、「アプリ固有のストレージ」「共有ストレージ」という概念もあり、それぞれ以下のような意味合いになります。
- アプリ固有のストレージ(App-specific storage): 内部ストレージと外部ストレージにそれぞれ設けられたアプリ専用の保存領域
- 共有ストレージ(Shared storage): 外部ストレージに設けられている、画像などのメディアファイルやドキュメントファイルのための保存領域
Android 4.4
- 外部ストレージの読み込みには READ_EXTERNAL_STORAGE 権限が必要になりました。
- 内部ストレージの読み書きに権限は不要です。
-
Storage Access Framework(SAF) が実装されました。
- 以下の Intent でAndroid標準のFile Pickerが起動し、ファイル単位で読み取り、書き込みの権限を付与できるようになりました。
- ACTION_OPEN_DOCUMENT
- ACTION_CREATE_DOCUMENT
Android 5.0
- SAFの機能が拡張され、ACTION_OPEN_DOCUMENT_TREE Intent を用いてディレクトリ全体に権限を付与することができるようになりました。
Android 6.0
- 実行時パーミッションが実装されました。
- それに伴い、WRITE_EXTERNAL_STORAGE 権限を付与するためには実行時パーミッションによる権限付与が必要になりました。
Android 7.0
- ファイルのパーミッションに関していくつか追加で制限が入りました。
- アプリのプライベートディレクトリのパーミッションが
0700
になりました。 -
file://
で始まるURIを使用したファイルの共有に FileProvider の利用が推奨されるようになりました。 - DownloadManager.COLUMN_LOCAL_FILENAME が使用不可になりました。
Android 8.0
ファイルストレージに関する仕様の変更はありません。
Android 9.0
- アプリ固有のファイルへの外部のアプリからのアクセスに制限が入りました。
- 他のアプリとのファイル共有については ContentProvider の使用が推奨されるようになりました。
Android 10
- 対象範囲別ストレージ が実装されました。
- 共有ストレージ内のファイルは Storage Access Framework か Media Store API を通してしかアクセスできなくなりました。
- ただし、移行措置として TargetSdkVersion <= 29 のアプリにおいては requestLegacyExternalStorage オプションを
true
にしていれば対象範囲別の利用を回避できるようになっています。 - Media Store API に
MediaStore.Downloads
テーブルが追加されました。- この変更により、Media Store API の テーブルは以下の4種となりました。
- MediaStore.Images
- MediaStore.Video
- MediaStore.Audio
- MediaStore.Downloads
Android 11
- Android 11では、ファイルストレージの仕様に関して大幅な変更が加えられました。(寧ろここがメインなのではというくらいに)
- 対象範囲別ストレージの強制化
- TargetSdkVersion >= 30 のアプリにおいては
requestLegacyExternalStorage
プロパティが使用不可になりました。 -
requestLegacyExternalStorage=false
が記述されていても無視されます。
- TargetSdkVersion >= 30 のアプリにおいては
- アプリ固有のディレクトリを Context#getExternalFilesDirs() メソッドによって得られるパス以外の場所に保存できなくなりました。
- メディアファイルに
Java File API
とfopen()
などのネイティブメソッド経由でアクセスできるようになりました。 - Android 9.0 で制限が入った他アプリからのアプリ固有データへのアクセスの制限が強化され、外部/内部ストレージともに外部アプリからのアクセスが一切不可になりました。
- Storage Access Framework でアクセス権を付与できるディレクトリに制限が入りました。なお、これらの制限は TargetSdkVersion >= 30 のときにのみ有効になります。
-
ACTION_OPEN_DOCUMENT_TREE
Intent を使用した場合、以下のディレクトリには権限を付与できなくなりました。- 内部ストレージのルートディレクトリ
- SDカードのルートディレクトリ
- Download ディレクトリ
-
ACTION_OPEN_DOCUMENT_TREE
またはACTION_OPEN_DOCUMENT
を使用した場合、以下のディレクトリ配下のファイルには権限を付与できなくなりました。-
Android/data/
とその配下のディレクトリ -
Android/obb/
とその配下のディレクトリ
-
- SAF以外の方法でメディア以外のファイルにアクセス可能となる権限として、
MANAGE_EXTERNAL_STORAGE
が追加されました。- Play Store にこの権限を付与したアプリをリリースするには、一定の基準をクリアする必要があります。
- Media Store API を用いる場合は
WRITE_EXTERNAL_STORAGE
権限とWRITE_MEDIA_STORAGE
が不要となりました。- これらの権限はもはや不要?
Android 12
- 新機能追加が主で、既存の実装に対していますぐ修正が必要になるような変更は含まれていません。
- Media Store API の MediaStore.Audio テーブルに
Recordings/
ディレクトリが追加されました。 - Media Store API の getMediaUri メソッドにおいて MediaDocumentsProvider 形式のURIがサポートされるようになりました。
-
MANAGE_MEDIA 権限が追加されました。
-
MANAGE_EXTERNAL_STORAGE
権限が付与されている場合に、追加の確認ダイアログを表示させないようにする権限のようです。
-
- StorageManager に getManageSpaceActivityIntent が追加されました。
- 他のファイルマネージャーなどにアクティビティを公開できるようです。
Android 13
- メディアファイルに対するアクセス権限の仕様が一部変更され、
READ_EXTERNAL_STORAGE
権限の代わりに、扱うメディアファイルごとに以下の3種類の権限の付与が必要となりました。- 画像及び写真:
READ_MEDIA_IMAGES
- 動画:
READ_MEDIA_VIDEO
- 音声ファイル:
READ_MEDIA_AUDIO
- 画像及び写真:
Android 14
-
Selected Photos Access
という仕組みが導入され、iOSのように特定のメディアへ個別にアクセス権限を付与できるようになりました。- 詳細についてはドキュメント を参照してください。
- SAFなどの手段で読み込もうとしているZipファイルのエントリに「..」や「/」が含まれていると、ZipException が発生するようになりました。
Android 15
ベータ版 4.1(2024 年 7 月)の時点では、
ストレージに影響のある仕様変更はありません。
まとめ
本記事では、Android OS のファイルストレージに関して、ユースケースに応じた実装と、過去から現在までにおける仕様の変遷についてご紹介しました。
ファイルストレージに関する仕様はOSのバージョンアップによる影響が大きく、設計に頭を悩ませがちな部分ではありますが、本記事が読者の皆様の一助となりましたら幸いです。