android.os.FileUriExposedException:file:///storage/emulated/0/test.txt 通过 Intent.getData() 在应用之外公开

android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()

提问人:Thomas Vos 提问时间:7/5/2016 最后编辑:Thomas Vos 更新时间:12/22/2022 访问量:620856

问:

当我尝试打开文件时,应用程序崩溃了。它可以在 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 为目标时,不再允许使用 URI。我们应该改用 URI。但是,我的应用程序需要打开根目录中的文件。有什么想法吗?file://content://

安卓 android-file android-7.0-牛轧糖

评论

66赞 nyanpasu64 9/17/2018
我觉得这是一个错误,它给应用程序开发人员带来了不必要的困难。必须将“FileProvider”和“authority”与每个应用程序捆绑在一起,这似乎是 Enterprisey 样板。必须为每个文件意图添加一个标志似乎很尴尬,而且可能没有必要。打破“路径”的优雅概念是令人不快的。有什么好处?有选择地向应用程序授予存储访问权限(而大多数应用程序具有完整的 SDCARD 访问权限,尤其是那些处理文件的应用程序)?
3赞 Binesh Kumar 10/8/2018
试试这个,小而完美的代码 stackoverflow.com/a/52695444/4997704
5赞 Ehsan 1/13/2021
@nyanpasu64 我同意。自 API 19 以来,Google 已经开始通过一些更改来羞辱自己
0赞 Val Martinez 3/25/2021
恕我直言,我认为谷歌基本上想知道你在哪里保存文件。从清单中读取路径。因此,他们可以自动处理该路径......

答:

92赞 CommonsWare 7/5/2016 #1

如果您的 24 或更高版本,则无法在 Android 7.0+ 设备上的 Intents 中使用 file: UritargetSdkVersion

您的选择是:

  1. 将您的年龄降至 23 或更低,或者targetSdkVersion

  2. 将您的内容放在内部存储中,然后使用 FileProvider 有选择地将其提供给其他 App

例如:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(来自此示例项目)

评论

0赞 Thomas Vos 7/5/2016
谢谢你的回答。当我将其用于分区上的文件时会发生什么?每个应用程序都应该能够在没有 root 的情况下访问此分区。/system
2赞 CommonsWare 7/5/2016
@SuperThomasLab:我不会指望一切都是世界可读的。话虽如此,我的猜测是你仍然会得到这个例外。我怀疑他们只是在检查方案,而不是试图确定该文件是否真的是全局可读的。但是,对您没有帮助,因为您无法教它从.您可以为我的 StreamProvider 创建一个自定义策略,也可以推出自己的策略来解决问题。/systemFileProvider/systemContentProvider
0赞 Thomas Vos 7/6/2016
还在想我将如何解决这个问题......我正在更新的 Android N 支持应用程序是 root 浏览器。但是现在您无法再在根目录中打开任何文件。(,),因为这个“好的变化”。/data/system
1赞 rommex 6/29/2017
将 targetSdkVersion 降至 23 的最重要缺点是什么?THNX的
2赞 CommonsWare 6/29/2017
@rommex:我不知道什么是“最重要的”。例如,在分屏模式或使用自由格式多窗口设备(Chromebook、Samsung DeX)的用户将被告知您的应用可能无法与多窗口一起使用。这是否重要取决于你。
1625赞 Palash kosta 8/10/2016 #2

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

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

  • 在 under 标记中添加 FileProvider 标记。为属性指定唯一权限以避免冲突,导入的依赖项可能会指定和其他常用权限。<provider>AndroidManifest.xml<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>
  • 然后在文件夹中创建一个文件。如果文件夹尚不存在,则可能需要创建该文件夹。文件内容如下所示。它描述了我们希望共享对名为 external_files 的根文件夹的外部存储的访问权。provider_paths.xmlres/xml(path=".")
<?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 使系统打开您的文件,您可能需要添加以下代码行:

     intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

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

评论

