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

アプリの内部ストレージ ディレクトリは、アプリのパッケージ名に応じて、Android ファイル システム内の特定の場所に指定され、下記の API でアクセスできます。

空き領域をクエリする

保存するデータの量が事前に判明している場合は、getFreeSpace() または getTotalSpace() を呼び出すことで、IOException を生じさせることなく十分な空き領域が利用可能かどうかを調べることができます。2 つのメソッドはそれぞれ、ストレージ ボリューム内で現在利用可能な領域とストレージ ボリューム内の総領域を取得できます。この情報は、ストレージ ボリュームの使用量が一定のしきい値を超えないようにするうえでも役立ちます。

ただし、getFreeSpace() によって示されたバイト数まで書き込みができるとは限りません。返された数値が、保存するデータのサイズよりも数 MB 以上多かった場合や、ファイル システムの使用率が 90% 未満の場合は、続行しても問題ありません。そうでない場合は、ストレージへの書き込みは避けてください。

ファイルを書き込む

ファイルを内部ストレージに保存する場合は、次の 2 つのメソッドのいずれかを呼び出すことで、該当するディレクトリを File オブジェクトとして取得することができます。

getFilesDir()
作成元アプリの内部ディレクトリを示す File を返します。
getCacheDir()

作成元アプリの一時キャッシュ ファイルの内部ディレクトリを示す File を返します。不要になったファイルは必ず削除し、使用する最大メモリ量に関して適切なサイズ制限(1 MB など)を実装してください。

注: キャッシュ ファイルは、システム内のストレージ容量が少なくなると、警告なく削除されることがあります。

上記のディレクトリのいずれかに新しいファイルを作成するには、File() コンストラクタを使用し、対応するメソッドによって取得した File(内部ストレージ ディレクトリを指定するオブジェクト)を渡します。たとえば、次のようになります。

Kotlin

    val file = File(context.filesDir, filename)
    

Java

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

