Невозможно удалить песню с SD-карты - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь создать код, который позволит моему музыкальному приложению удалить песню из хранилища. До сих пор успешно удаляется файл, если файл находится во внутреннем (эмулируемом) хранилище (то есть не во внутреннем хранилище приложения, а во внутреннем общем хранилище телефона). Но как только песня находится на внешней SD-карте, file.delete () не удаляет файл и возвращает false.

Вот мой код:

//Remove selected tracks from the database 
activity.getContentResolver()
     .delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, selection.toString(), null);

//Delete File from storage
File file = new File(song.getFilePath);
if(!file.delete()){
   Log.e("MusicFunctions", "Failed to delete file: " + song.getFilePath());
}

Когда я выбираю песню, которая находится на SD-карте, она не удаляется, а только удаляется из базы данных; Вот вывод logcat:

E/MusicFunctions: Failed to delete file: /storage/3138-3763/Music/Test/Odesza/In Return/Always This Late.mp3

Я тоже пробовал context.deleteFile(file), но мне тоже не повезло.

Как я уже сказал, его невозможно удалить, только когда файл находится на SD-карте. Когда он сохраняется на внутреннем хранилище, он удаляет нормально. Почему его не удаляют, и как правильно удалять файлы с SD-карты на Android 5.0+?

Большое спасибо заранее.

EDIT: Я забыл упомянуть, что я уже добавил разрешения:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE"/>

и я получаю необходимое разрешение на хранение во время выполнения:

ActivityCompat.requestPermissions(thisActivity,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL);

ДРУГОЕ РЕДАКТИРОВАНИЕ: Я заметил, что приложениям файлового менеджера нужно предоставить дополнительное разрешение, выполнив шаги, как в https://metactrl.com/docs/sdcard-on-lollipop/

Как мне этого добиться?

Ответы [ 4 ]

0 голосов
/ 08 февраля 2019

Я заметил, что вопрос снова вызвал некоторый интерес. Я счастлив сказать, что действительно нашел решение этой проблемы. Я провел обширное онлайн-исследование и нашел несколько файлов с исходным кодом (хотя, извините, не могу вспомнить, где я их нашел), что решило мою проблему

Проблема с Android 4.4 и выше заключается в том, что вам нужны дополнительные привилегии через Storage Access Framework для стороннего приложения для удаления / изменения внешних файлов SD-карты.

Чтобы получить эти привилегии, вам нужно получить URI документа или один из URI его родительских файлов (каталогов). Для этого вам нужно открыть встроенный в Android файловый браузер. Лучше всего, если пользователь выбирает корневой каталог SD-карты через Обозреватель файлов, чтобы ваше приложение могло изменить / удалить любой файл на SD-карте. Для этого вы можете следовать этому коду:

private int REQUEST_CODE = 42;
private void getSDCardAccess(){
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, REQUEST_CODE);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (resultCode == RESULT_OK) {
        Uri treeUri = resultData.getData();
        DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);

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

        if(shrdPref == null){
            shrdPref = getSharedPreferences(PREF_MAIN_FILE, MODE_PRIVATE);
        }
        //Takes the access so that we can use it again after the app reopens
        shrdPref.edit().putString(KEY_SDCARDSTORAGE, treeUri.toString()).apply();
    }
}

Кроме того, вам необходим файл документа для изменения и удаления файла. Следующие фрагменты кода могут помочь вам сделать это, а также определить, доступен ли для записи файл / каталог с использованием метода файла документа ... (Я знаю, что в нем много кода, большая часть которого пришла из другого источника. Я действительно сожалею о том, что забыл, откуда я его взял, поскольку они заслуживают большого уважения). Функции, которые вы хотели бы отметить: isWritableNormalOrSAF (), deleteFile () и, возможно, copyFile (). Обратите внимание, что большинство других функций требуется для их работы)

public static boolean isWritable(@NonNull final File file) {
    boolean isExisting = file.exists();

    try {
        FileOutputStream output = new FileOutputStream(file, true);
        try {
            output.close();
        }
        catch (IOException e) {
            // do nothing.
        }
    }
    catch (FileNotFoundException e) {
        return false;
    }
    boolean result = file.canWrite();

    // Ensure that file is not created during this process.
    if (!isExisting) {
        // noinspection ResultOfMethodCallIgnored
        file.delete();
    }

    return result;
}


