gpt4 book ai didi

android - 如何使用android存储访问框架正确覆盖文件内容

转载 作者:行者123 更新时间:2023-11-29 23:01:39 32 4
gpt4 key购买 nike

>>背景

我想使用 SAF(存储访问框架)将我的应用程序的数据文件保存到存储介质上用户所需的位置。我首先在应用程序专用文件夹中创建文件,然后将其复制到用户从文件选择器对话框中选择的文件(代码稍后提供)。

此过程非常适合新文件,但对于现有文件,尽管文件选择器警告覆盖文件,但最终文件在写入前不会被删除。

通过计算写入的字节数并使用十六进制编辑器调查文件,代码将正确的字节写入输出流但是:如果现有文件的字节数多于要写入的字节数,则最终覆盖的文件已损坏(实际上并没有损坏,请参阅下一节进行说明)并且如果现有的字节数少于要写入的字节数,则最终覆盖的文件是正确的。

>> 更多细节和代码

我使用下面的代码来显示问题(jpg 是示例):我将尝试使用两个文件:

file1.jpg 166,907 bytes
file2.jpg 1,323,647 bytes
file3.jpg The final file with variable size

首先,我会将文件 1 复制到用户选择的名为文件 3 的文件夹(目标文件),然后用文件 2 覆盖它,最后我将用文件 1 再次覆盖它。查看代码是什么以及会发生什么:

调用文件选择器的代码:

val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/jpeg"
}
startActivityForResult(intent, request)

现在在 onActivityResult() 中,我按如下方式处理数据:

contentResolver.openOutputStream(fileUri)?.use {output->
val input = FileInputStream(File(getExternalFilesDir(null),"Pictures/file1.jpg"))
// file1.jpg for first run, file2.jpg for 2nd run and file1.jpg again for 3rd run
copyStream(input, output)

}

以及复制流的代码:

@Throws(IOException::class)
fun copyStream(input: InputStream, output: OutputStream) {
val buffer = ByteArray(1024)
var bytesRead = input.read(buffer)
while (bytesRead > 0) {
output.write(buffer, 0, bytesRead)
bytesRead = input.read(buffer)
}
input.close()
//The output will be closes by kotlin standard function "use" at previous code
}

现在首先运行 file3.jpg 与 file1.jpg 完全一样。 file3.jpg 在第二次运行时也与 file2.jpg 相同。但是在用 file1.jpg 的内容覆盖 file3.jpg 的第三次运行(比 file3.jpg 有 kess 字节)file3.jpg 的大小仍然是 1,323,647 字节,前 166,907 个字节与 file1.jpg 相同,其余字节直到1,323,647 与第二次运行时写入的 file2.jpg 相同。

这是十六进制文件的内容:

file1.jpg

