LoginSignup
35
34

More than 1 year has passed since last update.

Androidのファイルストレージに関する仕様の整理

Last updated at Posted at 2021-12-04

本年度の株式会社ACCESS Advent Calendar 旗振り役の Soichi Ikebe (@aqua_ix) です。

本記事はAdvent Calendarの4日目の記事になります。(本日12/4は私の24歳の誕生日でもあります🎂)
今回は"Androidのファイルストレージに関する仕様の整理"というタイトルで記事を投稿します。

概要

モバイルアプリにおいてはしばしば画像・動画・音声といったメディアファイルや、ドキュメントファイルを扱う場面が多く存在します。
ことAndroidにおいては比較的柔軟にファイルを扱うことができるものの、OSバージョンアップの度に大きく仕様変更が入ることが多々あり開発者泣かせになっている節があります。
本記事においてはそんな複雑化している Android アプリのファイルストレージに関する近年の仕様の変遷と、執筆時点でのベストプラクティスについて記述しています。

ファイルストレージに関する仕様の変遷

公式ドキュメントの情報を基に、Android 4.4 以降におけるファイルストレージに関する仕様の変遷をまとめました。
(紙面の都合上、Android 4.4 以前についての情報は割愛させていただいております...)

なお、Androidには「内部ストレージ」と「外部ストレージ」という概念があります。
これはそれぞれ以下のような意味合いになります:

  • 内部ストレージ(Internal storage): アプリ専用のファイルを保存する領域
  • 外部ストレージ(External storage): 端末やSDカード内にある他アプリと共有されたファイルの保存領域

内部ストレージ/外部ストレージは、端末の内臓ストレージ/SDカードのような区分とは直接関係していません。
また、「アプリ固有のストレージ」「共有ストレージ」という概念もあり、それぞれ以下のような意味合いになります。

  • アプリ固有のストレージ(App-specific storage): 内部ストレージと外部ストレージにそれぞれ設けられたアプリ専用の保存領域
  • 共有ストレージ(Shared storage): 外部ストレージに設けられている、画像などのメディアファイルやドキュメントファイルのための保存領域

Android 4.4

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 テーブルが追加されました。

Android 11

  • Android 11では、ファイルストレージの仕様に関して大幅な変更が加えられました。(寧ろここがメインなのではというくらいに)
  • 対象範囲別ストレージの強制化
    • TargetSdkVersion >= 30 のアプリにおいては requestLegacyExternalStorage プロパティが使用不可になりました。
    • requestLegacyExternalStorage=false が記述されていても無視されます。
  • アプリ固有のディレクトリを Context#getExternalFilesDirs() メソッドによって得られるパス以外の場所に保存できなくなりました。
  • メディアファイルに Java File APIfopen() などのネイティブメソッド経由でアクセスできるようになりました。
  • 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のファイルストレージのユースケース

執筆時点での最新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のファイル操作周りの仕様は結構複雑です。
ファイルを扱うアプリを設計する際には Media Store API を使うのか、Storage Access Framework を使うのか、ベストプラクティスに沿った実装が可能となる選択をとるようにしてください。

35
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
34