首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >安卓: FileProvider.getUriForFile读取MediaStore生成的文件?

安卓: FileProvider.getUriForFile读取MediaStore生成的文件?
EN

Stack Overflow用户
提问于 2022-02-15 05:06:56
回答 7查看 1.5K关注 0票数 3

TL;DR

我想获得(读取/生成)文件Uri --路径类似于下面的路径-- FileProvider,但不知道如何获得:

代码语言:javascript
运行
复制
val file = File("/external/file/9359") // how do I get its Uri then?

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

我在做什么

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

我所做的:

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

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

  • File的Uri

但是通常的file.toUri()会在SDK 29之上抛出异常。因此,我将其改为FileProvider.getUriForFile(),这是谷歌官方推荐的。

直接问题码

代码语言:javascript
运行
复制
val fileUri: Uri = FileProvider.getUriForFile(context, "my.provider", file)

将引发异常:

代码语言:javascript
运行
复制
java.lang.IllegalArgumentException: Failed to find configured root that contains /external/file/9359

我的文件创建代码

代码语言:javascript
运行
复制
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注册供应商:

代码语言:javascript
运行
复制
<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:

代码语言:javascript
运行
复制
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external"
        path="." />
</paths>

我也尝试过这么多路径变体,一个接一个:

代码语言:javascript
运行
复制
<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="." />

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

代码语言:javascript
运行
复制
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

我哪部分做错了?

编辑1

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

代码语言:javascript
运行
复制
File(fileUri.path!!)

所以我把它改成了

代码语言:javascript
运行
复制
File(fileUri.toString())

但还是不起作用。

它们的内容差异如下:

代码语言:javascript
运行
复制
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

我最初想要实现的是向其他应用程序发送二进制数据。据官方记载,它似乎只接受文件Uri

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

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

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2022-02-22 15:12:24

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

代码语言:javascript
运行
复制
 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()
        }
    }
   }

--编辑--

内容Uri有路径,但没有文件路径。例如;

内容Uri路径:内容://media/外部/音频/media/710,

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

通过使用上面的函数,您可以创建一个临时文件,并且可以提取uri和路径,如下所示:

代码语言:javascript
运行
复制
val tempFile: File = createFileFromContentUri(contentUri)  
val filePath = tempFile.path  
val fileUri = tempFile.getUri() 

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

代码语言:javascript
运行
复制
tempFile.delete()

不需要编辑内容uri。将方案添加到内容uri可能不起作用

票数 2
EN

Stack Overflow用户

发布于 2022-07-04 09:14:18

首先,在最近的Android中,您通常不会从Uri中获取文件路径,因为您可能只有临时访问权限,所以通常使用context.contentResolver.openInputStream(externalUri)将其流中的内容复制到随后可以使用的文件中。

通过这样做,您将一个文件放在一个不属于您的应用程序的目录中,因此FileProvider可能不需要,您只需传递从MediaStore插入获得的uri。

使用FileProvider的正确方法是将文件放入您的FileProvider路径之一,而不使用MediaStore,因此假设您有

代码语言:javascript
运行
复制
<provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepath" />
</provider>

代码语言:javascript
运行
复制
<external-files-path name="external" path="shared" />

您将在该路径中创建文件。

代码语言:javascript
运行
复制
val sharedFolder=context.getExternalFilesDir("shared")
val fileName="test.txt"

File(sharedFolder,fileName).outputStream()?.use { fileOut ->
    fileOut.write("Hello, World!".toByteArray())
}

val fileProviderUri = FileProvider.getUriForFile(
                    context,
                    BuildConfig.APPLICATION_ID + ".provider",
                    File(sharedFolder,fileName)
                )
//example intent for viewing file, to change if you need to pass to another application
                context.startActivity(
                    Intent(Intent.ACTION_VIEW).setData(fileProviderUri )
                        .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                )
票数 1
EN

Stack Overflow用户

发布于 2022-02-15 05:52:24

兄弟,试试这个,也许它对你有用,把你的path.xml文件改成这个;

代码语言:javascript
运行
复制
<?xml version="1.0" encoding="utf-8"?>
<paths>
  <external-path
    name="external"
    path="." />
  <external-files-path
    name="external_files"
    path="." />
  <cache-path
    name="cache"
    path="." />
  <external-cache-path
    name="external_cache"
    path="." />
<files-path
    name="files"
    path="." />
</paths>

也可以检查这个,如果解决方案不起作用的话。

https://stackoverflow.com/a/42516202/7905787

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71121342

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档