android.os.FileUriExposedException: file:///storage/emulated/0/test.txt 通过 Intent.getData() 暴露在应用程序之外

新手上路,请多包涵

当我尝试打开文件时,应用程序崩溃了。它在 Android Nougat 下工作,但在 Android Nougat 上它会崩溃。只有当我尝试从 SD 卡而不是系统分区打开文件时,它才会崩溃。一些权限问题?

示例代码:

 File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line

日志:

android.os.FileUriExposedException: file:///storage/emulated/0/test.txt 通过 Intent.getData() 暴露在应用程序之外

编辑:

以 Android Nougat 为目标时,不再允许使用 file:// URI。我们应该使用 content:// URI 来代替。但是,我的应用程序需要在根目录中打开文件。有任何想法吗?

原文由 Thomas Vos 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

如果您的 targetSdkVersion >= 24 ,那么我们必须使用 FileProvider 类来授予对特定文件或文件夹的访问权限,以便其他应用程序可以访问它们。我们创建自己的类继承 FileProvider 以确保我们的 FileProvider 不会与 此处 所述的导入依赖项中声明的 FileProvider 冲突。

file:// URI 替换为 content:// URI 的步骤:

  • AndroidManifest.xml 标签下的 --- 添加一个 FileProvider <provider> <application> 标签。为 android:authorities 属性指定唯一权限以避免冲突,导入的依赖项可能指定 ${applicationId}.provider 和其他常用权限。
 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        ...
        <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/provider_paths" />
        </provider>
    </application>
</manifest>

  • 然后在 res/xml 文件夹中创建一个 provider_paths.xml --- 文件。如果文件夹尚不存在,则可能需要创建它。该文件的内容如下所示。它描述了我们希望共享对根文件夹 (path=".") 的外部存储的访问权限,名称为 external_files
 <?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

  • 最后一步是更改下面的代码行
   Uri photoURI = Uri.fromFile(createImageFile());

   Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());

  • 编辑: 如果您使用意图使系统打开您的文件,您可能需要添加以下代码行:
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

请参阅 此处已解释的完整代码和解决方案。

原文由 Pkosta 发布,翻译遵循 CC BY-SA 4.0 许可协议

我想从应用程序的范围存储中共享图像,这就是我遇到此异常的地方。搜索了几个小时,最后,我找到 了这个博客

它有点长,所以我在这里分享要点,但我会建议你仔细阅读。

底线是您不能从应用程序的范围存储中共享任何内容。同样在 Android 12 中,intent 选择器底部对话框会显示您正在共享的图像的预览,顺便说一句,这非常酷,但它无法从作用域存储 URI 加载预览。

解决方案是在缓存目录中创建您“打算”共享的文件的副本。

 val cachePath = File(externalCacheDir, "my_images/")
cachePath.mkdirs()
val bitmap = loadImageFromStorage(currentQuote.bookId)
val file = File(cachePath, "cache.png")
val fileOutputStream: FileOutputStream
try {
    fileOutputStream = FileOutputStream(file)
    bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
    fileOutputStream.flush()
    fileOutputStream.close()
} catch (e: FileNotFoundException) {
    e.printStackTrace()
} catch (e: IOException) {
    e.printStackTrace()
}
val cacheImageUri: Uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)

val intent = Intent(Intent.ACTION_SEND).apply {
    clipData = ClipData.newRawUri(null, cacheImageUri)
    putExtra(Intent.EXTRA_STREAM, cacheImageUri)
    type = "image/ *"
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, null))


这就是我从范围存储加载文件的方式

fun Context.loadImageFromStorage(path: String): Bitmap? {
    try {
        val file = getFile(path)
        val bitmap = BitmapFactory.decodeStream(FileInputStream(file))
        return bitmap
    } catch (e: Exception) {
        e.printStackTrace()

        //Returning file from public storage in case the file is stored in public storage
        return BitmapFactory.decodeStream(FileInputStream(File(path)))
    }

    return null
}

fun Context.getFile(path: String): File? {
    val cw = ContextWrapper(this)
    val directory = cw.getDir("image_dir", Context.MODE_PRIVATE)
    if (!directory.exists())
        directory.mkdir()
    try {
        val fileName = directory.absolutePath + "/" + path.split("/").last()
        return File(fileName)
    } catch (e: Exception) {
        e.printStackTrace()
    }

    return null
}

最后,不要忘记更新您的 provider_paths.xml 文件

<external-cache-path name="external_cache" path="." />

<external-cache-path name="external_files" path="my_images/"/>

原文由 Shunan 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题