ChatGPT解决这个技术问题 Extra ChatGPT

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。但是,我的应用程序需要在根目录中打开文件。有任何想法吗?

我觉得这是一个错误,让应用程序开发人员的生活变得不必要地困难。必须将“FileProvider”和“authority”与每个应用程序捆绑在一起,这似乎是 Enterprisey 样板。必须为每个文件意图添加一个标志似乎很尴尬并且可能没有必要。打破“路径”的优雅概念是不愉快的。还有什么好处?选择性地授予应用程序的存储访问权限(而大多数应用程序都具有完整的 sdcard 访问权限,尤其是那些处理文件的应用程序)?
试试这个,小而完美的代码stackoverflow.com/a/52695444/4997704
@nyanpasu64 我同意。自 API 19 以来,Google 已经开始通过一些变化来羞辱自己
恕我直言,我认为谷歌基本上想知道你保存文件的位置。从清单中读取路径。所以,他们可以自动处理那条路径......

l
logankilpatrick

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

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

标签下的 AndroidManifest.xml 中添加 FileProvider 标签。为 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);

请参考已解释的完整代码和解决方案here.


我只需要添加 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
它适用于所有 Android 版本,还是仅适用于 API 24?
这篇文章帮助了我medium.com/@ali.muzaffar/…
@rockhammer 我刚刚在 Android 5.0、6.0、7.1 和 8.1 上对此进行了测试,它适用于所有情况。所以 (Build.VERSION.SDK_INT > M) 条件是无用的。
仅当您想要覆盖任何默认行为时才应扩展 FileProvider,否则使用 android:name="android.support.v4.content.FileProvider"。请参阅developer.android.com/reference/android/support/v4/content/…
h
hqzxzwb

除了使用 FileProvider 的解决方案之外,还有另一种解决方法。简单的说

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

Application.onCreate()。这样,VM 会忽略文件 URI 的暴露。

方法

builder.detectFileUriExposure()

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

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

更新:

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


@LaurynasG 从 API 18 到 23,android 默认不检查文件 uri 暴露。调用此方法启用此检查。从 API 24 开始,android 默认会执行此检查。但是我们可以通过设置一个新的 VmPolicy 来禁用它。
是否需要任何其他步骤才能正常工作?不适用于我运行 Android 7.0 的 Moto G。
然而,这如何解决这个问题,StrictMode 是一个诊断工具,应该在开发者模式而不是发布模式下启用???
@ImeneNoomene 实际上我们在这里禁用了 StrictMode。在发布模式下不应该启用 StrictMode 似乎是合理的,但实际上 Android 无论调试模式还是发布模式都默认启用了一些 StrictMode 选项。但无论如何,当某些目标应用程序没有准备好接收内容 uri 时,这个答案只是一种解决方法。现在大多数应用程序都添加了对内容 uris 的支持,我们应该使用 FileProvider 模式。
@ImeneNoomene 我完全同意你的愤怒。你是对的,这是一个诊断工具,或者至少在很久以前我将它添加到我的项目中时。这太令人沮丧了! StrictMode.enableDefaults();,我只在我的开发版本上运行它可以防止发生这种崩溃 - 所以我现在有一个崩溃的生产应用程序,但它在开发时不会崩溃。所以基本上,在这里启用诊断工具隐藏了一个严重的问题。感谢@hqzxzwb 帮助我揭开这个谜团。
M
Miguel Beltran

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

创建一个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

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

编辑:当您使用 Intent 包含 URI 时,请确保添加以下行:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

你很高兴。希望能帮助到你。


@MaksimKniazev 您能简要描述一下您的错误吗?这样我才能帮助你。
@PankajLilan,我完全按照你说的做了。但是每次我在另一个应用程序中打开我的 pdf 时,它都会显示为空白(正确保存)。我需要编辑xml吗?我也已经添加了 FLAG_GRANT_READ_URI_PERMISSION;
我的错误,我将权限添加到错误的意图中。这是最好和最简单的正确答案。谢谢!
它抛出异常 java.lang.IllegalArgumentException: Failed to find configured root that contains / My file path is /storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf。你能帮忙吗?
这就是答案!其他的都是错的。这是新鲜的并且适用于新代码!
P
Pointer Null

