>>背景
我想使用SAF (存储访问框架)将我的应用程序的数据文件保存到用户想要的存储媒体位置。我首先在app专用文件夹中创建文件,然后将其复制到用户从文件选择器对话框中选择的文件中(代码稍后开始)。
此过程对于新文件非常有效,但是对于现有文件,尽管文件选择器警告要覆盖该文件,但在写入之前,最终文件将不会被擦除。
通过计算写入的字节并使用十六进制编辑器调查文件,代码会将正确的字节写入输出流,但是:如果现有文件的字节数超过要写入的字节数,则最终覆盖文件已损坏(未实际损坏,请参阅下一节以进行澄清),如果现有文件的字节少于要写入的字节,则最终覆盖的文件是正确的。
>>详细信息和代码
我使用下面的代码来显示问题( jpg是示例):我将尝试处理两个文件:
file1.jpg 166,907 bytes
file2.jpg 1,323,647 bytes
file3.jpg The final file with variable size首先,我将把file1 it复制到名为file3 (目标文件)的用户选择的文件夹中,然后用file2覆盖它,最后我将再次用file1覆盖它。看看这些代码是什么以及发生了什么:
调用文件选择器的代码:
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 (它有kess字节而不是file3.jpg),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的内容开始,在第三组的file1 (FF D9)的最后字节之后,在第0002:8BF0行中,它继续使用file2 (E8 D2) (Star points)的内容。
我测试了直接在应用程序专用文件夹中复制相同文件的过程,但是对于所有三次运行,结果都是正确的file3。问题只是为了苏丹武装部队。
发布于 2019-07-06 10:34:50
经过三天的搜索和一天的询问,我找到了答案。我没有删除这个问题,因为其他人可能会遇到同样的问题。问题的本质是把我引向了错误的方向。它不仅产生于复制流,而且还出现在写入(例如,4个字节(bbbb) )时,用8个字节(aaaaaaaa)覆盖文件。它创建一个文件,前4个新字节,然后4个旧字节!(bbbbaaaa)
答案是FileOutputStream()。具有写入文件的字节大小(input.channel.size()或(output.cannel.position(),并截断剩余字节(output.channel.truncate(大小))。
作为所讨论的代码,我更改为:
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)
}
},就这样,
发布于 2020-10-26 01:57:25
在花了几天的时间试图理解为什么我在文件结尾时会收到损坏的数据时,我很感激地找到了这篇文章。我的测试结果和OP一样。当写入比现有文件内容更短的新内容时,剩余数据将留在文件的末尾,从而导致“损坏”文件。我的错误假设是,向文件写入将完全删除/覆盖现有内容。
使用Android开发人员在“编辑A文档”(https://developer.android.com/training/data-storage/shared/documents-files#edit)上的示例突出了问题,因为使用他们的代码示例只覆盖第一条记录。例如,一个100行文本文件(例如),如果使用它们的代码示例,则仍然是一个100行文本文件,只有第一个记录被更改。如果您只想修改特定的记录“就地”,但如果您想要完全替换文件内容,则非常有用!
下面是一个修改过的java版本的示例代码,它确保输出在被覆盖之前先“清空”(使用上面OP描述的方法)。
private void overwriteDocument(Uri uri) {
try {
ParcelFileDescriptor pfd = getActivity().getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
// Use this code to ensure that the file is 'emptied'
FileChannel fChan=fileOutputStream.getChannel();
fChan.truncate(0);
fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() +
"\n").getBytes());
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}发布于 2022-03-29 17:42:07
实现所需结果的最简单方法是以截断模式打开文件。
contentResolver.openOutputStream(uri, "wt")?.use { outputStream ->
// Write to outputStream
}相关文件:
https://stackoverflow.com/questions/56902845
复制相似问题