アプリ固有のファイルにアクセスする

多くの場合、アプリが作成するファイルは、他のアプリがアクセスする必要のないファイルまたはアクセスすべきでないファイルです。このようなアプリ固有のファイルには、以下の保存場所が用意されています。

  • 内部ストレージ ディレクトリ: このディレクトリには、永続ファイルを保存するための専用の場所と、キャッシュ データを保存するための別の場所があります。他のアプリはこれらの場所にアクセスできません。Android 10(API レベル 29)以上では、これらの場所は暗号化されます。こうした特性により、これらの場所は、特定のアプリだけがアクセスできる機密データを保存するのに適しています。

  • 外部ストレージ ディレクトリ: このディレクトリには、永続ファイルを保存するための専用の場所と、キャッシュ データを保存するための別の場所があります。他のアプリも適切な権限を持っていればこれらのディレクトリにアクセスできますが、これらのディレクトリには特定のアプリのみが使用することを目的とするファイルが保存されます。他のアプリがアクセスできるファイルを作成する場合は、それらのファイルを外部ストレージの共有ストレージ領域に保存する必要があります。

ユーザーがアプリをアンインストールすると、アプリ固有のストレージに保存されているファイルは削除されます。そのため、アプリから独立して保持されることをユーザーが期待するファイルは、このストレージに保存すべきではありません。たとえば、ユーザーがアプリで写真を撮影した場合、通常はアプリをアンインストールした後でも写真にアクセスすることを望みます。その種のファイルは、共有ストレージを使用して適切なメディア コレクションに保存する必要があります。

以下のセクションでは、アプリ固有のディレクトリにファイルを保存してアクセスする方法を説明します。

内部ストレージからアクセスする

内部ストレージ内にはアプリごとにディレクトリが用意されており、アプリはそこでアプリ固有のファイルを整理できます。1 つのディレクトリはアプリの永続ファイル用に設計されています。もう 1 つのディレクトリにはアプリのキャッシュ ファイルを格納します。これらのディレクトリ内のファイルを読み書きするためのシステム権限は必要ありません。

他のアプリは内部ストレージ内に保存されているファイルにアクセスできません。したがって、内部ストレージは他のアプリがアクセスすべきでないアプリデータを保存する場所に適しています。

ただし、これらのディレクトリは容量が小さいことが多いため、注意が必要です。アプリ固有のファイルを内部ストレージに書き込む前に、デバイスの空き領域を問い合わせる必要があります。

永続ファイルにアクセスする

アプリの通常の永続ファイルは、コンテキスト オブジェクトの filesDir プロパティを使用してアクセスできるディレクトリにあります。フレームワークには、このディレクトリ内のファイルにアクセスして保存する方法がいくつか用意されています。

ファイルにアクセスして保存する

File API を使用すると、ファイルにアクセスして保存できます。

アプリのパフォーマンスを落とさないために、同じファイルを何度も開いたり閉じたりしないでください。

次のコード スニペットは、File API の使用方法を示しています。

Kotlin

val file = File(context.filesDir, filename)

Java

File file = new File(context.getFilesDir(), filename);

ストリームを使用してファイルを保存する

File API を使用する代わりに、openFileOutput() を呼び出して、filesDir ディレクトリ内のファイルへの書き込みを行う FileOutputStream を取得することもできます。

次のコード スニペットは、ファイルにテキストを書き込む方法を示しています。

Kotlin

val filename = "myfile"
val fileContents = "Hello world!"
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
        it.write(fileContents.toByteArray())
}

Java

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

内部ストレージ内のこのディレクトリに保存されているファイルに他のアプリがアクセスできるようにするには、FLAG_GRANT_READ_URI_PERMISSION 属性を指定して FileProvider を使用します。

ストリームを使用してファイルにアクセスする

ファイルをストリームとして読み取るには、openFileInput() を使用します。

Kotlin

context.openFileInput(filename).bufferedReader().useLines { lines ->
    lines.fold("") { some, text ->
        "$some\n$text"
    }
}

Java

FileInputStream fis = context.openFileInput(filename);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

ファイルのリストを表示する

次のコード スニペットに示すように、fileList() を呼び出すことで、filesDir ディレクトリ内のすべてのファイル名を含む配列を取得できます。

Kotlin

var files: Array<String> = context.fileList()

Java

Array<String> files = context.fileList();

ネスト ディレクトリを作成する

また、Kotlin ベースのコードで getDir() を呼び出すか、Java ベースのコードでルート ディレクトリと新しいディレクトリの名前を File コンストラクタに渡すことにより、ネスト ディレクトリを作成したり、内部ディレクトリを開いたりできます。

Kotlin

context.getDir(dirName, Context.MODE_PRIVATE)

Java

File directory = context.getFilesDir();
File file = new File(directory, filename);

