我正在尝试创建允许我的音乐应用从存储中删除歌曲的代码。 到目前为止,如果文件位于内部(模拟)存储(即不是应用程序的内部存储,而是手机的内部共享存储)上,则可以成功删除文件。 但是,只要歌曲在外部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/
之类的步骤,授予文件管理器应用程序其他权限。我该如何实现?
答案 0 :(得分:2)
我注意到这个问题再次引起了人们的兴趣。我很高兴地说,我确实找到了解决问题的办法。我进行了广泛的在线研究,发现了一些源代码文件(尽管很抱歉,我不记得在哪里找到它们)
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;
}
答案 1 :(得分:0)
尝试这个
arya@2OC-2OE:~/Documents/workspace-spring-tool-suite/HelperAPI$ mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building HelperAPI 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.0.0:clean (default-clean) @ HelperAPI ---
[INFO] Deleting /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ HelperAPI ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ HelperAPI ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 35 source files to /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ HelperAPI ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ HelperAPI ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.21.0:test (default-test) @ HelperAPI ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.411 s
[INFO] Finished at: 2018-10-31T10:50:53-04:00
[INFO] Final Memory: 32M/294M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.21.0:test (default-test) on project HelperAPI: There are test failures.
[ERROR]
[ERROR] Please refer to /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream.
[ERROR] The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd /home/arya/Documents/workspace-spring-tool-suite/HelperAPI && /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/surefire/surefirebooter3828813415223070401.jar /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/surefire 2018-10-31T10-50-53_064-jvmRun1 surefire8224915599358179621tmp surefire_0529698540830278579tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was /bin/sh -c cd /home/arya/Documents/workspace-spring-tool-suite/HelperAPI && /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/surefire/surefirebooter3828813415223070401.jar /home/arya/Documents/workspace-spring-tool-suite/HelperAPI/target/surefire 2018-10-31T10-50-53_064-jvmRun1 surefire8224915599358179621tmp surefire_0529698540830278579tmp
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:671)
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:533)
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:278)
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:244)
[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1149)
[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:978)
[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:854)
[ERROR] at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134)
[ERROR] at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
[ERROR] at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:154)
[ERROR] at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:146)
[ERROR] at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
[ERROR] at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
[ERROR] at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
[ERROR] at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
[ERROR] at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:309)
[ERROR] at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:194)
[ERROR] at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:107)
[ERROR] at org.apache.maven.cli.MavenCli.execute(MavenCli.java:955)
[ERROR] at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:290)
[ERROR] at org.apache.maven.cli.MavenCli.main(MavenCli.java:194)
[ERROR] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] at java.lang.reflect.Method.invoke(Method.java:498)
[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
[ERROR] at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
答案 2 :(得分:0)
您可以尝试使用规范的文件删除方法
File file = new File(uri.getPath());
file.delete();
if(file.exists()){
file.getCanonicalFile().delete();
if(file.exists()){
getApplicationContext().deleteFile(file.getName());
}
}
答案 3 :(得分:0)
您的文件路径错误,您应该通过uri查询ContentProvider的绝对路径,有关如何通过uri获取绝对路径,请检查此问题,How to get the Full file path from URI