public static boolean isWritableNormalOrSaf(@Nullable final File folder, Context context) {
    // Verify that this is a directory.
    Log.e("StorageHelper", "start");
    if (folder == null || !folder.exists() || !folder.isDirectory()) {
        Log.e("StorageHelper", "return 1");
        return false;
    }

    // Find a non-existing file in this directory.
    int i = 0;
    File file;
    do {
        String fileName = "AugendiagnoseDummyFile" + (++i);
        file = new File(folder, fileName);
        //Log.e("StorageHelper", "file:" + fileName);
    }
    while (file.exists());

    // First check regular writability
    if (isWritable(file)) {
        //Log.e("StorageHelper", "return 2 true");
        return true;
    }

    // Next check SAF writability.
    Log.e("StorageHelper", "start 2");
    DocumentFile document;
    try {
        document = getDocumentFile(file, false, false, context);
    }
    catch (Exception e) {
        //Log.e("StorageHelper", "return 3 exception");
        return false;
    }

    if (document == null) {
        //Log.e("StorageHelper", "return 4 doc null");
        return false;
    }

    // This should have created the file - otherwise something is wrong with access URL.
    boolean result = document.canWrite() && file.exists();

    // Ensure that the dummy file is not remaining.
    document.delete();

    //Log.e("StorageHelper", "return end");
    return result;
}


public static boolean deleteFile(@NonNull final File file, Context context) {
    // First try the normal deletion.
    if (file.delete()) {
        return true;
    }

    // Try with Storage Access Framework.
        DocumentFile document = getDocumentFile(file, false, true, context);
        return document != null && document.delete();

}




private static DocumentFile getDocumentFile(@NonNull final File file, final boolean isDirectory, final boolean createDirectories, Context context) {
    SharedPreferences sharedPreferences = context.getSharedPreferences(PREF_MAIN_FILE, Context.MODE_PRIVATE);
    String uriString = sharedPreferences.getString(KEY_SDCARDSTORAGE, null);
    if(uriString == null){
        return null;
    }

    Uri treeUri = Uri.parse(uriString);

    String fullPath;
    try {
        fullPath = file.getCanonicalPath();
    }
    catch (IOException e) {
        return null;
    }

    String baseFolder = null;

    // First try to get the base folder via unofficial StorageVolume API from the URIs.
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
        StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        StorageVolume volume = storageManager.getStorageVolume(file);
        String uuid = volume.getUuid();

        String volumeId = getVolumeIdFromTreeUri(treeUri);
        if (uuid.equals(volumeId)) {
            // Use parcel to get the hidden path field from StorageVolume
            Parcel parcel = Parcel.obtain();
            volume.writeToParcel(parcel, 0);
            parcel.setDataPosition(0);
            parcel.readString();
            parcel.readInt();
            String volumeBasePath = parcel.readString();
            parcel.recycle();
            baseFolder = getFullPathFromTreeUri(treeUri, volumeBasePath);
        }

    }
    else {
        // Use Java Reflection to access hidden methods from StorageVolume
        String treeBase = getFullPathFromTreeUri(treeUri, getVolumePath(getVolumeIdFromTreeUri(treeUri), context));
        if (treeBase != null && fullPath.startsWith(treeBase)) {
            treeUri = treeUri;
            baseFolder = treeBase;
        }
    }




    if (baseFolder == null) {
        // Alternatively, take root folder from device and assume that base URI works.
        baseFolder = getExtSdCardFolder(file, context);
    }

    if (baseFolder == null) {
        return null;
    }

    String relativePath = fullPath.substring(baseFolder.length() + 1);

    // start with root of SD card and then parse through document tree.
    DocumentFile document = DocumentFile.fromTreeUri(context, treeUri);

    String[] parts = relativePath.split("\\/");
    for (int i = 0; i < parts.length; i++) {
        DocumentFile nextDocument = document.findFile(parts[i]);

        if (nextDocument == null) {
            if (i < parts.length - 1) {
                if (createDirectories) {
                    nextDocument = document.createDirectory(parts[i]);
                }
                else {
                    return null;
                }
            }
            else if (isDirectory) {
                nextDocument = document.createDirectory(parts[i]);
            }
            else {
                nextDocument = document.createFile("image", parts[i]);
            }
        }
        document = nextDocument;
    }

    return document;
}









@Nullable
private static String getFullPathFromTreeUri(@Nullable final Uri treeUri, final String volumeBasePath) {
    if (treeUri == null) {
        return null;
    }
    if (volumeBasePath == null) {
        return File.separator;
    }
    String volumePath = volumeBasePath;
    if (volumePath.endsWith(File.separator)) {
        volumePath = volumePath.substring(0, volumePath.length() - 1);
    }

    String documentPath = getDocumentPathFromTreeUri(treeUri);
    if (documentPath.endsWith(File.separator)) {
        documentPath = documentPath.substring(0, documentPath.length() - 1);
    }

    if (documentPath.length() > 0) {
        if (documentPath.startsWith(File.separator)) {
            return volumePath + documentPath;
        }
        else {
            return volumePath + File.separator + documentPath;
        }
    }
    else {
        return volumePath;
    }
}


private static String getVolumeIdFromTreeUri(final Uri treeUri) {
    final String docId = DocumentsContract.getTreeDocumentId(treeUri);
    final String[] split = docId.split(":");

    if (split.length > 0) {
        return split[0];
    }
    else {
        return null;
    }
}