77赞 alorma 11/22/2016
我只需要添加 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
29赞 android developer 11/25/2016
它适用于所有 Android 版本,还是仅适用于 API 24?
11赞 AbdulMomen عبدالمؤمن 3/19/2017
这篇文章帮助我 medium.com/@ali.muzaffar/...
12赞 Sébastien 12/14/2017
@rockhammer我刚刚用 Android 5.0、6.0、7.1 和 8.1 测试了这一点,它适用于所有情况。所以条件是无用的。(Build.VERSION.SDK_INT > M)
72赞 JP Ventura 1/16/2018
FileProvider仅当要覆盖任何默认行为时才应扩展,否则使用 .查看 developer.android.com/reference/android/support/v4/content/...android:name="android.support.v4.content.FileProvider"
50赞 Karn Patel 8/13/2016 #3

首先,您需要向 AndroidManifest 添加一个提供程序

  <application
    ...>
    <activity>
    .... 
    </activity>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.your.package.fileProvider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
  </application>

现在在 xml 资源文件夹中创建一个文件(如果使用 Android Studio,您可以在突出显示file_paths后按 Alt + Enter,然后选择创建 xml 资源选项)

接下来在file_paths文件中输入

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <external-path path="Android/data/com.your.package/" name="files_root" />
  <external-path path="." name="external_storage_root" />
</paths>

此示例适用于外部路径,您可以在此处参考以获取更多选项。 这将允许您共享该文件夹及其子文件夹中的文件。

现在剩下的就是创建意向,如下所示:

    MimeTypeMap mime = MimeTypeMap.getSingleton();
    String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
    String type = mime.getMimeTypeFromExtension(ext);
    try {
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
            intent.setDataAndType(contentUri, type);
        } else {
            intent.setDataAndType(Uri.fromFile(newFile), type);
        }
        startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
    } catch (ActivityNotFoundException anfe) {
        Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
    }

编辑:我在file_paths中添加了SD卡的根文件夹。我已经测试了这段代码,它确实有效。

评论

2赞 praneetloke 1/1/2017
谢谢你。我还想让你知道,有一种更好的方法来获取文件扩展名。 此外,我建议任何寻找答案的人先通读 FileProvider,并了解您在这里使用 Android N 及更高版本中的文件权限处理的内容。有内部存储与外部存储以及常规文件路径与缓存路径的选项。String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString());
2赞 steliosf 7/1/2017
我收到以下异常:唯一有效的是在 xml 文件而不是 .我的文件保存在内部存储中。java.lang.IllegalArgumentException: Failed to find configured root ...<files-path path="." name="files_root" /><external-path ...
0赞 2/4/2021
不要在路径中写下你的包名称,使用类似这样的内容<files-path path=“../“ name=”private_data“ /> 如果需要访问所有根文件夹,而不仅仅是私有文件文件夹。
343赞 hqzxzwb 11/18/2016 #4

除了使用 的解决方案之外,还有另一种方法可以解决这个问题。简单地说FileProvider

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

在。这样,VM 将忽略文件公开。Application.onCreate()URI

方法

builder.detectFileUriExposure()

启用文件公开检查,如果我们不设置 VmPolicy,这也是默认行为。

我遇到了一个问题,如果我使用 a 发送东西,某些应用程序就无法理解它。并且不允许降级版本。在这种情况下,我的解决方案很有用。content://URItarget SDK

更新:

如评论中所述,StrictMode 是诊断工具,不应用于此问题。当我一年前发布此答案时,许多应用程序只能接收文件 uri。当我尝试向他们发送 FileProvider uri 时,它们就崩溃了。现在大多数应用程序都修复了这个问题,因此我们应该使用 FileProvider 解决方案。

评论

