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


735

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


20
我觉得这是一个错误,使应用程序开发人员的生活变得不必要。每个应用程序都必须捆绑一个“ FileProvider”和“ authority”,这看起来像Enterprisey样板。必须向每个文件意图添加标志似乎很尴尬,并且可能不必要。打破优雅的“道路”概念是不愉快的。那有什么好处呢?选择性地授予应用程序存储访问权限(而大多数应用程序具有完整的sdcard访问权限,尤其是那些对文件有效的应用程序)?
nyanpasu64

2
试试这个,小的和完美的代码stackoverflow.com/a/52695444/4997704
Binesh Kumar,

Answers:


1314

如果是targetSdkVersion >= 24,则我们必须使用FileProvider类来授予对特定文件或文件夹的访问权限,以使其他应用程序可以访问它们。我们创造我们自己的类继承FileProvider,以确保我们的FileProvider并不冲突与进口的依赖声明FileProviders描述这里

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

  • 添加一个扩展类 FileProvider

    public class GenericFileProvider extends FileProvider {}
  • 添加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=".GenericFileProvider"
            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>
  • 然后provider_paths.xmlres/xml文件夹中创建一个文件。如果文件夹不存在,则可能需要创建该文件夹。该文件的内容如下所示。它描述了我们要共享对外部存储在根文件夹中的访问(path="."),其名称为external_files
<?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 photoURI = Uri.fromFile(createImageFile());

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
  • 编辑:如果要使用意图使系统打开文件,则可能需要添加以下代码行:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

请参考,完整的代码和解决方案已在此处进行了说明


62
我只需要添加intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
alorma '16

24
它适用于所有Android版本,还是仅适用于API 24?
Android开发人员


11
@rockhammer我刚刚在Android 5.0、6.0、7.1和8.1上对此进行了测试,它可以在所有情况下正常工作。因此(Build.VERSION.SDK_INT > M)条件是无用的。
塞巴斯蒂安

66
FileProvider仅当您要覆盖任何默认行为时才应扩展该属性,否则请使用android:name="android.support.v4.content.FileProvider"。见developer.android.com/reference/android/support/v4/content/...
日本文

312

除了使用的解决方案外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是诊断工具,不应用于此问题。当我一年前发布此答案时,许多应用程序只能收到File uris。当我尝试向他们发送FileProvider uri时,它们崩溃了。现在,这在大多数应用程序中已得到修复,因此我们应该使用FileProvider解决方案。


1
@LaurynasG从API 18到23,默认情况下android不检查文件uri暴露情况。调用此方法将启用此检查。从API 24开始,android默认情况下会执行此检查。但是我们可以通过设置一个新的来禁用它VmPolicy
hqzxzwb

要执行此操作,还需要其他步骤吗?不能代表我的运行Android 7.0的MotoG。
CKP78 '17

3
但是,这如何解决此问题,StrictMode是一种诊断工具,应在开发人员模式而不是发布模式下启用?
Imene Noomene '18

1
@ImeneNoomene实际上,我们在这里禁用StrictMode。似乎不应该在发布模式下启用StrictMode,这是合理的,但实际上,无论调试模式还是发布模式,Android默认都会启用某些StrictMode选项。但是,以某种方式,这个答案仅是当某些目标应用未准备好接收内容uri时的一种变通方法。现在,大多数应用程序都增加了对内容uris的支持,我们应该使用FileProvider模式。
hqzxzwb

3
@ImeneNoomene在你的愤怒中我完全和你在一起。您是对的,这是一种诊断工具,或者至少是在我将其添加到项目中之前。这真令人沮丧!StrictMode.enableDefaults();,我只能在开发版本上运行,因此可以避免崩溃的发生-因此,我现在拥有一个生产应用程序,该应用程序崩溃了,但是在开发过程中却没有崩溃。因此,基本上,在此处启用诊断工具会隐藏一个严重的问题。感谢@hqzxzwb帮助我揭开了神秘面纱。
乔恩

174

如果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
        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并Intent确保添加以下行时:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

而且你很好。希望能帮助到你。


2
@MaksimKniazev您能简要描述一下您的错误吗?这样我可以帮助您。
Pankaj Lilan

1
@PankajLilan,我完全按照你的意思做了。但是,每次我在其他应用程序中打开pdf时,它就会显示为空白(正确保存)。我需要编辑xml吗?我已经添加了FLAG_GRANT_READ_URI_PERMISSION;
菲利佩·卡斯蒂略斯

1
我的错误是我在错误意图中添加了许可。这是最好,最简单的正确答案。谢谢!
菲利佩·卡斯蒂略斯

2
它引发异常java.lang.IllegalArgumentException:无法找到包含/我的文件路径为/storage/emulated/0/GSMManage_DCIM/Documents/Device7298file_74.pdf的配置的根。你能帮忙吗?
Jagdish Bhavsar

1
不知道为什么,但我不得不添加读取和写入权限:target.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)target.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)

160

如果您的应用程序针对API 24+,并且您仍然希望/需要使用file://意图,则可以使用黑客方式禁用运行时检查:

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.
*/

问题是我的应用程序不是la脚,而是不想被content:// intents所破坏,那里的许多应用程序都不了解。例如,使用content:// scheme打开mp3文件所提供的应用程序要比通过file:// scheme打开相同的应用程序少得多。我不想通过限制我的应用程序的功能来弥补Google的设计缺陷。

Google希望开发人员使用内容方案,但系统对此不作准备,多年来,使应用程序使用文件而非“内容”,可以编辑和保存文件,而不能通过内容方案提供文件(可以他们?)。


3
“虽然通过内容方案提供的文件不能(可以吗?)。” -当然,如果您具有对内容的写权限。ContentResolver同时具有openInputStream()openOutputStream()。一种不太灵活的方法是自己配置VM规则,而不启用file Uri规则。
CommonsWare,2017年

1
究竟。当您构建整个应用程序时,这很艰巨,然后在确定目标25种相机方法后找出来。这对我有效,直到我有时间做正确的事情为止。
马特W

5
可以在Android 7上运行。谢谢
Anton Kizema '17

4
同样适用于Android 8,并在华为Nexus 6P上进行了测试。
Gonzalo Ledezma Torres

4
我确认这正在生产中(我有超过500,000个用户),当前版本8.1是最高版本,并且可以使用。
伊莱(Eli)

90

如果您的targetSdkVersion年龄为24岁或更高,则不能在Android 7.0+设备上使用中的file: UriIntents

您的选择是:

  1. 放下你targetSdkVersion到23或更低,或

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

例如:

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

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

(来自此示例项目


感谢您的回答。当我对/system分区上的文件使用它时会发生什么?每个应用程序都应该能够不带root访问该分区。
Thomas Vos

2
@SuperThomasLab:我不会指望所有东西都能/system被世界认可。话虽如此,我的猜测是您仍然会收到此异常。我怀疑他们只是在检查方案,而不是在试图确定文件是否真正可读取。但是,FileProvider这对您无济于事,因为您无法教它从提供服务/system。您可以为我创建自定义策略StreamProvider,也可以自己制定策略来ContentProvider解决问题。
CommonsWare,2016年

仍在思考如何解决此问题。.我正在使用Android N支持更新的应用程序是根浏览器。但是现在您无法再在根目录中打开任何文件。(/data/system),因为这种“变好”的。
托马斯·沃斯

1
将targetSdkVersion降至23的最主要缺点是什么?thnx
rommex

2
@rommex:我不知道什么是“最重要的”。例如,在分屏模式下或在自由格式的多窗口设备(Chromebook,Samsung DeX)上工作的用户将被告知您的应用程序可能无法在多窗口下使用。这是否重要取决于您。
CommonsWare

47

首先,您需要在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卡的根文件夹。我已经测试过此代码,并且可以正常工作。


1
这次真是万分感谢。我也想告诉您,有一种更好的方法来获取文件扩展名。String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); 另外,我建议任何寻求答案的人首先阅读FileProvider,并在Android N及更高版本中使用文件权限了解您在这里处理的内容。有内部存储与外部存储的选项,还有常规文件路径与缓存路径的选项。
praneetloke '16

2
我收到以下异常:java.lang.IllegalArgumentException: Failed to find configured root ...唯一起作用的是<files-path path="." name="files_root" />xml文件而不是<external-path ...。我的文件已保存在内部存储器中。
steliosf

26

@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
您在哪里找到的<root-path?工作正常 <external-path path="Android/data/${applicationId}/" name="files_root" />对外部存储中的打开文件无效。
t0m

我从各种搜索结果中找到了这个,让我再次检查一下,然后尽快回复
Ramz

您提到的外部存储也是SD卡还是内置存储?
拉姆兹,

抱歉,不准确。我的意思是Android/data/${applicationId}/在SDcard中。
t0m

1
需要将此添加到意图中:intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
s-hunter

26

我的解决方案是将文件路径“ 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 new SDKs, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

似乎fromFile()使用一个文件指针,当内存地址公开给所有应用程序时,我认为这可能是不安全的。但是文件路径字符串从不伤害任何人,因此它可以工作而不会抛出FileUriExposedException。

经过API 9至27级测试!成功打开文本文件以在另一个应用程序中进行编辑。完全不需要FileProvider,也不需要Android支持库。


我希望我首先见过。我没有证明它对我有用,但是它比FileProvider麻烦得多。
戴尔

关于此方法真正起作用的注释:不是引起问题的文件指针,而是只有当您具有带有'file://'的路径时才会发生例外,该路径自动带有fromFile,但不带有parse 。
Xmister

3
这不会发生异常,但也无法将文件发送到相关的应用程序。因此,对我不起作用。
SerdarSamancıoğlu,

1
在Android 10及更高版本上,此操作将失败,因为您不能假定其他应用程序可以通过文件系统访问外部存储。
CommonsWare

24

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

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

它将忽略URI暴露


1
这是解决方案之一,但不是标准解决方案。拒绝答案的Stil人是错误的,因为这也是有效解决方案的有效代码。
saksham '18

23

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

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

它将忽略URI公开。

快乐的编码:-)


1
这有什么缺点?
James F

1
在Android 10及更高版本上,此操作将失败,因为您不能假定其他应用程序可以通过文件系统访问外部存储。
CommonsWare

18

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

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

更换:

startActivity(intent);

通过

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

7
Google将很快修补选择器,使其包含相同的支票。这不是解决方案。
指针为空,

这一个有效,但在未来的android版本中将不可用。
Diljeet

13

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

11

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

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

它将忽略URI暴露


这样,这将删除strictmode策略。并将忽略安全警告。不是一个好的解决方案。
inspire_coding

在Android 10及更高版本上,该操作也会失败,因为您无法假设其他应用可以通过文件系统访问外部存储。
CommonsWare

7

在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及更高版本上,此操作将失败,因为您不能假定其他应用程序可以通过文件系统访问外部存储。
CommonsWare

7

这是我的解决方案:

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

Тhat就是您所需要的。

没有必要创建

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上运行
良好。– inspire_coding

4

要从服务器下载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
是什么@xml/provider_paths
adityasnl

1
@Heisenberg请从网址参考拉胡尔Upadhyay后:stackoverflow.com/questions/38555301/...
Bhoomika肖汉

3

我不知道为什么,我所做的一切都与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上进行操作。


2

我把这种方法,所以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);
}

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

1

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

您在这里的意图,例如:从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),在我看来,它都可以正常工作


-1

https://stackoverflow.com/a/38858040/395097这个答案是完整的。

这个答案是针对的-您已经有一个目标是24以下的应用,现在您要升级到targetSDKVersion> = 24。

在Android N中,仅更改了公开给第三方应用程序的文件uri。(不是我们以前使用它的方式)。因此,仅更改与第三方应用共享路径的位置(在我的情况下为相机)

在我们的应用程序中,我们将uri发送到“相机”应用程序,在该位置,我们希望“相机”应用程序存储捕获的图像。

  1. 对于android N,我们生成新的基于Content:// uri的指向文件的url。
  2. 我们为相同的文件生成常用的基于File api的路径(使用旧方法)。

现在,对于同一文件,我们有2个不同的uri。#1与“相机”应用共享。如果相机意图成功,我们可以从#2访问图像。

希望这可以帮助。


1
您引用的答案已经在此处发布,如果需要完成,请在答案plz中进行注释。
IgniteCoders

1
@IgniteCoders正如我在消息中明确提到的那样,我的答案涵盖了相关的用例。
阿兰(Aram)

-1

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

-1

@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


-1

只需让它忽略URI暴露...创建后添加

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

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

不必在生产应用程序中使用。
Jorgesys '19

-1

试试这个解决方案

将这些权限放到清单中

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

谁能告诉我为什么投反对票。这是100%可行的解决方案。
阿卜杜勒·巴斯特·里希

这只会使您获得缩略图,而不是整个图片。
Build3r

-2

就我而言,我SetDataAndType用just 替换掉了异常SetData

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.