2013/02/23

AndroidアプリのデータはSDカードのどこに保存すべきか?

元ネタは↓。
Storage Options | Android Developers

Androidアプリを開発していると、大きなデータを扱わなければならないことが多々あります。
そのようなデータをSDカードに保存する場合、どこに保存すべきなのでしょうか。

SDカードではなく外部ストレージ

元ネタには以下のような記述があります。
It's possible that a device using a partition of the internal storage for the external storage may also offer an SD card slot. In this case, the SD card is not part of the external storage and your app cannot access it (the extra storage is intended only for user-provided media that the system scans).

(以下、意訳)
SDカードスロットを持ちながら、内蔵ストレージの一部を外部ストレージとして提供している端末があります。この場合、SDカードは外部ストレージではなくなり、アプリからアクセスすることは出来ません。(SDカードはユーザの作成したメディアファイルを保存するために使用され、システムスキャンによって認識されます)
私の訳が稚拙なため、ちょっとわかりにくくなってしまったので補足します。
私の Xperia Acro HD には /sdcard というディレクトリがありますが、実際にはSDカードではありません
実際のSDカードは /sdcard/external_sd というディレクトリにマウントされています。

このように、内部ストレージの一部を外部ストレージとして認識させている端末では、「実際のSDカードをアプリから使用することはできない。」ということのようです。

正確には、「実際のSDカードのパスをアプリから知ることは出来ない。」ということでしょう。
特定の端末でしか使用しないアプリという形であれば、フルパス指定することで使用することは出来ます。

というわけで、以後、SDカードではなく、外部ストレージという用語を使用することにします。

外部ストレージを使用可能か確認する

外部ストレージを使用する前には、必ずgetExternalStorageState()を使って、使用可能かどうか確認するようにしましょう。
以下は、確認するためのサンプルコードです。
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();

if (Environment.MEDIA_MOUNTED.equals(state)) {
    // 読み書き可能
    mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    // 読み取り可能(書き込み不可)
    mExternalStorageAvailable = true;
    mExternalStorageWriteable = false;
} else {
    // 読み書き不可
    mExternalStorageAvailable = mExternalStorageWriteable = false;
}

アプリ専用ファイルを保存する場所

getExternalFilesDir()を使用して、外部ストレージのファイル保存ディレクトリを取得することが出来ます。また、キャッシュディレクトリはgetExternalCacheDir()で取得することが出来ます。(API Level 8 (Android 2.2) 以上の場合2013年1月の時点で 2.1 以下のシェアは5%を切っているようなので、ここではAPI Level 8 以上だけを対象にします。
以下、簡単なサンプルと動作させた結果です。
@Override
protected void onResume() {
    super.onResume();

    Log.d(TAG, getExternalFilesDir(null).toString());
    Log.d(TAG, getExternalFilesDir("secret").toString());
    Log.d(TAG, getExternalFilesDir(Environment.DIRECTORY_RINGTONES).toString());
    Log.d(TAG, getExternalCacheDir().toString());
}

Xperia Acro HD (SO-03D) で動作させてみた結果。(端末によって結果は変わります)
/mnt/sdcard/Android/data/com.kokufu.android.test.exstoragetest/files
/mnt/sdcard/Android/data/com.kokufu.android.test.exstoragetest/files/secret
/mnt/sdcard/Android/data/com.kokufu.android.test.exstoragetest/files/Ringtones
/mnt/sdcard/Android/data/com.kokufu.android.test.exstoragetest/files/cache

getExternalFilesDir() の引数には、任意のディレクトリ名を指定することが出来ます。
null を指定すればサブディレクトリ無しの結果が得られます。

また、特殊なディレクトリとして、Environment に Ringtone等の定義があります。
これらのディレクトリは Android のメディアスキャナがサーチする対象になるようです。

これらの関数を呼んだ際に、ディレクトリが無ければ作成されます。
この時、外部ストレージへの書き込み権限が無いと、File オブジェクトではなく nullが返ってきてしまうので、AndroidManifest に以下の一文を記述しておくのを忘れないようにしましょう。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

また、アプリがアンインストールされた際、これらのディレクトリは自動的に削除されます

共有ファイルを保存する場所

他のアプリとファイルを共有したい場合や、アプリが削除されてもファイルを残しておきたい場合は、 getExternalStoragePublicDirectory() を使用します。(API Level 8 (Android 2.2) 以上)
以下、簡単なサンプルと動作させた結果です。
@Override
protected void onResume() {
    super.onResume();

    // null は動作しません
    //Log.d(TAG, Environment.getExternalStoragePublicDirectory(null).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory("secret").toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS).toString());
    Log.d(TAG, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).toString());
}  

Xperia Acro HD (SO-03D) で動作させてみた結果。(端末によって結果は変わります)
/mnt/sdcard/secret
/mnt/sdcard/Alarms
/mnt/sdcard/Download
/mnt/sdcard/Movies
/mnt/sdcard/Music
/mnt/sdcard/Notifications
/mnt/sdcard/Pictures
/mnt/sdcard/Podcasts
/mnt/sdcard/Ringtones

getExternalStoragePublicDirectory() の引数には、任意の文字列を指定することができますが、nullを指定することは出来ません。
Environment に定義されているディレクトリを指定することで Android のメディアスキャナのサーチ対象になります。
特殊なディレクトリは以下のようなものが用意されています。
詳細は Javadoc を読めばわかりますが、名前から大体想像つきますね。
Environment.DIRECTORY_ALARMS
Environment.DIRECTORY_DOWNLOADS
Environment.DIRECTORY_MOVIES
Environment.DIRECTORY_MUSIC
Environment.DIRECTORY_NOTIFICATIONS
Environment.DIRECTORY_PICTURES
Environment.DIRECTORY_PODCASTS
Environment.DIRECTORY_RINGTONES


0 件のコメント: