Android:FileProvider.getUriForFile 读取 MediaStore 生成的文件?

标签 android uri mediastore android-fileprovider

长话短说

我想通过 FileProvider 获取(读取/生成)文件 Uri - 路径如下 - 但不知道如何:

val file = File("/external/file/9359") // how do I get its Uri then?

我只知道 FileProvider.getUriForFile 方法,但它会抛出异常。

我在做什么

我试图模拟下载进度——在 Download 文件夹中创建一个文件,然后将其传递给 ShareSheet 让用户做任何它想做的事情。

我做了什么:

  1. 使用 MediaStoreContentResolverDownload 中创建文件。
  2. 有一个 ShareSheet util 函数。
  3. 已注册 FileProviderfilepath.xml

在我的例子中,我想通过 ShareSheet 函数共享文件,这需要

  • 文件的Uri

但通常的 file.toUri() 会在 SDK 29 以上抛出异常。因此我按照 Google 官方的建议将其更改为 FileProvider.getUriForFile()

直接问题代码

val fileUri: Uri = FileProvider.getUriForFile(context, "my.provider", file)

会抛出异常:

java.lang.IllegalArgumentException: Failed to find configured root that contains /external/file/9359

我的文件创建代码

val fileUri: Uri? = ContentValues().apply {
    put(MediaStore.MediaColumns.DISPLAY_NAME, "test.txt")
    put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // create file in download directory.
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
    }
}.let { values ->
    context.contentResolver.insert(MediaStore.Files.getContentUri("external"), values)
}
if (fileUri == null) return

context.contentResolver.openOutputStream(fileUri)?.use { fileOut ->
    fileOut.write("Hello, World!".toByteArray())
}

val file = File(fileUri.path!!) // File("/external/file/9359")

我可以保证代码是正确的,因为我可以在 Download 文件夹中看到正确的文件。

我的 FileProvider 设置

我已经在 AndroidManifest.xml 中注册了提供者:

<application>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="my.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepath" />
    </provider>
</application>

下面是 filepath.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external"
        path="." />
</paths>

我也尝试了很多路径变体,一一尝试:

<external-files-path name="download" path="Download/" />
<external-files-path name="download" path="." />
<external-path name="download" path="Download/" />
<external-path name="download" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />

我什至还注册了外部存储读取权限:

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

我哪里做错了?

编辑1

发布后,我认为 File 初始化可能是问题所在:

File(fileUri.path!!)

所以我改成了

File(fileUri.toString())

但还是不行。

顺便说一句,它们的内容差异如下:

fileUri.path       -> /external/file/9359
(file.path)        -> /external/file/9359
error              -> /external/file/9359

fileUri.toString() -> content://media/external/file/9359
(file.path)        -> content:/media/external/file/9359
error              -> /content:/media/external/file/9359

编辑2

本来想实现的是sending binary data to other app .正如官方记录的那样,它似乎只接受文件 Uri

如果有任何其他方法可以实现这一点,我将不胜感激,比如直接共享 File 等。

但我现在想知道的很简单——如何使 FileProvider 可用于获取/读取/生成文件 Uri,如 /external/file/9359 等等?这可能不仅对这种情况有帮助,而且对我来说似乎是一个更一般/基本的知识。

最佳答案

内容 uris 没有文件路径或方案。您应该创建一个输入流,并使用此输入流创建一个临时文件。您可以从此文件中提取文件 uri 或路径。这是我的一个函数,用于从 content-uri 创建一个临时文件:

 fun createFileFromContentUri(fileUri : Uri) : File{

    var fileName : String = ""

    fileUri.let { returnUri ->
        requireActivity().contentResolver.query(returnUri,null,null,null)
    }?.use { cursor ->
        val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
        cursor.moveToFirst()
        fileName = cursor.getString(nameIndex)
    }
    
    //  For extract file mimeType
    val fileType: String? = fileUri.let { returnUri ->
        requireActivity().contentResolver.getType(returnUri)
    }

    val iStream : InputStream = requireActivity().contentResolver.openInputStream(fileUri)!!
    val outputDir : File = context?.cacheDir!!
    val outputFile : File = File(outputDir,fileName)
    copyStreamToFile(iStream, outputFile)
    iStream.close()
    return  outputFile
}

fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
    inputStream.use { input ->
        val outputStream = FileOutputStream(outputFile)
        outputStream.use { output ->
            val buffer = ByteArray(4 * 1024) // buffer size
            while (true) {
                val byteCount = input.read(buffer)
                if (byteCount < 0) break
                output.write(buffer, 0, byteCount)
            }
            output.flush()
        }
    }
   }

--编辑--

Content Uri 有路径但没有文件路径。例如;

内容 Uri 路径:content://media/external/audio/media/710 ,

文件 uri 路径:file:///sdcard/media/audio/ringtones/nevergonnagiveyouup.mp3

通过使用上面的函数,你可以创建一个临时文件,你可以像这样提取 uri 和路径:

val tempFile: File = createFileFromContentUri(contentUri)  
val filePath = tempFile.path  
val fileUri = tempFile.getUri() 

使用 tempFile 后删除它以防止内存问题(它将被删除,因为它写在缓存中):

tempFile.delete()

无需编辑内容 uri。向内容 uri 添加方案可能行不通

关于Android:FileProvider.getUriForFile 读取 MediaStore 生成的文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71121342/

相关文章:

android - 如何为 Android 发布库

java - 使用 Intent 将 TikTok 应用打开至其主页

java.lang.NoClassDefFoundError & java.lang.ClassNotFoundException - Android

url - 没有协议(protocol)的 URL 有名称吗?

java - 使用 Android 的 MediaStore(ContentResolver/范围存储)创建图像文件后文件扩展名和文件名错误

java - 如何将数据放入XML的ListView中?

javascript - Firefox 7 中 URI 中的空格

url - ffmpeg 流式传输 rtsp 与 uri (url) 中的参数

android - 媒体存储插入 DATE_TAKEN 始终为空

android - 从Android设备android编程获取所有照片