ContentResolver.openOutputStream() 抛出 FileNotFoundException 异常

ContentResolver.openOutputStream() throws strage FileNotFoundException

我的目的是能够在 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_STORAGEWRITE_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) {}
    }        
}

稍后在代码中,我获得了对我感兴趣的文件的引用,其中的内容 URI 由 DocumentFile 包装。我想强调 DocumentFile.exists()DocumentFile.canRead()DocumentFile.canWrite() return 阳性结果,还与系统 File.exists() 进行了检查。例如,我用 DocumentFile.getUri() 得到的 URI 看起来像:

content://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 台设备中有 1 台设备,但它仍然是一个相关问题,因为它破坏了应用程序的主要目的,因此用户只能卸载它。

Whosebug 上所有过去的问题看起来都与此类似,但解决方案是不写入存储的根目录,这显然不是这里的问题。 在这种特殊情况下,我真的不知道这些异常,所以我可能会问是否有人知道可能无法获得具有文件写访问权限的文件描述符的情况。

我觉得你的代码不错,但如果你想试试这个:

                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();
                }

您的异常消息显示 "Read-only file system"。该消息通常与本机 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. You can learn more about per-process mountinfo file from Linux kernel documentation.