0000:0000 | FF D8 FF E1 09 49 45 78 69 66 00 00 49 49 2A 00 | ÿØÿá.IExif..II*.
0000:0010 | 08 00 00 00 09 00 0F 01 02 00 06 00 00 00 7A 00 | ..............z.
...
0002:8BE0 | 56 5E 2A EC C7 36 6D B1 57 1C D5 CD 95 8A BB 2F | V^*ìÇ6m±W.ÕÍ..»/
0002:8BF0*| 36 6C 55 AD F2 F3 65 60 43 FF D9* | 6lU.òóe`CÿÙ

file2.jpg

0000:0000 | FF D8 FF E0 00 10 4A 46 49 46 00 01 01 00 00 01 | ÿØÿà..JFIF......
0000:0010 | 00 01 00 00 FF E1 01 48 45 78 69 66 00 00 49 49 | ....ÿá.HExif..II
...
0002:8BC0 | F2 07 23 D4 57 CA 7E 13 FD A9 23 B5 86 2D 3E 4D | ò.#ÔWÊ~.ý©#µ.->M
0002:8BD0 | 66 7B 58 D1 42 A3 4D 6A 57 80 38 C9 CF EB 5E 93 | f{XÑB£MjW.8ÉÏë^.
0002:8BE0 | E1 3F DA 36 CA EA 10 2E 7C 49 0B C4 E3 21 F6 8C | á?Ú6Êê..|I.Äã!ö.
0002:8BF0*| 9F D6 BB 63 8B A3 86 D5 34 B5 D9*E8 D2 E9 D7 AE | .Ö»c.£.Õ4µÙèÒé×®
0002:8C00 | B7 34 9F B5 85 18 C6 B5 DF 2E FA 6B AD B6 5D BC | ·4.µ..Ƶß.úk.¶]¼
0002:8C10 | F7 3D 6E F3 C3 50 6B 56 32 D9 CC 14 AB AE 30 C3 | ÷=nóÃPkV2ÙÌ.«®0Ã
...
0014:3260 | E8 8B 0A CE 4E 47 AD 4A 92 B2 E4 E6 8B 3B 7F 34 | è..ÎNG.J.²äæ.;.4
0014:3270 | 1C 55 D8 6C 14 83 BA 88 AB 98 46 4D 33 FF D9 | .UØl..º.«.FM3ÿÙ

file3.jpg (After the 3rd run)

0000:0000 | FF D8 FF E1 09 49 45 78 69 66 00 00 49 49 2A 00 | ÿØÿá.IExif..II*.
0000:0010 | 08 00 00 00 09 00 0F 01 02 00 06 00 00 00 7A 00 | ..............z.
...
0002:8BD0 | D9 B1 43 BA E6 39 B7 CD 8A B5 97 9B 36 29 76 5E | Ù±Cºæ9·Í.µ..6)v^
0002:8BE0 | 56 5E 2A EC C7 36 6D B1 57 1C D5 CD 95 8A BB 2F | V^*ìÇ6m±W.ÕÍ..»/
//content of file1 continues with content of file2 (Next line)
0002:8BF0*| 36 6C 55 AD F2 F3 65 60 43 FF D9*E8 D2 E9 D7 AE | 6lU.òóe`CÿÙèÒé×®
0002:8C00 | B7 34 9F B5 85 18 C6 B5 DF 2E FA 6B AD B6 5D BC | ·4.µ..Ƶß.úk.¶]¼
0002:8C10 | F7 3D 6E F3 C3 50 6B 56 32 D9 CC 14 AB AE 30 C3 | ÷=nóÃPkV2ÙÌ.«®0Ã
0002:8C20 | 8C F3 83 5E 55 3D 86 A1 F0 EB C5 72 E9 C6 62 E2 | .ó.^U=.¡ðëÅréÆbâ
...
0014:3260 | E8 8B 0A CE 4E 47 AD 4A 92 B2 E4 E6 8B 3B 7F 34 | è..ÎNG.J.²äæ.;.4
0014:3270 | 1C 55 D8 6C 14 83 BA 88 AB 98 46 4D 33 FF D9 | .UØl..º.«.FM3ÿÙ

如您所见,file3 以 file1 的内容开始,在第 0002:8BF0 行的第三组 file1 (FF D9) 的最后字节之后,它继续 file2 (E8 D2) 的内容(星点)

我测试了直接在应用程序的专用文件夹中复制相同文件的过程,但结果是正确的,所有三个运行的 file3 都是正确的。问题只是针对 SAF。

最佳答案

经过三天的搜索和在这里提问一天后,我找到了答案。我没有删除这个问题,因为其他人可能会遇到同样的问题。问题的本质是让我走错了路。它不仅出现在复制流中,而且出现在编写例如4 字节 (bbbb) 用 8 字节 (aaaaaaaa) 覆盖文件。它创建一个包含前 4 个新字节和 4 个旧字节的文件! (bbbbaaaa)。

所以答案在 FileOutputStream() 中。具有写入文件的字节大小 (input.channel.size()) 或 (output.cannel.position()) 并截断剩余字节 (output.channel.truncate(size))。

作为有问题的代码,我改为:

contentResolver.openOutputStream(fileUri)?.use {output->
output as FileOutputStream
FileInputStream(File(getExternalFilesDir(null),"Pictures/file1.jpg")).use{input->
copyStream(input, output)
// this new line removes bytes beyond the input file size
output.channel.truncate(input.channel.size())
// or
// output.channel.truncate(output.channel.position())
}
}

2019/09/15 更新

感谢来自 @mjanssen 的第一条评论您也可以通过在复制文件 copyStream(input, output) 之前放置 output.channel.truncate(0) 来获得相同的结果:

contentResolver.openOutputStream(fileUri)?.use {
output-> output as FileOutputStream
FileInputStream(File(getExternalFilesDir(null),"Pictures/file1.jpg")).use{input->
output.channel.truncate(0)
copyStream(input, output)
}
}

就是这样

关于android - 如何使用android存储访问框架正确覆盖文件内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56902845/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com