Android - получите DocumentFile с доступом на запись для любого пути к файлу на SD-карте (уже получив разрешение SD-карты)

В моем приложении я получаю разрешение на запись SD-карты, используя следующее намерение. Если пользователь выбирает папку SD-карты из системного файлового проводника, тогда у меня есть доступ на запись SD-карты.

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

После этого я могу изменять файлы на SD-карте с помощью класса DocumentFile. Но у меня проблема с получением DocumentFile для случайного пути к файлу.

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

оба возвращают объект DocumentFile, который возвращает false в .canWrite (). Хотя у меня уже есть разрешение на SD-карту.

Поэтому я написал метод, опубликованный в конце моего вопроса, чтобы получить DocumentFile, который возвращает true в .canWrite (). Но это медленно ... И тоже очень неправильно! Должен быть способ сделать это лучше. Я также написал метод, который возвращает ту же строку, что и

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

для любого файла, где docFile - это DocumentFile, возвращаемый приведенным ниже методом. Но

DocumentFile.fromTreeUri(Uri.parse(docFileUriString ));

возвращает DocumentFile, который указывает на корень SD-карты вместо пути 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; 
}

person Anonymous    schedule 27.03.2016    source источник
comment
Вы когда-нибудь находили решение? Я считаю, что фреймворк доступа к хранилищу довольно сложен в использовании   -  person user1159819    schedule 13.04.2016
comment
Нет, не делал ... А использование класса DocumentFile сильно замедляло работу. Вместо этого я использовал класс DocumentsContract для выполнения нужных мне операций. Я согласен, что SAF довольно плохо ...   -  person Anonymous    schedule 13.04.2016
comment
Любой пример с вашим решением DocumentsContract?   -  person ToYonos    schedule 14.09.2016
comment
Этот API-интерфейс SAF, вероятно, является худшим из реализованных и задокументированных API-интерфейсов на Android. Мне нужно получить файл из DocumentFile или Uri с правильной схемой, а не с content: //com.android.externalstorage.documents/tree/primary:, тот, у которого есть file: /// storage / emulated / 0 или storage / emulated / 0 но я не мог найти способ. Мой сценарий: пользователь выбирает путь через пользовательский интерфейс SAF. Сохраняет изображение с DocumentFile по пути, который я использую этот Uri и получаю File для записи данных EXIF ​​в изображение, но я не могу получить файл, который правильно указывает на этот файл с существующим URI с content: //   -  person Thracian    schedule 27.09.2017
comment
Этот код работает хорошо, и я использую его в своем приложении New Playlist Manager. Чтобы определить, находится ли источник во внутренней памяти, я просто проверяю sourcefile.toString (). Contains (Environment.getExternalStorageDirectory (). Если это так, то просто file.delete (). Если на SD-карте, я использую процедуру, получаю действительный файл документа и примените documentfile.delete (). Спасибо !!!   -  person Theo    schedule 21.10.2017
comment
@Theo Как вам удается получить объект DocumentFile из обычного файла?   -  person AndiM    schedule 21.03.2018


Ответы (2)


чтобы ответить на @AndiMar (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();

    }
person Theo    schedule 25.03.2018

Document.fromFile(File) не работает должным образом для этой цели на последних устройствах

Вот ссылка на ответ. Я получил больше всего информации о проблеме https://stackoverflow.com/a/26765884/971355 < / а>

Ключевые шаги:

  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 is: и %2F is / См. 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"));

и напишите ему.

person ka3ak    schedule 25.01.2021