外部ストレージにファイルを保存する

他のアプリと共有するファイルや、ユーザーがパソコンからアクセスするファイルの保存には、外部ストレージを使用することをおすすめします。

外部ストレージは通常、SD カードなどのリムーバブル デバイスで使用できます。Android ではこれらのデバイスを、/sdcard のようなパスを使用して表します。

ストレージの権限をリクエストして、そのストレージが使用可能であることを確認した後、以下のタイプのファイルを保存できるようになります。

  • パブリック ファイル: 他のアプリおよびユーザーから自由に利用可能にすべきファイル。ユーザーがアプリをアンインストールしても、これらのファイルはユーザーから利用可能なままにしておく必要があります。たとえば、アプリでキャプチャした写真は、パブリック ファイルとして保存する必要があります。
  • プライベート ファイル: アプリ固有のディレクトリに保存されるファイル。ファイルへのアクセスには Context.getExternalFilesDir() を使用します。ユーザーがアプリをアンインストールすると、これらのファイルはクリーンアップされます。プライベート ファイルは外部ストレージに保存されるため、技術的にはユーザーや他のアプリからアクセス可能ですが、アプリの外部のユーザーにとっては価値がありません。このディレクトリは、他のアプリと共有しないファイルの保存に使用します。

このガイドでは、外部ストレージ デバイスで利用可能なファイルの管理方法について説明します。内部ストレージに保存されているファイルの操作方法については、内部ストレージに保存されているファイルを管理する方法についてのガイドをご覧ください。

仮想外部ストレージ デバイスをセットアップする

リムーバブル外部ストレージのないデバイスでは、次のコマンドを使用することで、仮想ディスクを有効にしてテストすることができます。

    adb shell sm set-virtual-disk true
    

外部ストレージの権限をリクエストする

Android には、外部ストレージに保存されているファイルへのアクセスに関する以下の権限が用意されています。

READ_EXTERNAL_STORAGE
外部ストレージ デバイス内のファイルにアプリからアクセスできます。
WRITE_EXTERNAL_STORAGE
アプリで外部ストレージ デバイスにファイルを書き込むことができます。また、アプリでその中のファイルを変更できます。この権限を持つアプリには、READ_EXTERNAL_STORAGE 権限も自動的に付与されます。

Android 4.4(API レベル 19)以降では、ストレージ関連の権限がなくても、アプリ固有のディレクトリでファイルの読み取りと書き込みを行うことができます。アプリが Android 4.3(API レベル 18)以前をサポートしており、アプリ固有のディレクトリにのみアクセスしたい場合は、maxSdkVersion 属性を追加して、Android のバージョンが 4.3 以前の場合にのみ権限をリクエストすることを宣言する必要があります。

    <manifest ...>
        <!-- If you need to modify files in external storage, request
             WRITE_EXTERNAL_STORAGE instead. -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
                         android:maxSdkVersion="18" />
    </manifest>
    

外部ストレージを使用できるか確認する

外部ストレージは使用できない場合があります(ユーザーが別のマシンにストレージをマウントしている場合や、外部ストレージを提供する SD カードを取り外した場合など)。そのため、アクセスする前に、当該ボリュームが使用可能かどうかを確認する必要があります。外部ストレージの状態を照会するには、getExternalStorageState() を呼び出します。返された状態が MEDIA_MOUNTED の場合は、ファイルの読み取りと書き込みを行うことができます。MEDIA_MOUNTED_READ_ONLY の場合は、ファイルの読み取りのみを行うことができます。

たとえば次のメソッドは、ストレージが使用可能かどうかを判断するのに役立ちます。

Kotlin

    /* Checks if external storage is available for read and write */
    fun isExternalStorageWritable(): Boolean {
        return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
    }

    /* Checks if external storage is available to at least read */
    fun isExternalStorageReadable(): Boolean {
         return Environment.getExternalStorageState() in
            setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
    }
    

Java

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    /* Checks if external storage is available to at least read */
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
            Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }
    

パブリック ディレクトリに保存する

他のアプリがアクセスできる必要がある外部ストレージにファイルを保存する場合は、次のいずれかの API を使用します。

  • 写真、音声ファイル、動画クリップを保存する場合は、MediaStore API を使用します。
  • その他のファイル(PDF ドキュメントなど)を保存する場合は、ACTION_CREATE_DOCUMENT インテントを使用します。このインテントはストレージ アクセス フレームワークに含まれています。