セキュリティを強化するには、Jetpack セキュリティ ライブラリを使用します。次のコード スニペットをご覧ください。

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

    // Creates a file with this name, or replaces an existing file
    // that has the same name. Note that the file name cannot contain
    // path separators.
    val fileToWrite = "my_sensitive_data.txt"
    val encryptedFile = EncryptedFile.Builder(
        File(directory, fileToWrite),
        context,
        masterKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build()

    encryptedFile.bufferedWriter().use { writer ->
        writer.write("MY SUPER-SECRET INFORMATION")
    }
    

Java

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

    // Creates a file with this name, or replaces an existing file
    // that has the same name. Note that the file name cannot contain
    // path separators.
    String fileToWrite = "my_sensitive_data.txt";
    try {
        EncryptedFile encryptedFile = new EncryptedFile.Builder(
                new File(directory, fileToWrite),
                context,
                masterKeyAlias,
                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
        ).build();

        // Write to a file.
        BufferedWriter writer = new BufferedWriter(new FileWriter(encryptedFile));
        writer.write("MY SUPER-SECRET INFORMATION");
    } catch (GeneralSecurityException gse) {
        // Error occurred getting or creating keyset.
    } catch (IOException ex) {
        // Error occurred opening file for writing.
    }
    

また、openFileOutput() を呼び出して、内部ディレクトリ内にファイルに書き込みを行う 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!";
    FileOutputStream outputStream;

    try {
        outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
        outputStream.write(fileContents.getBytes());
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    

なお、openFileOutput() メソッドにはファイルモード パラメータが必要です。MODE_PRIVATE を渡すと、作成元アプリ専用の非公開ファイルになります。他のモード オプションの MODE_WORLD_READABLEMODE_WORLD_WRITEABLE は、API レベル 17 でサポートが終了しています。Android 7.0(API レベル 24)以降は、サポートが終了したモードを使用すると SecurityException がスローされます。アプリ専用のファイルを他のアプリと共有する必要がある場合は、FileProvider を使用して FLAG_GRANT_READ_URI_PERMISSION 属性を指定してください。詳細については、ファイルを共有するをご覧ください。

Android 6.0(API レベル 23)以前の場合、ファイルモードを「誰でも読み取り可能」に設定すると、アプリ専用の内部ファイルを別のアプリでも読み取ることができるようになります。ただし、別のアプリが読み取りを行う場合は、作成元アプリのパッケージ名とファイル名を知っている必要があります。明示的にファイルを読み取り可能や書き込み可能に設定しない限り、別のアプリから作成元アプリの内部ディレクトリを閲覧することはできず、読み取りアクセス権限や書き込みアクセス権限が付与されることもありません。内部ストレージ内のファイルに対して MODE_PRIVATE を使用している限り、別のアプリがアクセス可能になることは決してありません。

キャッシュ ファイルを書き込む

ファイルのキャッシュを保存する必要がある場合は、createTempFile() を使用します。たとえば、次のメソッドの場合、URL オブジェクトからファイル名を抽出し、その名前の付いたファイルを作成元アプリの内部キャッシュ ディレクトリ内に作成します。

Kotlin

    // For a more secure solution, use EncryptedFile from the Security library
    // instead of File.
    private fun getTempFile(context: Context, url: String): File? =
            Uri.parse(url)?.lastPathSegment?.let { filename ->
                File.createTempFile(filename, null, context.cacheDir)
            }
    

Java

    private File getTempFile(Context context, String url) {
        // For a more secure solution, use EncryptedFile from the Security library
        // instead of File.
        File file;
        try {
            String fileName = Uri.parse(url).getLastPathSegment();
            file = File.createTempFile(fileName, null, context.getCacheDir());
        } catch (IOException e) {
            // Error while creating file
        }
        return file;
    }
    

createTempFile() を使用して作成したファイルは、作成元アプリ専用のキャッシュ ディレクトリ内に配置されます。不要になったファイルは定期的に削除してください。

既存のファイルを開く

セキュリティ ライブラリを使用すると、ファイルを安全に読み取ることができます。次のコード スニペットをご覧ください。

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

    val fileToRead = "my_sensitive_data.txt"
    lateinit var byteStream: ByteArrayOutputStream
    val encryptedFile = EncryptedFile.Builder(
        File(directory, fileToRead),
        context,
        masterKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build()

    val contents = encryptedFile.bufferedReader().useLines { lines ->
        lines.fold("") { working, line ->
            "$working\n$line"
        }
    }
    

Java

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

    String fileToRead = "my_sensitive_data.txt";
    ByteArrayOutputStream byteStream;

    EncryptedFile encryptedFile = new EncryptedFile.Builder(
            File(directory, fileToRead),
            context,
            masterKeyAlias,
            EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build();

    StringBuffer stringBuffer = new StringBuffer();
    try (BufferedReader reader =
                 new BufferedReader(new FileReader(encryptedFile))) {

        String line = reader.readLine();
        while (line != null) {
            stringBuffer.append(line).append('\n');
            line = reader.readLine();
        }
    } catch (IOException e) {
        // Error occurred opening raw file for reading.
    } finally {
        String contents = stringBuffer.toString();
    }
    

また、openFileInput(name) を呼び出すことで、ファイルの名前を渡すこともできます。

fileList() を呼び出すと、アプリ内のすべてのファイル名の配列を取得できます。

ディレクトリを開く

内部ファイル システム上のディレクトリを開くには、以下のメソッドを使用します。

getFilesDir()
アプリに一意に関連付けられているファイル システム ディレクトリを示す File オブジェクトを返します。
getDir(name, mode)
アプリの一意のファイル システム ディレクトリ内に新しいディレクトリを作成します(あるいは、既存のディレクトリを開きます)。この新しいディレクトリは、getFilesDir() で取得できるディレクトリの内部に配置されます。
getCacheDir()
アプリに一意に関連付けられているファイル システム キャッシュ ディレクトリを示す File オブジェクトを返します。このディレクトリは一時ファイル用であり、定期的にクリーンアップする必要があります。このディレクトリ内のファイルは、システム内のディスク容量が少なくなると削除されることがあります。キャッシュ ファイルを読み取る際は、その前にキャッシュ ファイル自体が存在しているか必ず確認してください。

上記のディレクトリのいずれかに新しいファイルを作成するには、File() コンストラクタを使用し、対応するメソッドによって取得した File オブジェクト(内部ストレージ ディレクトリを指定するオブジェクト)を渡します。たとえば、次のようになります。

Kotlin

    val directory = context.filesDir
    val file = File(directory, filename)
    

Java

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

ファイルを削除する

不要になったファイルは、必ず削除する必要があります。ファイルを削除する最も簡単な方法は、File オブジェクトに対して delete() を呼び出すことです。

Kotlin

    myFile.delete()
    

Java

    myFile.delete();
    

ファイルが内部ストレージに保存されている場合は、Context に対して deleteFile() を呼び出すことで、ファイルを検索して削除することもできます。

Kotlin

    myContext.deleteFile(fileName)
    

Java

    myContext.deleteFile(fileName);
    

注: ユーザーがアプリをアンインストールすると、Android システムによって以下のファイルが削除されます。

  • 内部ストレージに保存したすべてのファイル
  • getExternalFilesDir() を使用して外部ストレージに保存したすべてのファイル

上記以外にも、getCacheDir() を使用して作成したキャッシュ ファイルは、すべて定期的に手動で削除してください。また、不要になったファイルも定期的に削除してください。

参考リンク

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

コードラボ