Удаление файлов с SD карты Android

В моем приложении есть функция удаления файлов (может быть любой файл на устройстве). Я регистрирую свои попытки удаления файлов, а затем выделю проблемы, с которыми столкнулся. Устройство, на котором я тестировал, работало под управлением Android 10 (API 29), но мне нужно удалить файлы SD-карты из API 21 и выше.

Подход 1. Удаление с помощью ContentResolver

Этот подход отлично работает с памятью телефона и памятью SD-карты, за исключением того, что я не могу удалить файлы из папки «Загрузки» SD-карты, в то время как то же самое работает с папкой «Загрузки» памяти телефона.

applicationContext.contentResolver.delete(file.uri, null, null)

Я провел некоторое исследование и наткнулся на сообщение SO, где рекомендуемый подход к удалению файлов на SD-карте — через DocumentsProvider или с помощью DocumentFile.

Подход 2: удаление с помощью DocumentFile

Я последовал этому ответу и прошел uri дерева документов, получил файл и вызвал delete(). К моему замешательству, delete() возвращает true, но не удаляет файл. Этот подход даже не удалял файлы из других папок на SD-карте, в то время как подход 1 мог это сделать.

Разрешение, которое у меня есть для дерева документов SD-карты, было принято следующим образом.

fun openSafTreePicker(){
    val intent = Intent(ACTION_OPEN_DOCUMENT_TREE).apply {
        flags = FLAG_GRANT_PERSISTABLE_URI_PERMISSION 
                    or FLAG_GRANT_READ_URI_PERMISSION 
                    or FLAG_GRANT_WRITE_URI_PERMISSION
    }
    startActivityForResult(intent, requestCode)
}

// On activity result taking the persistable permission
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    val uri = data.data!!
    val flags = (data.flags) and (FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)        
    applicationContext.contentResolver.takePersistableUriPermission(uri, flags)
    AppPrefs.sdCardUri = uri
    Log.d(TAG, AppPrefs.sdCardUri)
    // AppPrefs.sdCardUri = content://com.android.externalstorage.documents/tree/EAB3-F2BF%3A
    // Delete the selected files
    val selection = SelectionManager.getEntireSelection()
    selection.forEach { 
        when {
          it.isSdFile -> deleteSdCardFile(it.path)
          it.isPhoneFile -> applicationContext.contentResolver.delete(it.uri, null, null) 
        }
    }
}

Я попытался удалить файл, используя статические методы, доступные в DocumentsContract. Мне не удалось удалить файлы с SD-карты, а deleteDocument() вернул true.

 private fun deleteSdCardFile(filePath: String) {
     val segments = filePath.split("/")
      // Skip first 3 segments /storage/ABC-1234/0/ corresponding to storage volume and user.
     val docPath = StringBuilder()
     for (index in 3 until segments.size) {
         if(index < segments.size-1) docPath.append(segments[index]).append("/")
         else docPath.append(segments[index])
     }
     val treeUri = Uri.parse(AppPrefs.sdCardUri)
     val docUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, docPath.toString())
     DocumentsContract.deleteDocument(applicationContext.contentResolver, docUri)
 }

Вопросы:

  1. Как правильно удалить файлы с SD-карты? MediaStore работает нормально, за исключением папки SD-карты Downloads.
  2. В чем загадка файлов, которые не удаляются из папки «Загрузки» SD-карты.
  3. Почему delete() и deleteDocument() из DocumentFile и DocumentsContract возвращают true, даже если они не удалили файл?

