【问题标题】:Cannot delete a song from SD Card无法从 SD 卡中删除歌曲
【发布时间】:2018-10-31 14:37:08
【问题描述】:

我正在尝试创建允许我的音乐应用从存储中删除歌曲的代码。 到目前为止,如果文件在内部(模拟)存储(即不是应用程序的内部存储,而是手机的内部共享存储)上,则成功删除文件。 但是,一旦歌曲在外部 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 卡上时,它才会删除失败。当它保存在内部存储上时,它可以很好地删除。 为什么不删除,在 Android 5.0+ 上从 SD 卡中删除文件的正确方法是什么?

非常感谢。

编辑: 我忘了说我已经添加了权限:

<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/ 中的步骤授予额外的权限

我怎样才能做到这一点?

【问题讨论】:

  • 您是否在 Android Manifest 中包含了&lt;uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /&gt; 权限?
  • 我已经添加了 。我也会尝试
  • 不幸的是也没有用
  • 您能否提供一个 tiny 应用程序来重现您的问题,并将源代码上传到某个地方供我们查看?
  • @user9656809 您找到解决问题的方法了吗?我也有同样的问题。

标签: android file android-sdcard


【解决方案1】:

我注意到这个问题再次引起了人们的兴趣。我很高兴地说,我确实找到了解决问题的方法。我进行了广泛的在线研究并找到了一些源代码文件(尽管很抱歉我不记得在哪里找到它们)解决了我的问题

Android 4.4 及更高版本的问题在于,您需要通过存储访问框架获得额外权限,以便第三方应用删除/修改外部 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();
    }
}

您还需要文件的“文档文件”才能修改和删除文件。 以下代码 sn-ps 可以帮助您做到这一点,以及使用文档文件方法检测文件/目录是否可写... (我知道它有很多代码,其中大部分来自另一个来源。我真的很遗憾忘记了我从哪里得到它,因为它们确实值得很多赞誉)。您需要注意的函数是: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;
}

【讨论】:

  • DocumentFile document = getDocumentFile(file, false, true, context); isDirectory false 和 createDirectories true 的值是做什么的,当我只想从 sdcard 中删除文件时,我是否只保留这些值?
  • DocumentFilepickDir = DocumentFile.fromTreeUri(this, treeUri);也从未使用过,所以这条线有什么作用?
  • @Vince 至于pickDir,你可以删除它,它只是用于调试代码。使用“delteFile()”函数将尝试删除任何文件,如果成功删除则返回。至于“isDirectory”和“createDirectory”的值,可以这样保留。
【解决方案2】:

试试这个

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

【讨论】:

  • 仍然没有运气,当我使用文件管理器应用程序检查目录时,文件仍保留在 SD 卡上。
【解决方案3】:

您可以尝试使用规范的文件删除方法

File file = new File(uri.getPath());
file.delete();
if(file.exists()){
      file.getCanonicalFile().delete();
      if(file.exists()){
           getApplicationContext().deleteFile(file.getName());
      }
}

【讨论】:

  • 还是不行,当我使用文件管理器应用程序检查目录时,文件仍保留在 SD 卡上。
【解决方案4】:

你的文件路径错误,你应该通过uri从ContentProvider查询绝对路径,关于如何通过uri获取绝对路径检查这个问题,How to get the Full file path from URI

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    • 2018-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多