1赞 hqzxzwb 7/20/2017
@LaurynasG 从 API 18 到 23,Android 默认不检查文件 URI 公开。调用此方法将启用此检查。从 API 24 开始,android 默认执行此检查。但是我们可以通过设置新的 .VmPolicy
0赞 CKP78 11/23/2017
还需要其他步骤才能工作吗?不起作用,因为它代表我运行 Android 7.0 的 Moto G。
6赞 Imene Noomene 1/4/2018
然而,这如何解决这个问题,StrictMode是一个诊断工具,应该在开发人员模式下启用,而不是在发布模式下启用???
1赞 hqzxzwb 1/4/2018
@ImeneNoomene 实际上,我们在这里禁用了 StrictMode。在发布模式下不应该启用 StrictMode 似乎是合理的,但实际上 Android 默认启用了一些 StrictMode 选项,无论调试模式还是发布模式。但不管怎样,这个答案只是为了当一些目标应用程序没有准备好接收内容 uri 时的解决方法。现在,大多数应用都添加了对内容 uri 的支持,我们应该使用 FileProvider 模式。
3赞 Jon 1/6/2018
@ImeneNoomene 我完全支持你的愤怒。你是对的,这是一个诊断工具,或者至少很久以前我把它添加到我的项目中。这太令人沮丧了!,我只在我的开发版本上运行它,以防止这种崩溃发生 - 所以我现在有一个生产应用程序,它崩溃了,但在开发时不会崩溃。所以基本上,在这里启用诊断工具隐藏了一个严重的问题。感谢@hqzxzwb帮助我揭开这个神秘面纱。StrictMode.enableDefaults();
13赞 Atiq 12/12/2016 #5

我使用了上面给出的 Palash 的答案,但它有点不完整,我必须提供这样的许可

Intent intent = new Intent(Intent.ACTION_VIEW);
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));

        List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }else {
        uri = Uri.fromFile(new File(path));
    }

    intent.setDataAndType(uri, "application/vnd.android.package-archive");

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    startActivity(intent);
26赞 Ramz 1/15/2017 #6

@palash k 答案是正确的,适用于内部存储文件,但就我而言,我也想从外部存储打开文件,当从外部存储(如 sdcard 和 usb)打开文件时,我的应用程序崩溃了,但我设法通过修改接受的答案中的provider_paths.xml来解决问题

更改provider_paths.xml如下所示

<?xml version="1.0" encoding="utf-8"?>
 <paths xmlns:android="http://schemas.android.com/apk/res/android">

<external-path path="Android/data/${applicationId}/" name="files_root" />

<root-path
    name="root"
    path="/" />

</paths>

在java类中(没有变化作为接受的答案,只是一个小的编辑)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

这有助于我修复外部存储中文件的崩溃,希望这能帮助一些与我有相同问题的人 :)

评论

1赞 t0m 3/16/2017
你在哪里找到的关于请?它正在工作。 对从外部存储打开的文件没有影响。<root-path<external-path path="Android/data/${applicationId}/" name="files_root" />
0赞 Ramz 3/16/2017
我从各种搜索结果中找到了这一点,让我再次检查并尽快回复您
0赞 Ramz 3/16/2017
您提到的外部存储是SD卡还是内置存储?
0赞 t0m 3/20/2017
对不起,不准确。我的意思是在SD卡中。Android/data/${applicationId}/
1赞 s-hunter 2/17/2018
需要将其添加到 intent 中:intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
18赞 Simon 2/8/2017 #7

使用 fileProvider 是要走的路。 但是您可以使用以下简单的解决方法:

警告:它将在下一个 Android 版本中修复 - https://issuetracker.google.com/issues/37122890#comment4

取代:

startActivity(intent);

startActivity(Intent.createChooser(intent, "Your title"));

评论

7赞 Pointer Null 2/24/2017
谷歌将很快修补选择器,以包含相同的检查。这不是一个解决方案。
0赞 Diljeet 4/25/2017
这个可以工作,但在将来的 android 版本中不起作用。
0赞 Mohamad Ghaith Alzin 10/11/2022
这是行不通的!
177赞 Pointer Null 2/24/2017 #8

如果你的应用以 API 24+ 为目标,并且你仍然希望/需要使用 file:// 意图,则可以使用 hacky 方式禁用运行时检查:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

方法被隐藏并记录为:StrictMode.disableDeathOnFileUriExposure

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

问题是我的应用程序不是蹩脚的,而是不想通过使用许多应用程序无法理解的 content:// 意图而瘫痪。例如,使用 content:// 方案打开 mp3 文件提供的应用程序比通过 file:// 方案打开相同的应用程序要少得多。我不想通过限制我的应用程序的功能来为 Google 的设计错误买单。

