Android - 获取具有写访问权限的DocumentFile,以访问SD卡上任何文件路径(已获得SD卡权限)

8

我在我的应用程序中使用以下意图获取SD卡写入权限。 如果用户从系统文件浏览器中选择SD卡文件夹,则我可以访问SD卡写入。

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);

之后,我可以使用DocumentFile类修改sd卡中的文件。但是我在获取随机文件路径的DocumentFile时遇到了问题。

Document.fromFile(new File(path));
Document.fromSingleUri(Uri.fromFile(new File(path)));

两者都返回一个DocumentFile对象,并在.canWrite()上返回false。尽管我已经拥有了SD卡权限。

所以我写了在我的问题末尾发布的方法,以获得一个DocumentFile,在.canWrite()上返回true。但这很慢……而且感觉非常不对!一定有更好的方法来做到这一点。我还编写了一个返回与之相同字符串的方法

String docFileUriString = docFile.getUri().toString(); 

对于任何文件,其中docFile是通过下面方法返回的DocumentFile。但是:
DocumentFile.fromTreeUri(Uri.parse(docFileUriString ));

返回一个指向sd卡根目录而不是DocumentFile路径的DocumentFile,这很奇怪。有人能提出更优雅的解决方案吗?

public static DocumentFile getDocumentFileIfAllowedToWrite(File file, Context con){ 

    List<UriPermission> permissionUris = con.getContentResolver().getPersistedUriPermissions();

    for(UriPermission permissionUri:permissionUris){

        Uri treeUri = permissionUri.getUri();
        DocumentFile rootDocFile = DocumentFile.fromTreeUri(con, treeUri);
        String rootDocFilePath = FileUtil.getFullPathFromTreeUri(treeUri, con);

        if(file.getAbsolutePath().startsWith(rootDocFilePath)){

            ArrayList<String> pathInRootDocParts = new ArrayList<String>();
            while(!rootDocFilePath.equals(file.getAbsolutePath())){
                pathInRootDocParts.add(file.getName());
                file = file.getParentFile();
            } 

            DocumentFile docFile = null;  

            if(pathInRootDocParts.size()==0){ 
                docFile = DocumentFile.fromTreeUri(con, rootDocFile.getUri()); 
            }
            else{
                for(int i=pathInRootDocParts.size()-1;i>=0;i--){
                    if(docFile==null){docFile = rootDocFile.findFile(pathInRootDocParts.get(i));}
                    else{docFile = docFile.findFile(pathInRootDocParts.get(i)); }  
                }
            }
            if(docFile!=null && docFile.canWrite()){ 
                return docFile; 
            }else{
                return null;
            }

        }
    }
    return null; 
}

2
你有找到解决方案吗?我发现存储访问框架非常难用。 - user1159819
1
不,我没有这样做...而且使用DocumentFile类确实降低了性能。我改用DocumentsContract类来执行我想要的操作。我同意SAF确实很糟糕... - Anonymous
1
你有使用DocumentsContract解决方案的示例吗? - ToYonos
1
这个SAF api可能是Android上实现和文档化最糟糕的api。我需要从DocumentFile或Uri获取正确的scheme的File,而不是使用content://com.android.externalstorage.documents/tree/primary:,应该用file:///storage/emulated/0或storage/emulated/0,但我找不到方法。我的情况是:用户通过SAF UI选择路径。使用该Uri保存图像到DocumentFile中并获取文件以将EXIF数据写入该图像,但我无法获取指向该文件的正确文件,因为现有uri使用了content://。 - Thracian
这段代码运行良好,我在我的应用程序“新播放列表管理器”中使用它。为了确定源文件是否存储在内部存储器上,我只需检查sourcefile.toString().contains(Environment.getExternalStorageDirectory()。如果是,则直接使用file.delete()删除。如果在SD卡上,则使用get valid documentfile例程并应用documentfile.delete()。谢谢! - Theo
@Theo 你是如何从普通文件中获取DocumentFile对象的? - AndiM
2个回答

2

Document.fromFile(File) 在最近的设备上似乎无法正确使用此方法。

以下是我收集到的问题答案链接:https://dev59.com/Gl8d5IYBdhLWcg3wgya4#26765884

关键步骤如下:

  1. 获取对SD卡的写入访问权限,并使其在手机重新启动时持久化

    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {

    ...

    getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

    }

  2. 之后,您可以访问文件和文件夹,无论它们嵌套多深,但您必须注意复杂的URL。

如果您像我一样是Linux人,并认为一切都是文件且简单易懂。这次不是这样。 例如SD卡上的文件:/storage/13E5-2604/testDir/test.txt 其中13E5-2604是您的SD卡根目录,将是:

content://com.android.externalstorage.documents/tree/13E5-2604%3A/document/13E5-2604%3A%2FtestDir%2Ftest.txt

在这个新的现实中,他们为什么要把它做得如此复杂——这是另一个问题...
我不知道是否有API方法可以透明且轻松地将普通的Linux路径转换为这个新的现实路径。但在你完成它之后(%3A代表:,%2F代表/,请参见https://www.w3schools.com/tags/ref_urlencode.ASP),你可以轻松地创建DocumentFile的实例:
DocumentFile txtFile = DocumentFile.fromSingleUri(context,
Uri.parse("content://com.android.externalstorage.documents/tree/13E5-2604%3A/document/13E5-2604%3A%2FtestDir%2Ftest.txt"));

并将内容写入文件。


1
感谢您的指导,让我找对了方向。 我已经能够使用更简单的URL使其工作: DocumentFile.fromTreeUri(getContext(), Uri.parse("content://com.android.externalstorage.documents/tree/7A71-1EFC%3Atest%2FT2")); - jkb016

1
为了回应@AndiMar(3月21日)的问题,我只需检查它是否存在于内部存储中,如果存在就不必担心。
       try {
        if (sourcefile.toString().contains(Environment.getExternalStorageDirectory().toString())) {
            if (sourcefile.exists()) {
                sourcefile.delete();
            }
        } else {
            FileUtilities fio = new FileUtilities();
            DocumentFile filetodelete = fio.getDocumentFileIfAllowedToWrite(sourcefile, context);
            if (filetodelete.exists()) {
                filetodelete.delete();
            }
        }

    } catch (Exception e) {
        e.printStackTrace();

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接