简体   繁体   English

如何在 android 中安全地将文件保存到磁盘?

[英]How to safely save a file to disk in android?

I have written an android app that saves (potentially) large files to the SD Card.我编写了一个 android 应用程序,可以将(可能)大文件保存到 SD 卡中。 Occasionally I get an IOException during the write operation which causes the file to be left in a corrupt state. Based on the answer to this question:有时,我在写操作期间会遇到 IOException,这会导致文件留在损坏的 state 中。基于对这个问题的回答:

Question: How to safely write to a file? 问题:如何安全地写入文件?

the strategy I should use is to create a temporary file and then copy this file once the write has completed.我应该使用的策略是创建一个临时文件,然后在写入完成后复制该文件。 My questions are:我的问题是:

1) Is this the safest approach on Android? 1) 这是 Android 上最安全的方法吗? (eg Can you copy files on the android sd card and if so is the operation atomic?) (例如,您可以复制 android SD 卡上的文件吗?如果可以,该操作是原子操作吗?)

2) In an embedded system with limited resources (disk space) does anyone know of another strategy for safely writing to a disk? 2)在资源(磁盘空间)有限的嵌入式系统中,有人知道另一种安全写入磁盘的策略吗? (instead of creating two large files) (而不是创建两个大文件)

Thanks谢谢

The typical way to safely create a file on most reasonable platforms (Linux/Android is one of them) is to create a temporary file and then rename the file, as mentioned in the question & answers that you linked to. 在大多数合理平台(Linux / Android 就是其中之一)上安全创建文件的典型方法是创建一个临时文件,然后重命名该文件,如您链接到的问题和答案中所述。 Note the emphasis on rename ; 注意重写重命名 ; renaming a file is usually an atomic operation within the same filesystem , copying one is not. 重命名文件通常同一文件系统中的原子操作,复制文件系统不是。 In addition, this method only requires enough space for a single copy of the data. 此外,此方法仅需要足够的空间来存储单个数据副本。

Therefore you create a file in the target directory using a temporary name and then use File.renameTo() to give it a proper name. 因此,您使用临时名称在目标目录中创建一个文件,然后使用File.renameTo()为其指定正确的名称。 By using a standard naming convention for the temporary files you can always find and delete them, even if your application terminates unexpectedly due to eg a device power-off. 通过对临时文件使用标准命名约定,您始终可以找到并删除它们,即使您的应用程序由于例如设备电源关闭而意外终止也是如此。

If you are really paranoid, you may want to insert a few calls to FileDescriptor.sync() or equivalent... 如果你真的是偏执狂,你可能想要插入几个调用FileDescriptor.sync()或等效的...

EDIT: 编辑:

BTW, you do not mention what kind of IOException your are getting and whether you have tracked down its cause. 顺便说一下,你没有提到你正在获得什么样的IOException以及你是否已经找到了它的原因。 If it's due to insufficient space, fine, but if we are talking about a faulty SD card, then there is no such thing as "safe" in this case. 如果是因为空间不足,那很好,但是如果我们谈论的是一张有问题的SD卡,那么在这种情况下就没有“安全”这样的东西了。

EDIT 2: 编辑2:

In order to check the available free space, you can create a File object for the destination directory (ie the directory where your file will end up) and call File.getFreeSpace() . 为了检查可用空间,可以为目标目录(即文件最终到达的目录)创建File对象,并调用File.getFreeSpace() Keep in mind that this check does not provide any guarantees - you may still end up without enough space if eg another process writes data to the medium. 请记住,此检查不提供任何保证 - 如果另一个进程将数据写入介质,您可能仍然没有足够的空间。

I know this is too late to answer, but will help anyone who come across this will find it helpful. 我知道现在回答已经太晚了,但是会帮助遇到这种情况的人发现它有用。

You need to use AtomicFile . 您需要使用AtomicFile

Following code snippet may help. 以下代码段可能会有所帮助。

private void writeFile(File file, byte[] data) {
    file.mkdirs();
    try {
        AtomicFile aFile = new AtomicFile(file);
        FileOutputStream fos = aFile.startWrite();
        fos.write(data);
        aFile.finishWrite(fos);
    } catch (IOException ioe) {
        Log.e(TAG, "Cannot write file " + file.getPath());
    }
}