person Siddharth Kamaria    schedule 09.01.2021    source источник
comment
flags = FLAG_GRANT_PERSISTABLE_URI_PERMISSION or FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION Удалите эти флаги. Они бесполезны. ВЫ ничего не можете предоставить. Вы должны быть рады, что вам предоставлен доступ. Вы можете проверить эти флаги в onActivityResult, чтобы узнать, разрешено ли вам чтение или запись.   -  person blackapps    schedule 09.01.2021
comment
val treeUri = Uri.parse(AppPrefs.sdCardUri) Пожалуйста, покажите более полный код. Для начала мы хотим точно увидеть значение sdCardUri. Адаптируйте свой пост, пожалуйста.   -  person blackapps    schedule 09.01.2021
comment
Чтобы вас успокоить: удалить файлы со съемной micro sd карты с помощью SAF вполне возможно.   -  person blackapps    schedule 09.01.2021
comment
// docPath is the relative file... Пожалуйста, опубликуйте полный код.   -  person blackapps    schedule 09.01.2021
comment
Я предлагаю позволить пользователю выбрать папку, в которой находится файл. Таким образом, код в onActivityResult для удаления файла будет максимально коротким. Пожалуйста, опубликуйте этот код.   -  person blackapps    schedule 09.01.2021
comment
@blackapps Я добавил больше кода. Я ищу решение, с помощью которого пользователь один раз предоставляет разрешение на каталог, а затем я могу удалить файлы из этого дерева каталогов, не запрашивая разрешения снова.   -  person Siddharth Kamaria    schedule 09.01.2021
comment
Я не вижу код, который я просил в моем последнем комментарии. Дальше вполне возможно то, о чем вы спрашиваете в своем последнем комментарии.   -  person blackapps    schedule 09.01.2021
comment
Таким образом, код в onActivityResult для удаления файла будет максимально коротким. Пожалуйста, опубликуйте этот код. -- Извините, я не понял, какой код вы ищете? Я могу опубликовать это здесь, как только узнаю, что вы ищете.   -  person Siddharth Kamaria    schedule 09.01.2021
comment
@blackapps Afaik, вы хотели увидеть код, в котором логика удаления SAF вызывается из onActivityResult. Я добавил эту часть. Это в основном цикл for, перебирающий все выбранные файлы один за другим и пытающийся удалить его.   -  person Siddharth Kamaria    schedule 09.01.2021
comment
Я понятия не имею о вашем менеджере по подбору. Пожалуйста, сделайте, как я просил. Будь проще. Нет циклов тоже. Вам нужно только одно имя файла для файла, который вы хотите удалить в выбранном каталоге. Не вызывайте также другие функции в onActivityResult.   -  person blackapps    schedule 09.01.2021
comment
@blackapps С чем я борюсь, так это с тем, что 1.) У меня есть постоянное разрешение на каталог, поэтому onActivityResult будет вызываться только один раз. 2.) У меня есть RecyclerView, где я показываю все файлы/носители, откуда пользователь может удалить. Просить пользователя выбрать этот конкретный файл в SAF, скажем, 10-15 файлов каждый раз, когда он хочет удалить 10-15 файлов, просто слишком много. Кроме того, выбор тех же файлов, которые они выбрали в приложении, снова в SAF - это не то, что мне нужно.   -  person Siddharth Kamaria    schedule 09.01.2021
comment
Это все не имеет значения. Я могу помочь вам удалить файл. Поэтому просто для решения вашей проблемы вы опубликуете код, который я просил. Как только вы сможете удалить один файл, вы можете попытаться реализовать его в своем приложении или чем-то еще. Возьмите помощь, которую вы можете получить!   -  person blackapps    schedule 09.01.2021
comment
@blackapps Звучит круто, покажи мне, как удалить один файл, и тогда я смогу взять его оттуда. На данный момент вы можете с уверенностью предположить, что в onActivityResult нет цикла for, и я получаю treeUri, так как я использую ACTION_OPEN_DOCUMENT_TREE. Дайте мне знать, что нужно сделать оттуда.   -  person Siddharth Kamaria    schedule 09.01.2021
comment
Извините .. но вы должны опубликовать новый результат onActivityResult, где мы видим, что вы пытаетесь удалить файл в выбранной папке. Скажите, какую папку пользователь выбирает для начала. Создайте переменную DocumentFile для выбранной папки.   -  person blackapps    schedule 09.01.2021
comment
@blackapps Я только что заставил его работать через DocumentFile, теперь я понимаю, что вы пытаетесь сказать. Я начал с DocumentFile.fromTreeUri(), а затем перешел к нужному файлу, рекурсивно используя document.findFile(). Благодарю вас!   -  person Siddharth Kamaria    schedule 09.01.2021
comment
Да так и надо делать. DocumentFile общеизвестно медленный. Так что, если скорость имеет значение, попробуйте DocumentsContract.   -  person blackapps    schedule 09.01.2021
comment
У меня вопрос: как получить uri поставщика документов для использования с DocumentsContract.deleteDocument(). У меня есть URI дерева и относительный путь к файлу в этом дереве.   -  person Siddharth Kamaria    schedule 09.01.2021
comment
На данный момент я не могу вам помочь, так как у меня нет кода под рукой, пока я лежу под пальмой ;-)   -  person blackapps    schedule 09.01.2021
comment
Конечно, всякий раз, когда вы получаете какой-либо пример, пожалуйста, опубликуйте его здесь. Я приму это как ответ. Я не нашел ни одного примера для DocumentsContract, а тот, что в официальных документах, представляет собой пример из одной строки, и я почти уверен, что использую его неправильно в приведенном выше коде.   -  person Siddharth Kamaria    schedule 09.01.2021


Ответы (1)


Вы не можете удалить какие-либо файлы с SD-карты, если вы не прочитали и не записали URI в это хранилище. Предположим, что пользователь предоставил URI (read), тогда вы можете удалить все файлы на SD-карте рекурсивно:

// AAAA-BBBB is the SD card's ID
val files = DocumentFileCompat.fromSimplePath(context, storageId = "AAAA-BBBB", basePath = "Music")
// the path will be /storage/AAAA-BBBB/Music
val success = files?.deleteRecursively(context)

DocumentFileCompat можно скачать здесь.

person Anggrayudi H    schedule 10.06.2021