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

发布于 2024-12-12 07:38:35 字数 403 浏览 0 评论 0原文

我编写了一个 Android 应用程序,可以将(可能)大文件保存到 SD 卡上。有时,我会在写入操作期间收到 IOException,这会导致文件处于损坏状态。基于此问题的答案:

问题:如何安全地写入文件?

我应该使用的策略是创建一个临时文件,然后在写入完成后复制该文件。我的问题是:

1)这是 Android 上最安全的方法吗? (例如,您可以在android sd卡上复制文件吗?如果可以的话,操作是原子的吗?)

2)在资源(磁盘空间)有限的嵌入式系统中,有人知道安全写入磁盘的另一种策略吗? (而不是创建两个大文件)

谢谢

I have written an android app that saves (potentially) large files to the SD Card. 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:

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? (e.g. Can you copy files on the android sd card and if so is the operation atomic?)

2) In an embedded system with limited resources (disk space) does anyone know of another strategy for safely writing to a disk? (instead of creating two large files)

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

闻呓 2024-12-19 07:38:35

我知道现在回答已经太晚了,但会帮助遇到此问题的任何人,并会发现它很有帮助。

您需要使用 AtomicFile

以下代码片段可能会有所帮助。

public void writeFile(File file, byte[] data) {
    AtomicFile aFile = new AtomicFile(file);
    FileOutputStream fos = null;

    try {
        fos = aFile.startWrite();
        fos.write(data);
        // Close stream and commit new data.
        aFile.finishWrite(fos);
        fos = null;
    } catch (IOException e) {
        Log.e(TAG, "Cannot write file " + file.getPath(), e);
    } finally {
        if (fos != null) {
            // Something went wrong.
            // Close stream and roll back data.
            aFile.failWrite(fos);
        }
    }
}

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.

Following code snippet may help.

public void writeFile(File file, byte[] data) {
    AtomicFile aFile = new AtomicFile(file);
    FileOutputStream fos = null;

    try {
        fos = aFile.startWrite();
        fos.write(data);
        // Close stream and commit new data.
        aFile.finishWrite(fos);
        fos = null;
    } catch (IOException e) {
        Log.e(TAG, "Cannot write file " + file.getPath(), e);
    } finally {
        if (fos != null) {
            // Something went wrong.
            // Close stream and roll back data.
            aFile.failWrite(fos);
        }
    }
}
念三年u 2024-12-19 07:38:35

在最合理的平台(Linux/Android 是其中之一)上安全创建文件的典型方法是创建一个临时文件,然后重命名该文件,如问题&您链接到的答案。请注意重命名的重点;重命名文件通常是同一文件系统内的原子操作,而复制文件则不是。此外,此方法仅需要足够的空间来容纳单个数据副本。

因此,您使用临时名称在目标目录中创建一个文件,然后使用 File.renameTo() 为其指定适当的名称。通过对临时文件使用标准命名约定,您始终可以找到并删除它们,即使您的应用程序由于设备关闭等原因意外终止。

如果您真的很偏执,您可能需要插入一些对 FileDescriptor.sync() 或等效...

编辑:

顺便说一句,您没有提及您收到的 IOException 类型以及您是否已跟踪下降其原因。如果是由于空间不足,没问题,但如果我们谈论的是有故障的 SD 卡,那么在这种情况下就没有“安全”之说。

编辑2:

为了检查可用空间,您可以创建一个 File 目标目录(即文件最终所在的目录)对象并调用 File.getFreeSpace()。请记住,此检查不提供任何保证 - 如果例如另一个进程将数据写入介质,您可能仍然会没有足够的空间。

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. 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. 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 e.g. a device power-off.

If you are really paranoid, you may want to insert a few calls to FileDescriptor.sync() or equivalent...

EDIT:

BTW, you do not mention what kind of IOException your are getting and whether you have tracked down its cause. 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.

EDIT 2:

In order to check the available free space, you can create a File object for the destination directory (i.e. the directory where your file will end up) and call File.getFreeSpace(). Keep in mind that this check does not provide any guarantees - you may still end up without enough space if e.g. another process writes data to the medium.

如果没有你 2024-12-19 07:38:35

正如 Pawan 的回答中提到的,建议使用 AtomicFile 来安全地写入文件。

但使用时仍然需要小心,有时它并不是那么“原子”。

您可以在 android.utilandroid.support.v4.utilandroidx.core.util 中找到 AtomicFile 。但它们的实现在不同版本的API级别/support库/androidx库中是不同的,所以请注意在使用之前更改为您的依赖版本。

一般来说,您应该使用最新版本的 androidx 的 AtomicFile。它位于androidx.core:core中,并被androidx.appcompat:appcompat所依赖。


让我们看看当我们尝试在较低版本中使用 AtomicFile 写入文件时实际会发生什么。

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;
}

正如你所看到的,AtomicFile 更喜欢返回在写入之前先创建基本文件。
这确实可以在写入基础文件失败时提供有效的恢复手段。但你只能说写操作是安全的,而不是原子的。

想象一下,如果系统在步骤 1 和 2 之间断电,会发生什么情况,基本文件将丢失,仅保留备份文件。

此外,由于 FileOutputStream 指向基本文件,因此写入也不是原子的。


但google在高版本中改进了AtomicFile的实现。

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) {
        // ...
    }
}

改进后的 AtomicFile 采用了另一种方法:当您将内容写入 startWrite() 返回的输出流时,您实际上写入了一个新文件(a后缀为.new的临时文件),调用finishWrite()后新文件将重命名为基础文件。

现在,基础文件的内容只有两种情况:

  1. 写入失败,基础文件的内容保持不变。
  2. 写作成功。

现在它是真正的原子了。

As mentioned in Pawan's answer, AtomicFile is recommended for writing file safely.

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. 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.

In general, you should use the latest version of androidx's AtomicFile. It's in androidx.core:core, and dependenced by androidx.appcompat:appcompat.


Let's see what actually happens when we try to write a file with AtomicFile in lower versions.

API level: [17, 29] / support library: [22.1.0, 28.0.0] / androidx core: [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.
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.

Also, because the FileOutputStream points to the base file, writing is not atomically either.


But google has improved the implementation of AtomicFile in high versions.

API level: 30+ / androidx core: 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.

Now, the content of the base file will only have two cases:

  1. The writing fails, and the content of base file remains unchanged.
  2. The writing is successful.

It's real atomic now.

如日中天 2024-12-19 07:38:35

我不知道是否要复制它,但重命名临时文件是典型的做法。如果写入过程中出现异常,删除临时文件即可。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文