如何控制Storage Access Framework(SAF)复制文件时的重命名规则
如何控制Storage Access Framework(SAF)复制文件时的重命名规则
我完全懂你这个烦恼——SAF默认的重命名逻辑真的有点反人类,好好的文件非要把序号加到扩展名后面,比如把aHoot.db3变成aHoot.db3(1),而不是我们想要的aHoot(1).db3。之前用传统文件API的时候你已经把文件名拆分的逻辑写得很完美了,现在换成SAF的Uri模式,就得换个思路来复刻这个规则,核心是不要依赖SAF的自动重命名,我们自己来控制文件名的生成和文件的创建。
问题根源
SAF的openOutputStream如果写入到已存在的Uri,或者通过ACTION_CREATE_DOCUMENT让用户选择已存在的文件名时,会自动在文件名末尾追加序号,不管扩展名的位置。所以要规避这个问题,我们得主动在目标目录下生成符合规则的唯一文件名,再通过SAF创建对应文件,最后写入内容。
解决步骤&代码实现
1. 实现唯一文件名生成逻辑
和你之前的传统文件逻辑类似,但要通过SAF的ContentResolver来查询目录下的文件,判断是否存在同名文件,生成带序号的正确文件名:
// 生成符合规则的唯一文件名 private String generateUniqueFileName(Uri directoryUri, String originalFileName) { // 拆分文件名主体和扩展名(和你之前的拆分逻辑一致) String[] nameParts = originalFileName.split("\\.(?=[^\\.]+$)"); String baseName = nameParts[0]; String extension = nameParts.length > 1 ? nameParts[1] : ""; int num = 0; String candidateFileName = originalFileName; // 循环检查目录下是否已存在该文件名,直到找到唯一的 while (isFileExistsInDirectory(directoryUri, candidateFileName)) { num++; if (extension.isEmpty()) { candidateFileName = baseName + "(" + num + ")"; } else { candidateFileName = baseName + "(" + num + ")." + extension; } } return candidateFileName; } // 辅助方法:检查指定目录下是否存在某个文件名的文件 private boolean isFileExistsInDirectory(Uri directoryUri, String fileName) { // 只查询文件名列,提升性能 String[] projection = {DocumentsContract.Document.COLUMN_DISPLAY_NAME}; String selection = DocumentsContract.Document.COLUMN_DISPLAY_NAME + " = ?"; String[] selectionArgs = {fileName}; Cursor cursor = getContentResolver().query( directoryUri, projection, selection, selectionArgs, null ); boolean fileExists = cursor != null && cursor.getCount() > 0; if (cursor != null) { cursor.close(); } return fileExists; }
2. 用生成的文件名创建SAF文档并复制内容
接下来,你需要先让用户选择目标目录(通过ACTION_OPEN_DOCUMENT_TREE),然后用上面生成的文件名在目录下创建新文档,再执行你的复制逻辑:
// 假设你已经通过SAF的ACTION_OPEN_DOCUMENT_TREE获取到了目标目录的directoryUri String targetFileName = "aHoot.db3"; // 你的原始文件名 String uniqueFileName = generateUniqueFileName(directoryUri, targetFileName); // 在目标目录下创建符合规则的新文件 Uri destinationUri = DocumentsContract.createDocument( getContentResolver(), directoryUri, "application/x-sqlite3", // 根据你的.db3文件设置正确的MIME类型 uniqueFileName ); if (destinationUri != null) { // 打开源文件的输入流(这里替换成你实际的源输入流) InputStream in = getContentResolver().openInputStream(sourceFileUri); // 调用你已有的复制方法 copyUriDatabase(in, destinationUri); }
3. 保留你现有的复制流方法
你之前写的copyUriDatabase方法可以直接用,不需要修改,它负责把输入流的内容写入到生成的正确Uri中:
public void copyUriDatabase(InputStream in, Uri destinationUri) { try { OutputStream out = null; out = this.getContentResolver().openOutputStream(destinationUri); byte[] buffer = new byte[1024]; int read; try { while ((in.read(buffer)) > 0) { if (out != null) { out.write(buffer); } } } catch (IOException e) { throw new RuntimeException(e); } if (out != null) { out.flush(); out.close(); } in.close(); } catch (IOException e) { throw new RuntimeException(e); } }
关键注意点
- MIME类型要匹配:创建文档时的MIME类型要和你的文件类型对应,比如
.db3用application/x-sqlite3,如果不确定可以用通用的application/octet-stream。 - 避免使用ACTION_CREATE_DOCUMENT:如果用这个Intent让用户输入文件名,当文件名已存在时SAF会自动加后缀,不符合我们的规则,所以优先用
ACTION_OPEN_DOCUMENT_TREE让用户选目录,我们自己控制文件名。 - 权限问题:确保你已经获取了SAF的相关权限,比如读取源文件、写入目标目录的权限。
这样一来,生成的文件名就会是aHoot(1).db3这种符合预期的格式,再也不会出现SAF那种反直觉的命名了。
备注:内容来源于stack exchange,提问作者Dana Bell
相关产品推荐
相关产品推荐