キャッシュ ファイルを作成する

機密データを一時的に保存する必要がある場合は、内部ストレージ内のアプリが指定したキャッシュ ディレクトリを使用してデータを保存します。すべてのアプリ固有のストレージと同様に、このディレクトリに保存されているファイルは、ユーザーがアプリをアンインストールすると削除されますが、それよりも早いタイミングで削除される場合もあります。

キャッシュ ファイルを作成するには、File.createTempFile() を呼び出します。

Kotlin

File.createTempFile(filename, null, context.cacheDir)

Java

File.createTempFile(filename, null, context.getCacheDir());

アプリは、コンテキスト オブジェクトの cacheDir プロパティと File API を使用して、このディレクトリ内のファイルにアクセスします。

Kotlin

val cacheFile = File(context.cacheDir, filename)

Java

File cacheFile = new File(context.getCacheDir(), filename);

キャッシュ ファイルを削除する

キャッシュ ファイルは随時 Android によって自動的に削除されますが、だからといってキャッシュ ファイルのクリーンアップをシステムにまかせるべきではありません。内部ストレージ内のアプリのキャッシュ ファイルを常にメンテナンスする必要があります。

内部ストレージ内のキャッシュ ディレクトリからファイルを削除するには、次のいずれかのメソッドを使用します。

  • ファイルを表す File オブジェクトで使用する delete() メソッド。

    Kotlin

    cacheFile.delete()

    Java

    cacheFile.delete();
  • アプリのコンテキストの deleteFile() メソッド。ファイル名を渡して使用します。

    Kotlin

    context.deleteFile(cacheFileName)

    Java

    context.deleteFile(cacheFileName);

外部ストレージからアクセスする

アプリ固有のファイルを保存するのに十分な容量が内部ストレージにない場合は、代わりに外部ストレージの使用を検討してください。外部ストレージには、特定のアプリ内でのみユーザーに価値を提供するファイルを整理するためのディレクトリが用意されています。1 つのディレクトリはアプリの永続ファイル用に設計されています。もう 1 つのディレクトリにはアプリのキャッシュ ファイルを格納します。

Android 4.4(API レベル 19)以上では、外部ストレージ内のアプリ固有のディレクトリにアクセスするために、ストレージに関する権限をリクエストする必要はありません。これらのディレクトリに保存されているファイルは、アプリをアンインストールすると削除されます。

Android 9(API レベル 28)以前を搭載したデバイスでは、適切なストレージ権限が付与されていれば、他のアプリに属するアプリ固有のファイルにアクセスできます。ユーザーがファイルをきめ細かく管理して整理できるように、Android 10(API レベル 29)以降をターゲットとするアプリには、外部ストレージに対する限定的なアクセス権がデフォルトで付与されます(そのようなストレージを対象範囲別ストレージと呼びます)。対象範囲別ストレージを有効にすると、アプリは他のアプリに属するアプリ固有のディレクトリにアクセスできなくなります。

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

外部ストレージはユーザーが削除できる物理ボリューム上にあるため、外部ストレージとの間でアプリ固有のデータの読み取りまたは書き込みを行う前に、ボリュームにアクセスできることを確認する必要があります。

ボリュームの状態を問い合わせるには、Environment.getExternalStorageState() を呼び出します。返された状態が MEDIA_MOUNTED の場合は、外部ストレージ内のアプリ固有のファイルの読み取りと書き込みを行うことができます。MEDIA_MOUNTED_READ_ONLY の場合は、それらのファイルの読み取りのみを行うことができます。

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

Kotlin

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

// Checks if a volume containing 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 a volume containing external storage is available
// for read and write.
private boolean isExternalStorageWritable() {
    return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}

// Checks if a volume containing external storage is available to at least read.
private boolean isExternalStorageReadable() {
     return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
            Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}

リムーバブル外部ストレージのないデバイスでは、次のコマンドで仮想ボリュームを有効にして、外部ストレージの可用性を確認するロジックをテストできます。

adb shell sm set-virtual-disk true

物理ストレージの場所を選択する

内部メモリのパーティションを外部ストレージとして割り当てているデバイスには、SD カードスロットも用意されていることがあります。つまり、その種のデバイスには外部ストレージを格納できる物理ボリュームが複数あります。したがって、アプリ固有のストレージに使用する物理ボリュームを選択する必要があります。

複数の場所にアクセスするには、ContextCompat.getExternalFilesDirs() を呼び出します。次のコード スニペットに示すように、返された配列の最初の要素がプライマリ外部ストレージ ボリュームと見なされます。空き領域がない場合またはボリュームを使用できない場合を除いて、このボリュームを使用します。

Kotlin

val externalStorageVolumes: Array<out File> =
        ContextCompat.getExternalFilesDirs(applicationContext, null)