如果您的应用程序针对 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:// 方案打开相同的应用程序提供的应用程序要少得多。我不想通过限制我的应用程序的功能来为谷歌的设计缺陷买单。

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


“虽然通过内容方案提供的文件不能(可以吗?)。” -- 当然,如果您对内容有写入权限。 ContentResolver 同时具有 openInputStream()openOutputStream()。这样做的一个不太麻烦的方法是只使用 configure the VM rules yourself,而不启用 file Uri 规则。
确切地。当你构建你的整个应用程序时,这是一项艰苦的工作,然后在瞄准 25 次后找出你所有的相机方法都坏了。这对我有用,直到我有时间以正确的方式去做。
适用于 Android 7。谢谢
也适用于 Android 8,在华为 Nexus 6P 上测试。
我确认这正在生产中(我有超过 500,000 个用户),目前版本 8.1 是最高版本,并且可以在上面运行。
C
CommonsWare

如果您的 targetSdkVersion 是 24 或更高,you can not use file: Uri values in Intents on Android 7.0+ devices

您的选择是:

将您的 targetSdkVersion 降至 23 或更低,或者将您的内容放在内部存储上,然后使用 FileProvider 使其有选择地可供其他应用程序使用

例如:

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

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

(来自 this sample project


感谢你的回答。当我将它与 /system 分区上的文件一起使用时会发生什么?每个应用程序都应该能够在没有 root 的情况下访问此分区。
@SuperThomasLab:我不会指望 /system 中的所有内容都是世界可读的。话虽如此,我的猜测是你仍然会得到这个例外。我怀疑他们只是在检查方案,而不是试图确定文件是否真的是世界可读的。但是,FileProvider 不会帮助您,因为您无法教它从 /system 发球。您可以 create a custom strategy for my StreamProvider 或推出自己的 ContentProvider 来解决问题。
仍在思考我将如何解决这个问题。我正在更新并支持 Android N 的应用程序是一个根浏览器。但是现在您不能再在根目录中打开任何文件了。 (/data, /system),因为这个“好变化”。
将 targetSdkVersion 降到 23 最重要的缺点是什么?谢谢
@rommex:我不知道什么是“最重要的”。例如,在分屏模式或自由形式的多窗口设备(Chromebook、Samsung DeX)上工作的用户会被告知您的应用程序可能无法在多窗口下工作。这是否重要取决于您。
K
Karn Patel

首先,您需要向您的 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 并选择创建 xml 资源选项后按 Alt + Enter)

接下来在 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>

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

现在剩下的就是按如下方式创建意图:

    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 卡的根文件夹。我已经测试了这段代码,它确实有效。


这次真是万分感谢。我还想让你知道有更好的方法来获取文件扩展名。 String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); 另外,我建议任何寻找答案的人先通读 FileProvider 并了解您在此处处理的 Android N 及更高版本中的文件权限问题。有内部存储与外部存储以及常规文件路径与缓存路径的选项。
我收到以下异常:java.lang.IllegalArgumentException: Failed to find configured root ...,唯一有效的是 xml 文件上的 <files-path path="." name="files_root" /> 而不是 <external-path ...。我的文件保存在内部存储器中。
不要在路径中写你的包名,如果需要访问你的所有根文件夹而不仅仅是私有文件夹,请使用类似这样的东西
C
CrazyJ36

我的解决方案是将文件路径“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() 使用文件指针,我想当内存地址暴露给所有应用程序时,它可能是不安全的。但是文件路径字符串永远不会伤害任何人,因此它可以在不抛出 FileUriExposedException 的情况下工作。

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


我希望我第一次看到这个。我没有证明它对我有用,但它比 FileProvider 简单得多。
关于为什么这实际上有效的注释:导致问题的不是文件指针,而是只有当您有一个带有“file://”的路径时才会发生异常,该路径会自动添加 fromFile,但不会使用 parse .
这不会出现异常,但它也无法将文件发送到相关应用程序。所以,对我不起作用。
这将在 Android 10 及更高版本上失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。
@AndroidGuy:使用 FileProvider 及其 getUriForFile() 方法。
V
Vini.g.fer

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

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