谷歌希望开发人员使用内容方案,但系统没有为此做好准备,多年来,应用程序被制作为使用文件而不是“内容”,文件可以编辑和保存回来,而通过内容方案提供的文件不能(可以吗?

评论

3赞 CommonsWare 2/26/2017
“虽然通过内容方案提供的文件不能(可以吗?)”——当然,如果你对内容有写入权限。 同时具有 和 。执行此操作的一种不太棘手的方法是自行配置 VM 规则,而不启用该规则。ContentResolveropenInputStream()openOutputStream()fileUri
1赞 Matt W 5/26/2017
完全。当您构建整个应用程序时,这是一项艰苦的工作,然后在定位 25 个后发现您的所有相机方法都失败了。这对我有用,直到我有时间以正确的方式去做。
5赞 Anton Kizema 5/29/2017
适用于 Android 7。谢谢
4赞 Gonzalo Ledezma Torres 8/7/2017
也适用于Android 8,在华为Nexus 6P上进行了测试。
4赞 Eli 4/22/2018
我确认这正在生产中工作(我有超过 500,000 个用户),目前版本 8.1 是最高版本,它可以在它上面工作。
223赞 Pankaj Lilan 8/18/2017 #9

如果大于 24,则使用 FileProvider 授予访问权限。targetSdkVersion

创建xml文件(路径:res\xml)provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

AndroidManifest 中添加提供程序.xml

    <provider
        android:name="android.support.v4.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>

如果您使用的是 androidx,则 FileProvider 路径应为:

 android:name="androidx.core.content.FileProvider"

替换

Uri uri = Uri.fromFile(fileImagePath);

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

编辑:在包含带有 URI 的 URI 时,请确保添加以下行:Intent

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

你就可以走了。

评论

2赞 Pankaj Lilan 11/16/2017
@MaksimKniazev 你能简要描述一下你的错误吗?这样我就可以帮助你。
2赞 Felipe Castilhos 3/8/2018
@PankajLilan,我完全按照你说的做了。但是每次我在另一个应用程序中打开我的pdf时,它都会显示为空白(正确保存)。我需要编辑 xml 吗?我也已经添加了FLAG_GRANT_READ_URI_PERMISSION;
2赞 Felipe Castilhos 3/8/2018
我的错误,我向错误的意图添加了权限。这是最好和最简单的正确答案。谢谢!
3赞 Jagdish Bhavsar 3/8/2018
它抛出异常 java.lang.IllegalArgumentException:无法找到包含 / 我的文件路径为 /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf 的配置根。你能帮忙吗?
3赞 paakjis 8/9/2019
这就是答案!其他人都错了。这是新鲜的,适用于新代码!
4赞 Bhoomika Chauhan 7/28/2018 #10

要从服务器下载 pdf,请在服务类中添加以下代码。希望这对您有所帮助。

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf");
    intent = new Intent(Intent.ACTION_VIEW);
    //Log.e("pathOpen", file.getPath());

    Uri contentUri;
    contentUri = Uri.fromFile(file);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

    if (Build.VERSION.SDK_INT >= 24) {

        Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
        intent.setDataAndType(apkURI, "application/pdf");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    } else {

        intent.setDataAndType(contentUri, "application/pdf");
    }

是的,不要忘记在清单中添加权限和提供者。

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

<application

<provider
        android:name="android.support.v4.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>

评论

1赞 Aditya 11/30/2018
什么?@xml/provider_paths
1赞 Bhoomika Chauhan 4/16/2019
@Heisenberg请参考 Rahul Upadhyay 的帖子来自 url : stackoverflow.com/questions/38555301/...
4赞 nyanpasu64 9/17/2018 #11

我不知道为什么,我所做的一切都与 Pkosta (https://stackoverflow.com/a/38858040) 完全相同,但不断出现错误:

java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

我在这个问题上浪费了几个小时。罪魁祸首?科特林。

val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intent实际上是设置而不是在我新声明的 playIntent 上操作。getIntent().addFlags

-1赞 samus 9/26/2018 #12

Xamarin.Android的

注意:路径 xml/provider_paths.xml (.axml) 无法解析,即使在 Resources 下创建了 xml 文件夹(也许它可以放在 Values 等现有位置,没有尝试),所以我求助于这个现在有效的方法。测试表明,每次应用程序运行只需要调用一次(这是有道理的,因为它会更改主机 VM 的运行状态)。

注意:xml 需要大写,所以 Resources/Xml/provider_paths.xml

Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");                
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");                
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);
43赞 CrazyJ36 10/22/2018 #13

我的解决方案是将文件路径“Uri.parse”为字符串,而不是使用 Uri.fromFile()。

String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
    uri = Uri.fromFile(file);
} else {
    uri = Uri.parse(file.getPath()); // My work-around for SDKs up to 29.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

似乎 fromFile() 使用 A 文件指针,我认为当内存地址暴露给所有应用程序时,这可能是不安全的。但是文件路径 String 从不伤害任何人,因此它可以在不抛出 FileUriExposedException 的情况下工作。

在 API 级别 9 到 29 上进行了测试!成功打开文本文件以在另一个应用程序中进行编辑。根本不需要 FileProvider,也不需要 Android 支持库。这在 API 级别 30(Android 11) 或更高版本上不起作用,因为 getExternalStorageDirectory() 已被弃用。

评论

0赞 Dale 11/21/2018
我希望我先看到这个。我没有证明它对我有用,但它比 FileProvider 麻烦得多。
0赞 Xmister 1/24/2019
关于为什么这实际上有效的说明:导致问题的不是文件指针,而是仅当您的路径带有“file://”时才会发生异常,该路径会自动以 fromFile 开头,而不是 parse。
7赞 Serdar Samancıoğlu 3/14/2019
这不会出现异常,但它也无法将文件发送到相关应用。所以,对我不起作用。
2赞 CommonsWare 11/8/2019
这在 Android 10 及更高版本上将失败,因为您不能假设其他应用可以通过文件系统访问外部存储。
1赞 CommonsWare 12/22/2020
@AndroidGuy:用途及其方法。FileProvidergetUriForFile()
1赞 NeoWang 1/25/2019 #14

@Pkosta的答案是做到这一点的一种方式。

除了使用 ,您还可以将文件插入(尤其是图像和视频文件),因为每个应用程序都可以访问 MediaStore 中的文件:FileProviderMediaStore

MediaStore 主要针对视频、音频和图像 MIME 类型,但从 Android 3.0(API 级别 11)开始,它还可以存储非媒体类型(如需了解详情,请参阅 MediaStore.Files)。可以使用 scanFile() 将文件插入到 MediaStore 中,然后将适合共享的 content:// 样式 Uri 传递给提供的 onScanCompleted() 回调。请注意,一旦添加到系统 MediaStore,设备上的任何应用程序都可以访问该内容。

例如,您可以像这样将视频文件插入 MediaStore:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUri就像 ,可以直接传递给:content://media/external/video/media/183473Intent.putExtra

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

这对我有用,并省去了使用.FileProvider

7赞 DEVSHK 3/13/2019 #15

在 onCreate 中添加这两行

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

分享方式

File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));

