Unmotivated

やる気はない

Android におけるストレージまとめ

Android で外部ファイルを保存する場合、どこに何を保存すれば良いのか?それぞれの違いは何なのか?
内蔵メモリなのか?SDカードなのか?

いい加減しっかり把握しておきたいと思ったので、まとめてみました。

ざっくりまとめ

それぞれの領域の名前は、この記事の中で区別するためにつけたもので、アクセス権減は非 root ユーザから見た図です。

ディレクトリ取得メソッド アプリ専用? ユーザがアクセス可能? クリア方法 アプリ削除時に
内部データ領域 Context.getFilesDir() yes no データを消去 消える
内部キャッシュ領域 Context.getCacheDir() yes no キャッシュを消去 消える
外部データ領域 Context.getExternalFilesDir() yes yes データを消去 消える
外部キャッシュ領域 Context.getExternalCacheDir() yes yes キャッシュを消去 消える
外部公開領域 Environment.getExternalStorageDirectory() no yes 提供されず 消えない
外部公開共有領域 Environment.getExternalStoragePublicDirectory() no yes 提供されず 消えない

 

  • ユーザに見せたくないファイルは内部の領域を使う
  • 消えるとアプリが維持できなくなるようなファイルは、内部データ領域
  • 消えても全く問題ないような一時ファイルは、ユーザが消しやすいキャッシュ領域が良さそう
  • アプリが消えても消したくないファイルは、外部公開領域にディレクトリ掘って作る
  • 端末全体に共有したいようなファイルは外部公開共有領域に入れる
    • カメラから取得した画像を保存するなら Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) にディレクトリ掘るとか
    • アプリで生成した画像を保存するなら Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE) にディレクトリ掘るとか

その他のまめ知識

  • キャッシュ領域にサイズ制限はないが、ストレージが圧迫されてくるとシステムによって消されることがあるらしい(未実験)
  • マルチアカウント利用されている場合でも、 Context からディレクトリを取得していれば良きに計らってくれるみたい
  • アプリをアンインストール時にも残しておきたいが、ユーザに見られたくない!消されたくない!は「できない」
    • このようなデータは公開領域に置くのが主流っぽいが、隠蔽することはできない
    • せいぜい見つけづらいところに置くくらいしかできないので、補助的に使うしかない

保存先はSDカード?内蔵ストレージ?

結論から言うと、 getExternal 系のメソッドでとれるディレクトリは、SDカードの場合もあれば内蔵ストレージの場合もあるようです。
基本的にアプリ側からSDカードを指定して扱うようなことは できない というのが正しそうです。

getExternal〜 でとれるディレクトリはシステムに決められたプライマリストレージであり、それがSDカードである保証はないということ。 よく getExternalStorageDirectory() でSDカード内のディレクトリがとれるという記事を見ますが、正確にはSDカードである可能性が高いディレクトリがとれるというのが正しそうです。

External という名前がついているので紛らわしいですが(というか完全に勘違いする)、これはSDカードのことをさしている訳ではなく、 システム領域の外のストレージという意味で External とついているようです。(参考)

とはいえ getExternal〜 で取得したディレクトリを使う場合は、プライマリストレージがSDカードである可能性を考慮しなくてはいけないため、マウントされているかどうかなどを確かめる必要があります。

1
2
3
4
5
6
7
8
String state = Environment.getExternalStorageState()
if(state.equals(Environment.MEDIA_MOUNTED)){
    // マウントされていて read/write 可能
}else if(state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)){
    // マウントされているが read 権限しかない
}else{
    // マウントされていない
}

今指定されているプライマリストレージが取り外し可能(普通はイコールSDカードになると思います)かどうかは以下を確認することで分かるので、取り外されるとまずい場合などは判別できそうです。

1
2
3
if(Environment.isExternalStorageRemovable()) {
    // 取り外し可能
}

ちなみに手持ちの 206SH / Android 4.2.2 で確認してみたところ、Environment のさしているプライマリストレージは内蔵メモリのようでした。
ついでに 206SH でプライマリストレージを切り替えられるのかどうかを調べてみたけれど、結局切り替わる条件は不明でした。

1
端末設定 > ストレージ > 優先インストール先

あたりで切り替えられそうだなーと睨んで色々と試行錯誤してみましたが、プライマリストレージは変わりませんでした。

(おまけ)各ディレクトリのファイルパスなど

Nexus7 のほうは getExternal〜 でとれるディレクトリがマルチアカウント対応の Emulated なものになってました。
4.2 で追加されたマルチアカウントですが、 206SH では封じられているようです。

206SH/Android4.2.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 各種ディレクトリ
Context.getFilesDir()                           :/data/data/com.example/files
Context.getCacheDir()                           :/data/data/com.example/cache
Context.getExternalCacheDir()                   :/storage/sdcard0/Android/data/com.example/cache
Context.getExternalFilesDir()                   :/storage/sdcard0/Android/data/com.example/files
Environment.getExternalStorageDirectory()       :/storage/sdcard0
Environment.getExternalStoragePublicDirectory() :/storage/sdcard0/DCIM
Environment.isExternalStorageEmulated()         :false
Environment.isExternalStorageRemovable()        :false

# マウント状況 - getFilesDir() などでとれるところ
/dev/block/platform/msm_sdcc.1/by-name/userdata /data ext4 rw,nosuid,nodev,noatime,discard,noauto_da_alloc,data=ordered 0 0
# マウント状況 - getExternalStorageDirectory() でとれるところ
/dev/fuse /storage/sdcard0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
# マウント状況 - 実際のSDカードがマウントされているところ
/dev/block/vold/179:33 /storage/sdcard0/external_sd vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0602,dmask=0602,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
Nexus7(2013)/Android4.4.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 各種ディレクトリ
Context.getFilesDir()                           :/data/data/com.example/files
Context.getCacheDir()                           :/data/data/com.example/cache
Context.getExternalCacheDir()                   :/storage/emulated/0/Android/data/com.example/cache
Context.getExternalFilesDir()                   :/storage/emulated/0/Android/data/com.example/files
Environment.getExternalStorageDirectory()       :/storage/emulated/0
Environment.getExternalStoragePublicDirectory() :/storage/emulated/0/DCIM
Environment.isExternalStorageEmulated()         :true
Environment.isExternalStorageRemovable()        :false

# マウント状況 - getFilesDir() などでとれるところ
/dev/block/platform/msm_sdcc.1/by-name/userdata /data ext4 rw,seclabel,nosuid,nodev,noatime,nomblk_io_submit,errors=panic,data=ordered 0 0
# マウント状況 - getExternalStorageDirectory() でとれるところの実体?
/dev/fuse /mnt/shell/emulated fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0

# /storage/emulated に置いてあるシンボリックリンク
shell@flo:/ $ ls -la /storage/emulated/
lrwxrwxrwx root     root              2014-01-30 19:47 legacy -> /mnt/shell/emulated/0

Comments