メディア スキャナに対してファイルを非表示にする場合は、アプリ固有のディレクトリに .nomedia という名前の空のファイルを格納します(ファイル名のドット プレフィックスに注意してください)。これにより、メディア スキャナがメディア ファイルを読み取ったり、MediaStore API を介して他のアプリにメディア ファイルを提供したりしないようにすることができます。

プライベート ディレクトリに保存する

アプリに限定公開されているファイルを外部ストレージに保存する場合、getExternalFilesDir() を呼び出して、希望するディレクトリのタイプを示す名前を渡すことにより、アプリ固有のディレクトリを作成できます。このように作成した各ディレクトリは、アプリの外部ストレージ ファイルをすべてカプセル化する親ディレクトリに追加されます(この親ディレクトリは、ユーザーがアプリをアンインストールしたときにシステムによってクリーンアップされます)。

次のコード スニペットは、個々のフォトアルバム用のディレクトリを作成する方法を示しています。

Kotlin

    fun getPrivateAlbumStorageDir(context: Context, albumName: String): File? {
        // Get the directory for the app's private pictures directory.
        val file = File(context.getExternalFilesDir(
                Environment.DIRECTORY_PICTURES), albumName)
        if (!file?.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created")
        }
        return file
    }
    

Java

    public File getPrivateAlbumStorageDir(Context context, String albumName) {
        // Get the directory for the app's private pictures directory.
        File file = new File(context.getExternalFilesDir(
                Environment.DIRECTORY_PICTURES), albumName);
        if (!file.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created");
        }
        return file;
    }
    

API 定数(DIRECTORY_PICTURES など)で指定されたディレクトリ名を使用することが重要です。こうすることで、ファイルがシステムによって正しく処理されるようになります。たとえば、DIRECTORY_RINGTONES に保存されたファイルは、システムのメディア スキャナにより、音楽ではなく着信音として分類されます。

事前定義済みのどのサブディレクトリ名もファイルに適合しない場合は、代わりに getExternalFilesDir() を呼び出して null を渡すことができます。これにより、外部ストレージ上のアプリのプライベート ディレクトリのルート ディレクトリが返されます。

複数の保存場所から選択する

内部メモリのパーティションを外部ストレージ用に割り当てることができるデバイスに、SD カードスロットが用意されていることもあります。つまり、そのデバイスには 2 つの異なる外部ストレージ ディレクトリが存在することになります。そのため、「プライベート」ファイルを外部ストレージに書き込む場合、どちらを使用するか選択する必要があります。

Android 4.4(API レベル 19)以降では、getExternalFilesDirs() を呼び出すことで、どちらの場所にもアクセスできます。このメソッドは、各保存場所のエントリを含む File 配列を返します。配列の最初のエントリはプライマリ外部ストレージとみなされます。いっぱいになっているか使用できない場合を除いて、この場所を使用する必要があります。

アプリが Android 4.3 以前をサポートしている場合は、サポート ライブラリの静的メソッドである ContextCompat.getExternalFilesDirs() を使用する必要があります。このメソッドは常に File 配列を返しますが、デバイスで Android 4.3 以前が実行されている場合、配列に含まれるプライマリ外部ストレージのエントリは 1 つだけです(保存場所がもう 1 つある場合でも、Android 4.3 以前ではその保存場所にアクセスできません)。

一意のボリューム名

Android 10(API レベル 29)以降を対象とするアプリは、システムが各外部ストレージ デバイスに割り当てる一意の名前にアクセスできます。この命名システムにより、コンテンツを効率的に整理してインデックスに登録し、新しいコンテンツの保存場所を管理することができます。

メインの共有ストレージ デバイスには必ず VOLUME_EXTERNAL_PRIMARY という名前が付けられます。他のボリュームは MediaStore.getExternalVolumeNames() を呼び出すことによって検出できます。

特定のボリュームを照会、挿入、更新、削除するには、次のコード スニペットに示すように、MediaStore API で使用できる getContentUri() メソッドのいずれかにボリューム名を渡します。

    // Assumes that the storage device of interest is the 2nd one
    // that your app recognizes.
    val volumeNames = MediaStore.getExternalVolumeNames(context)
    val selectedVolumeName = volumeNames[1]
    val collection = MediaStore.Audio.Media.getContentUri(selectedVolumeName)
    // ... Use a ContentResolver to add items to the returned media collection.
    

参考情報

デバイスのストレージへのファイルの保存について詳しくは、以下のリソースをご覧ください。

コードラボ