评论

0赞 CommonsWare 11/8/2019
这在 Android 10 及更高版本上将失败,因为您不能假设其他应用可以通过文件系统访问外部存储。
1赞 Jitu Batiya 5/28/2019 #16

我放置此方法以便 imageuri 路径轻松获取内容。

enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
    String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), 
    inImage, "Title", null);
    return Uri.parse(path);
}
1赞 user11094471 5/29/2019 #17

我知道这是一个很老的问题,但这个答案是给未来的观众的。所以我遇到了类似的问题,经过研究,我找到了这种方法的替代方案。

您的意图在这里 例如:在 Kotlin 中从您的路径查看您的图像

 val intent = Intent()
 intent.setAction(Intent.ACTION_VIEW)
 val file = File(currentUri)
 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
 val contentURI = getContentUri(context!!, file.absolutePath)
 intent.setDataAndType(contentURI,"image/*")
 startActivity(intent)

主要功能如下

private fun getContentUri(context:Context, absPath:String):Uri? {
        val cursor = context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            arrayOf<String>(MediaStore.Images.Media._ID),
            MediaStore.Images.Media.DATA + "=? ",
            arrayOf<String>(absPath), null)
        if (cursor != null && cursor.moveToFirst())
        {
            val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
            return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
        }
        else if (!absPath.isEmpty())
        {
            val values = ContentValues()
            values.put(MediaStore.Images.Media.DATA, absPath)
            return context.getContentResolver().insert(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        }
        else
        {
            return null
        }
    }