请问您在哪里找到有关 <root-path 的信息?它正在工作。 <external-path path="Android/data/${applicationId}/" name="files_root" /> 对从外部存储打开的文件没有影响。
我从各种搜索结果中找到这个,让我再次检查并尽快回复你
您提到的外部存储也是sd卡还是内置存储?
对不起,不准确。我的意思是 SD 卡中的 Android/data/${applicationId}/
需要将此添加到意图中:intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
M
Math

只需将以下代码粘贴到活动 onCreate() 中。

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

它将忽略 URI 暴露。

快乐编码:-)


这有什么缺点?
这将在 Android 10 及更高版本上失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。
为什么有几个人重复 this answer 的 @KaushalSachan?
h
hata

只需将以下代码粘贴到 Activity onCreate() 中:

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

它将忽略 URI 暴露。


这是解决方案之一,但不是标准解决方案。仍然对答案投反对票的人是错误的,因为这也是工作解决方案的工作代码。
这将在 Android 10 及更高版本上失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。
M
Maksim Ostrovidov

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

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

代替:

startActivity(intent);

经过

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

选择器将很快被谷歌修补以包含相同的检查。这不是解决方案。
这一个有效,但在未来的 android 版本中无效。
M
Max

我使用了上面给出的 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);

h
hata

只需将以下代码粘贴到 Activity onCreate() 中:

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

它将忽略 URI 暴露。


这将删除严格模式策略。并将忽略安全警告。不是一个好的解决方案。
它也将在 Android 10 及更高版本上失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。
为什么有几个人重复 this answer 的 @KaushalSachan?
A
Alex

这是我的解决方案:

在 Manifest.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 上进行了测试,并且成功了。


我已经测试过这个解决方案,只需稍加改动即可正常工作:intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.putExtra(Intent.EXTRA_STREAM, myPhotoFileUri) intent.type = "image/png" startActivity(Intent.createChooser(intent, "通过“共享图像”))它可以在 Android 7 和 8 上正常工作。
h
hata

在 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..."));

这将在 Android 10 及更高版本上失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。
B
Bhoomika Chauhan

要从服务器下载 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>

@xml/provider_paths 是什么?
@Heisenberg 请参考来自 url 的 Rahul Upadhyay 帖子:stackoverflow.com/questions/38555301/…
n
nyanpasu64

我不知道为什么,我所做的一切都与 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 实际上是在设置 getIntent().addFlags 而不是对我新声明的 playIntent 进行操作。


S
Suraj Bahadur
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);

除此之外,我必须在 provider_paths.xml 中添加 <root-path name="root" path="/" />
@AlbertoM 这是可选的
我知道,但这对我来说很重要。其他事情并没有让我经历它
@AlbertoM 您能否提及您遇到了什么错误或与我分享示例代码以便解决它。
没有<root-path>,我也不会这样做
l
lomza

我花了将近一天的时间试图弄清楚为什么我会得到这个异常。经过一番努力,这个配置完美运行(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>

文件路径.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
}

我解释了整个过程here


S
SWIK

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


N
NeoWang

@Pkosta 的回答是这样做的一种方式。

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

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/183473,可以直接传递给 Intent.putExtra

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

这对我有用,并且省去了使用 FileProvider 的麻烦。


J
Jitu Batiya

我使用了这种方法,因此 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);
}

佚名

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

您在此处的意图例如:从您在 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)代替图像,在我的情况下,它工作得很好


S
Shunan

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

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

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

s
samus

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

A
Abdul Basit Rishi

试试这个解决方案

将这些权限放在清单中

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

意图捕捉图像

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

在活动结果中获取捕获的图像

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

获取图像 URI 的方法

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }

这只会为您提供缩略图而不是完整图片。
A
Aalishan Ansari

这有效

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

h
hata

只需让它忽略 URI Exposure... 在创建后添加它

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

这将在 Android 10 及更高版本上失败,因为您不能假设其他应用程序可以通过文件系统访问外部存储。
这不能在生产应用程序中使用。
@CommonsWare,但这不应该起作用..我只在华为上得到了fileuriexposedexception,但是添加这个奇怪地解决了它,而其他任何方法都没有!
为什么有几个人重复 this answer 的 @KaushalSachan?

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