private static final String PRIMARY_VOLUME_NAME = "primary";
private static String getVolumePath(final String volumeId, Context context) {
    try {
        StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);

        Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");

        Method getVolumeList = storageManager.getClass().getMethod("getVolumeList");
        Method getUuid = storageVolumeClazz.getMethod("getUuid");
        Method getPath = storageVolumeClazz.getMethod("getPath");
        Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
        Object result = getVolumeList.invoke(storageManager);

        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            Object storageVolumeElement = Array.get(result, i);
            String uuid = (String) getUuid.invoke(storageVolumeElement);
            Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);

            // primary volume?
            if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) {
                return (String) getPath.invoke(storageVolumeElement);
            }

            // other volumes?
            if (uuid != null) {
                if (uuid.equals(volumeId)) {
                    return (String) getPath.invoke(storageVolumeElement);
                }
            }
        }

        // not found.
        return null;
    }
    catch (Exception ex) {
        return null;
    }
}


private static String getDocumentPathFromTreeUri(final Uri treeUri) {
    final String docId = DocumentsContract.getTreeDocumentId(treeUri);
    final String[] split = docId.split(":");
    if ((split.length >= 2) && (split[1] != null)) {
        return split[1];
    }
    else {
        return File.separator;
    }
}


public static String getExtSdCardFolder(@NonNull final File file, Context context) {
    String[] extSdPaths = getExtSdCardPaths(context);
    try {
        for (String extSdPath : extSdPaths) {
            if (file.getCanonicalPath().startsWith(extSdPath)) {
                return extSdPath;
            }
        }
    }
    catch (IOException e) {
        return null;
    }
    return null;
}

private static String[] getExtSdCardPaths(Context context) {
    List<String> paths = new ArrayList<>();
    for (File file : context.getExternalFilesDirs("external")) {
        if (file != null && !file.equals(context.getExternalFilesDir("external"))) {
            int index = file.getAbsolutePath().lastIndexOf("/Android/data");
            if (index < 0) {
                Log.w("StorageHelper", "Unexpected external file dir: " + file.getAbsolutePath());
            }
            else {
                String path = file.getAbsolutePath().substring(0, index);
                try {
                    path = new File(path).getCanonicalPath();
                }
                catch (IOException e) {
                    // Keep non-canonical path.
                }
                paths.add(path);
            }
        }
    }
    return paths.toArray(new String[paths.size()]);
}








public static boolean copyFile(@NonNull final File source, @NonNull final File target, Context context) {
    FileInputStream inStream = null;
    OutputStream outStream = null;
    FileChannel inChannel = null;
    FileChannel outChannel = null;
    try {
        inStream = new FileInputStream(source);

        // First try the normal way
        if (isWritable(target)) {
            // standard way
            outStream = new FileOutputStream(target);
            inChannel = inStream.getChannel();
            outChannel = ((FileOutputStream) outStream).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
        }
        else {
            // Storage Access Framework
            DocumentFile targetDocument = getDocumentFile(target, false, true, context);
            if (targetDocument != null) {
                outStream = context.getContentResolver().openOutputStream(targetDocument.getUri());
            }

            if (outStream != null) {
                // Both for SAF and for Kitkat, write to output stream.
                byte[] buffer = new byte[4096]; // MAGIC_NUMBER
                int bytesRead;
                while ((bytesRead = inStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, bytesRead);
                }
            }

        }
    }
    catch (Exception e) {
        Log.e("StorageHelper",
                "Error when copying file from " + source.getAbsolutePath() + " to " + target.getAbsolutePath(), e);
        return false;
    }
    finally {
        try {
            inStream.close();
        }
        catch (Exception e) {
            // ignore exception
        }
        try {
            outStream.close();
        }
        catch (Exception e) {
            Log.e("StorageHelper", "OutStreamClose: " + e.toString());
            // ignore exception
        }
        try {
            ((FileChannel) inChannel).close();
        }
        catch (Exception e) {
            // ignore exception
        }
        try {
            outChannel.close();
        }
        catch (Exception e) {
            Log.e("StorageHelper", "OutChannelClose: " + e.toString());
            // ignore exception
        }
    }
    return true;
}


    public static String getExtensionFromName(String fileName){
    String extension = "";

    int i = fileName.lastIndexOf('.');
    if (i > 0) {
        extension = fileName.substring(i+1);
    }

    return extension;
}
0 голосов
/ 31 октября 2018

Вы можете попробовать использовать метод удаления канонического файла

File file = new File(uri.getPath());
file.delete();
if(file.exists()){
      file.getCanonicalFile().delete();
      if(file.exists()){
           getApplicationContext().deleteFile(file.getName());
      }
}
0 голосов
/ 31 октября 2018

Ваш путь к файлу был неверный, вы должны запросить абсолютный путь от ContentProvider по uri, чтобы узнать, как получить абсолютный путь по uri, проверьте этот вопрос, Как получить полный путь к файлу по URI

0 голосов
/ 31 октября 2018

попробуйте это

File file = new File(song.getFilePath);
  if(!file.delete()){
   if(file.exists()){
      file.getCanonicalFile().delete();
      if(file.exists()){
        getApplicationContext().deleteFile(file.getName());
     }
    }
  }
...