我的目的是能够在Android 21+上编辑可移动外部存储上的文件。为此,必须使用存储访问框架。
首先,我授予了我感兴趣的文件夹的读写权限:
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), 123);
@Override
public void onActivityResult(int requestCode,int resultCode,Intent resultData) {
if (resultCode != RESULT_OK)
return;
Uri treeUri = resultData.getData();
PreferenceUtils.setPersistedUri(this, treeUri);
grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
我确信这是正确完成的,因为我仔细检查了一下:
for(UriPermission uri : getContentResolver().getPersistedUriPermissions())
if(uri.isReadPermission() && uri.isWritePermission())
// This prints the correct folder
如果相关,我还在清单中声明并请求READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
的运行时权限。
DocumentFile parentDocumentFile = DocumentFile.fromTreeUri(getActivity(), PreferenceUtils.getPersistedUri());
if(parentDocumentFile == null) return;
for(DocumentFile file : parentDocumentFile.listFiles()) {
if(file.canRead() && file.canWrite() && file.exists()) {
try
{
OutputStream outStream = getActivity().getContentResolver().openOutputStream(file.getUri());
// I/O operations
} (catch FileNotFoundException e) {}
}
}
稍后在代码中,我通过DocumentFile
包装的内容URI获得了我感兴趣的文件的引用。我想强调DocumentFile.exists()
,DocumentFile.canRead()
,DocumentFile.canWrite()
返回肯定结果,同时系统File.exists()
的检查也已完成。例如,我使用DocumentFile.getUri()
获得的URI如下所示:
内容://com.android.externalstorage.documents/tree/486E-2542%3A/document/486E-2542%3ADownload%2FFildoDownloads%2FLed%20Zeppelin%2FLed%20Zeppelin%20-%20Kashmir.mp3
好吧。但是如果我想打开它来写,我不能,这是唯一可用的方法来编写一个通过存储访问框架授予权限的文件:
OutputStream outStream = getContentResolver().openOutputStream(DocumentFile.getUri());
某些设备(我无法重现)使用以下堆栈跟踪抛出FileNotFoundExcpetion
:
java.io.FileNotFoundException: Failed opening content provider: content://com.android.externalstorage.documents/tree/486E-2542%3A/document/486E-2542%3ADownload%2FFildoDownloads%2FLed%20Zeppelin%2FLed%20Zeppelin%20-%20Kashmir.mp3
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1032)
at android.content.ContentResolver.openOutputStream(ContentResolver.java:718)
at android.content.ContentResolver.openOutputStream(ContentResolver.java:694)
at helpers.c.b(SourceFile:405)
其他一些人:
java.io.FileNotFoundException: Failed to open for writing: java.io.FileNotFoundException: Read-only file system
at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:144)
at android.content.ContentProviderProxy.openAssetFile(ContentProviderNative.java:621)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1011)
at android.content.ContentResolver.openOutputStream(ContentResolver.java:753)
at android.content.ContentResolver.openOutputStream(ContentResolver.java:729)
at helpers.c.b(SourceFile:405)
这不会经常发生 ,让我们说一下200个设备,但它仍然是一个相关的问题,因为打破了应用程序的主要目的,所以用户可以卸载它。
StackOverflow上的所有过去的问题看起来都与此相似,但是那里的解决方案没有写入存储的根目录,这显然不是问题所在。 在这个特定的场景中,我真的不知道这些异常,所以我可能会问是否有人知道可能的情况使得无法获得具有文件写入权限的文件描述符。
答案 0 :(得分:2)
您的例外中的消息显示"只读文件系统"。该消息通常与本机errno "EROFS"相关联,当Linux进程尝试在只读存储上打开文件进行写入时会发生此错误。
有多种原因,为什么存储可以只读安装。例如,大多数文件系统在严重数据损坏的情况下由内核自动重新安装(Android系统可能甚至没有意识到,设备SD卡已切换到只读模式)。
由于Android系统代码中的错误,也可能发生这种情况。在这种情况下,了解设备供应商和型号可能对调试很有用。
最简单的解决方案:如果打开写入失败,请尝试以只读模式打开文件。如果只读打开成功,则可以通过弹出并重新插入SD卡来解决问题(这应该会导致Android自动在其上运行fsck)。
Uri uri = file.getUri();
try (OutputStream o = context.getContentResolver().openOutputStream(uri))
{
// I/O operations
} (catch FileNotFoundException e) {
try (OutputStream s = context.getContentResolver().openInputStream(uri)) {
// the file can be opened for reading, but not for writing
// ask user to reinsert SD card and/or disable
// write protection toggle on it
Toast.makeText(context, "Please reinsert SD card", LENGTH_SHORT);
} catch (FileNotFoundException err) {
// This is hopeless, just report error to user
Toast.makeText(context, err.toString(), LENGTH_SHORT);
}
}
如果要确定文件系统是否实际以只读方式挂载,请考虑从特殊文件/proc/self/mountinfo
中读取。您可以将其内容发送到Crashlytics进行调试,甚至是parse them in your code。您可以从Linux kernel documentation了解有关每个进程mountinfo
文件的详细信息。
答案 1 :(得分:-1)
您的代码对我来说很好,但如果您愿意,可以尝试:
ContentResolver cr = this.getContentResolver();
String myString= "This is what we will write" ;
OutputStream stream = null;
try {
stream = cr.openOutputStream(///YourFile///.getUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
stream.write(myString.getBytes());
} catch (IOException e) {
e.printStackTrace();
}catch (NullPointerException e){
e.printStackTrace();
}