将文件保存到内部存储空间中

应用的内部存储目录由位于 Android 文件系统的特殊位置(可使用以下 API 访问)的应用软件包名称指定。

查询可用空间

如果您事先知道要保存多少数据,则可以通过调用 getFreeSpace()getTotalSpace() 确定是否有足够的可用空间,而不引发 IOException。这两个方法会分别提供存储卷中的当前可用空间和总空间。这些信息还有助于避免填入存储卷的数据量超过特定阈值。

不过,系统并不能保证您可以写入的字节数能够达到 getFreeSpace() 所指示的量。如果返回的可用空间量比您要保存的数据量多几 MB,或者已占用的文件系统容量不到 90%,则可以继续保存数据。否则,您可能不应该写入存储空间。

写入文件

将文件保存到内部存储空间时,您可以通过调用以下方法之一,获取相应目录作为 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 Security 库,如以下代码段所示:

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 会使该文件成为应用的私有文件。自 API 级别 17 起,其他模式选项 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 已弃用。从 Android 7.0(API 级别 24)开始,如果您使用这些选项,Android 会抛出 SecurityException。如果您的应用需要与其他应用共享私有文件,您应改用具有 FLAG_GRANT_READ_URI_PERMISSION 属性的 FileProvider。如需了解详情,请参阅共享文件

在 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() 创建的文件位于应用的私有缓存目录下。您应定期删除不再需要的文件

打开现有文件

您可以使用 Security 库以更安全的方式读取文件,如以下代码段所示:

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();
    

如果该文件保存在内部存储空间中,您还可以通过调用 deleteFile()Context 找到并删除该文件:

Kotlin

    myContext.deleteFile(fileName)
    

Java

    myContext.deleteFile(fileName);
    

注意:如果用户卸载您的应用,Android 系统会删除以下文件:

  • 您在内部存储空间中保存的所有文件。
  • 您使用 getExternalFilesDir() 在外部存储空间中保存的所有文件。

不过,您应定期使用 getCacheDir() 手动删除创建的所有缓存文件,并定期删除不再需要的其他文件。

其他资源

如需详细了解如何将文件保存到设备存储空间中,请参阅以下资源。

Codelab