val primaryExternalStorage = externalStorageVolumes[0]

Java

File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];

永続ファイルにアクセスする

外部ストレージからアプリ固有のファイルにアクセスするには、getExternalFilesDir() を呼び出します。

アプリのパフォーマンスを落とさないために、同じファイルを何度も開いたり閉じたりしないでください。

次のコード スニペットは、getExternalFilesDir() を呼び出す方法を示しています。

Kotlin

val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)

Java

File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);

キャッシュ ファイルを作成する

外部ストレージ内のキャッシュにアプリ固有のファイルを追加するには、externalCacheDir への参照を取得します。

Kotlin

val externalCacheFile = File(context.externalCacheDir, filename)

Java

File externalCacheFile = new File(context.getExternalCacheDir(), filename);

キャッシュ ファイルを削除する

外部キャッシュ ディレクトリからファイルを削除するには、ファイルを表す File オブジェクトで delete() メソッドを使用します。

Kotlin

externalCacheFile.delete()

Java

externalCacheFile.delete();

メディア コンテンツ

特定のアプリ内でのみユーザーに価値を提供するメディア ファイルを使用する場合は、次のコード スニペットに示すように、外部ストレージ内のアプリ固有のディレクトリに保存することをおすすめします。

Kotlin

fun getAppSpecificAlbumStorageDir(context: Context, albumName: String): File? {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    val file = File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName)
    if (!file?.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created")
    }
    return file
}

Java

@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (file == null || !file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

DIRECTORY_PICTURES などの API 定数で指定されたディレクトリ名を使用することが重要です。そうしたディレクトリ名により、ファイルがシステムによって適切に処理されることが保証されます。事前定義済みのサブディレクトリ名のいずれもファイルに適合しない場合は、代わりに nullgetExternalFilesDir() に渡すことができます。そうすると、外部ストレージ内のアプリ固有のルート ディレクトリが返されます。

空き領域を問い合わせる

多くのユーザーのデバイスでは空き領域があまり残っていないため、アプリでは慎重にストレージ領域を使用する必要があります。

保存するデータの量が事前にわかっている場合は、getAllocatableBytes() を呼び出して、アプリが使用できるデバイスの空き容量を確認できます。getAllocatableBytes() の戻り値は、デバイスの現在の空き容量よりも大きくなることがあります。これは、システムが他のアプリのキャッシュ ディレクトリから削除できるファイルを発見したためです。

アプリのデータを保存するのに十分な空き領域がある場合は、allocateBytes() を呼び出します。それ以外の場合は、デバイスから一部のファイルを削除するか、デバイスからすべてのキャッシュ ファイルを削除するようユーザーに求めることができます。

アプリがデバイスの空き容量を問い合わせる方法の例を次のコード スニペットに示します。

Kotlin

// App needs 10 MB within internal storage.
const val NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

val storageManager = applicationContext.getSystemService<StorageManager>()!!
val appSpecificInternalDirUuid: UUID = storageManager.getUuidForPath(filesDir)
val availableBytes: Long =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid)
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
        appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP)
} else {
    val storageIntent = Intent().apply {
        // To request that the user remove all app cache files instead, set
        // "action" to ACTION_CLEAR_APP_CACHE.
        action = ACTION_MANAGE_STORAGE
    }
}

Java

// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

StorageManager storageManager =
        getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
            appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
    // To request that the user remove all app cache files instead, set
    // "action" to ACTION_CLEAR_APP_CACHE.
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
}

ストレージ管理アクティビティを作成する

アプリは、起動時にアプリがユーザーのデバイスに保存したデータをユーザーが管理できるカスタム アクティビティを宣言、作成できます。マニフェスト ファイルの android:manageSpaceActivity 属性を使用して、このカスタム「容量管理」アクティビティを宣言します。ファイル マネージャー アプリは、アプリがアクティビティをエクスポートしない場合(つまり、アクティビティが android:exportedfalse に設定する場合)でも、このアクティビティを呼び出すことができます。

一部のデバイス ファイルを削除するようユーザーに依頼する

デバイス上のファイルを選択して削除するようユーザーに求めるには、ACTION_MANAGE_STORAGE アクションを含むインテントを呼び出します。このインテントによって、ユーザーにプロンプトが表示されます。デバイスの使用可能な空き容量をこのプロンプトに表示することもできます。このユーザー フレンドリーな情報を表示するには、次の計算の結果を使用します。

StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()

すべてのキャッシュ ファイルを削除するようユーザーに依頼する

デバイス上のすべてのアプリからキャッシュ ファイルを削除するようユーザーに求めることもできます。そのためには、ACTION_CLEAR_APP_CACHE インテント アクションを含むインテントを呼び出します。

参考情報

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

動画