同样,您可以使用任何其他文件格式(如 pdf)代替图像,就我而言,它工作得很好

11赞 Alex 7/11/2019 #18

这是我的解决方案:

清单 .xml

<application
            android:name=".main.MainApp"
            android:allowBackup="true"
            android:icon="@drawable/ic_app"
            android:label="@string/application_name"
            android:logo="@drawable/ic_app_logo"
            android:theme="@style/MainAppBaseTheme">

        <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>

res/xml/provider_paths.xml

   <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="external_files" path="."/>
    </paths>

在我的片段中,我有下一个代码:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

这就是你所需要的。

不需要创建

public class GenericFileProvider extends FileProvider {}

我在 Android 5.0、6.0 和 Android 9.0 上进行了测试,取得了成功。

评论

0赞 PeterPazmandi 10/16/2019
我已经测试了这个解决方案,它可以通过一个小的更改来工作: intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra(Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = “image/png” startActivity(Intent.createChooser(intent, “Share image via”)) 它在 Android 7 和 8 上运行。
4赞 Suraj Bahadur 1/18/2020 #19
As of Android N, in order to work around this issue, you need to use the FileProvider API

这里有 3 个主要步骤,如下所述

第 1 步:清单条目

<manifest ...>
    <application ...>
        <provider
            android:name="android.support.v4.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>

步骤 2:创建 XML 文件 res/xml/provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

第 3 步:代码更改

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);

评论

0赞 Alberto M 7/15/2021
除此之外,我还必须在provider_paths.xml内添加<root-path name="root" path="/" />
0赞 Suraj Bahadur 7/21/2021
@AlbertoM 这是可选的
0赞 Alberto M 7/21/2021
我知道,但这对我来说是不同的。其他事情没有让我完成它
0赞 Suraj Bahadur 7/21/2021
@AlbertoM 您能否提及您遇到的错误或与我分享示例代码,以便解决它。
0赞 gtxtreme 4/24/2022
没有它也不会发生在我身上<root-path>
4赞 lomza 6/3/2020 #20

我花了将近一天的时间试图弄清楚为什么我会得到这个异常。经过一番挣扎,这个配置运行良好(Kotlin):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths" />
</provider>

file_paths.xml

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

意图本身

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

我在这里解释整个过程。

2赞 SWIK 3/23/2021 #21

我刚刚完成了以下操作,如果 android 版本 > 24

File fl = new File(url);
    Uri uri = Uri.fromFile(fl);
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (android.os.Build.VERSION.SDK_INT>=24)
    {
        Context context = getApplicationContext();
        uri = FileProvider.getUriForFile(
                context,
                context.getApplicationContext()
                        .getPackageName() + ".provider", fl);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(uri, mimetype);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);

点击此链接,然后 https://medium.com/@ali.muzaffar/what-is-android-os-fileuriexposedexception-and-what-you-can-do-about-it-70b9eb17c6d0#.54odzsnk4

-1赞 Aalishan Ansari 4/9/2021 #22

这行得通

 val uri = if (Build.VERSION.SDK_INT < 24) Uri.fromFile(file) else Uri.parse(file.path)
                val shareIntent = Intent().apply {
                    action = Intent.ACTION_SEND
                    type = "application/pdf"
                    putExtra(Intent.EXTRA_STREAM, uri)
                    putExtra(
                        Intent.EXTRA_SUBJECT,
                        "Purchase Bill..."
                    )
                    putExtra(
                        Intent.EXTRA_TEXT,
                        "Sharing Bill purchase items..."
                    )
                }
                startActivity(Intent.createChooser(shareIntent, "Share Via"))
0赞 Shunan 7/17/2021 #23

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

它有点长,所以我在这里分享要点,但我建议你看一遍。

底线是你不能从应用的范围存储中共享任何内容。同样在 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/"/>