As mentioned in Pawan's answer, AtomicFile is recommended for writing file safely.正如 Pawan 的回答中提到的,建议使用AtomicFile来安全地写入文件。

But you still need to be careful when using it, sometimes it's not that 'atomic'.但是你在使用它的时候还是要小心,有时候它不是那么“原子”的。

You can find AtomicFile in android.util , android.support.v4.util and androidx.core.util .您可以在android.utilandroid.support.v4.utilandroidx.core.util AtomicFile But their implementation are DIFFERENT in different versions of API level / support library / androidx library , so please pay attention to your dependency version before using it.但是他们的实现在API API level / support library / androidx library的不同版本是不同的,所以在使用之前请注意你的依赖版本。

In general, you should use the latest version of androidx's AtomicFile .一般来说,你应该使用最新版本的androidx 的 AtomicFile It's in androidx.core:core , and dependenced by androidx.appcompat:appcompat .它在androidx.core:core中,并且依赖于androidx.appcompat:appcompat


Let's see what actually happens when we try to write a file with AtomicFile in lower versions.让我们看看当我们尝试在较低版本中使用AtomicFile编写文件时实际发生了什么。

API level: [17, 29] / support library: [22.1.0, 28.0.0] / androidx core: [1.0.0, 1.3.2] API 级别:[17, 29] / 支持库:[22.1.0, 28.0.0] / androidx 核心:[1.0.0, 1.3.2]

public FileOutputStream startWrite() throws IOException {
    // Step 1: rename base file to backup file, or delete it.
    if (mBaseName.exists()) {
        if (!mBackupName.exists()) {
            if (!mBaseName.renameTo(mBackupName)) {
                Log.w("AtomicFile", "Couldn't rename file " + mBaseName
                    + " to backup file " + mBackupName);
                }
        } else {
            mBaseName.delete();
        }
    }

    // At this moment,the base file does not exist anyway.

    // Step 2: open output stream to base file.
    FileOutputStream str;
    try {
        str = new FileOutputStream(mBaseName);
    } catch (FileNotFoundException e) {
        // ...
    }
    return str;
}

As you can see, AtomicFile prefers to back up the base file before writing it.如您所见, AtomicFile更喜欢在写入基础文件之前对其进行备份。 This can indeed provide an effective means of recovery when writing to the base file fails.这确实可以在写入基础文件失败时提供一种有效的恢复手段。 But you can only say that the write operation is safely, not atomically.但是你只能说写操作是安全的,不是原子的。

Imagine what happens if the system loses power between steps 1 and 2, the base file will be lost, and only the backup file will remain.想象一下,如果系统在步骤 1 和步骤 2 之间断电,会发生什么情况,基础文件将丢失,只剩下备份文件。

Also, because the FileOutputStream points to the base file, writing is not atomically either.另外,因为FileOutputStream指向基本文件,所以写入也不是原子的。


But google has improved the implementation of AtomicFile in high versions.但是google在高版本中改进了AtomicFile的实现。

API level: 30+ / androidx core: 1.5.0+ API 级别:30+ / androidx 核心:1.5.0+

public FileOutputStream startWrite() throws IOException {
    // Step 1: recover backup file, which was used in old version.
    if (mLegacyBackupName.exists()) {
        rename(mLegacyBackupName, mBaseName);
    }

    // Step 2: open output stream to new file, not the base file.
    try {
        return new FileOutputStream(mNewName);
    } catch (FileNotFoundException e) {
        // ...
    }
}

The improved AtomicFile takes another approach: when you write content to the output stream returned by startWrite() , you actually write to a new file (a temporary file with suffix of .new ), and the new file will be rename to base file after finishWrite() called.改进后的 AtomicFile 则采用了另一种方式:当你向startWrite()返回的 output stream 写入内容时,实际上是写入了一个新文件(后缀为.new的临时文件),之后新文件将重命名为基础文件finishWrite()

Now, the content of the base file will only have two cases:现在,基础文件的内容只会有两种情况:

  1. The writing fails, and the content of base file remains unchanged.写入失败,base文件内容不变。
  2. The writing is successful.写入成功。

It's real atomic now.现在是真正的原子。

I don't know if I'd copy it, but renaming the temporary file is typical. 我不知道我是否复制它,但重命名临时文件是典型的。 If you get an exception during writing, just delete the temporary file. 如果在写入期间出现异常,只需删除临时文件即可。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM