首页 文章

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

提问于
浏览
520

当我尝试打开文件时,应用程序崩溃了 . 它可以在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()暴露在app之外

Edit:

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

17 回答

  • -1

    https://stackoverflow.com/a/38858040/395097这个答案已经完成 .

    这个答案适用于 - 您已经有一个目标低于24的应用程序,现在您正在升级到targetSDKVersion> = 24 .

    在Android N中,仅更改了暴露给第三方应用程序的文件uri . (不是我们之前使用它的方式) . 所以只改变你与第三方应用程序共享路径的地方(在我的情况下为Camera)

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

    • 对于android N,我们生成新的Content://基于uri的url指向文件 .

    • 我们生成相同的基于File api的路径(使用旧方法) .

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

    希望这可以帮助 .

  • 78

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

    replace

    Uri uri = Uri.fromFile(fileImagePath);
    

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

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

  • -2

    首先,您需要为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>
    

    此示例适用于外部路径,您可以参考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();
        }
    

    EDIT :我在file_paths中添加了sd卡的根文件夹 . 我已经测试了这段代码,但确实有效 .

  • 24

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

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

    Application.onCreate() . 这样,VM会忽略文件 URI 曝光 .

    Method

    builder.detectFileUriExposure()
    

    启用文件曝光检查,如果我们不设置VmPolicy,这也是默认行为 .

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

    Update:

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

  • 968

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

    你的选择是:

    • targetSdkVersion 降至23或更低,或

    • 将您的内容放在内部存储上,然后use 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

  • 2

    在我的情况下,我通过用 SetData 替换 SetDataAndType 来摆脱异常 .

  • -1

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

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

    测试API级别9到26!不需要FileProvider,也不需要Android支持库 .

  • 0

    如果您的应用程序针对API 24,并且您仍然需要/需要使用file:// intents,则可以使用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:// scheme打开mp3文件提供的应用程序比在file:// scheme上打开相同的应用程序要少得多 . 我不想通过限制我的应用程序的功能来支付Google的设计错误 .

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

  • 89

    使用fileProvider是可行的方法 . 但您可以使用这个简单的解决方法:

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

    更换:

    startActivity(intent);
    

    通过

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

    只需在活动onCreate()中粘贴以下代码即可

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

    它将忽略URI暴露

  • 251

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

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

    • 添加一个扩展 FileProvider 的类
    public class GenericFileProvider extends FileProvider {}
    
    • <application> 标签下的 AndroidManifest.xml 中添加FileProvider <provider> 标签 . 为 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}.my.package.name.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 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() + ".my.package.name.provider", createImageFile());
    
    • Edit: 如果您正在使用意图使系统打开您的文件,您可能需要添加以下代码行:
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

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

  • 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 实际上是在设置 getIntent().addFlags 而不是在我新发布的playIntent上运行 .

  • 17

    只需在活动onCreate()中粘贴以下代码即可

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

    它将忽略URI暴露

    快乐的编码:-)

  • 1

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

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

  • 5

    Xamarin.Android

    Note: 路径xml / provider_paths.xml(.axml)无法解析,即使在资源下创建了xml文件夹(也许它可以放在像Values这样的现有位置,没试过),所以我求助于此这适用于现在 . 测试显示每个应用程序运行只需要调用一次(这有意义的是它改变了主机VM的运行状态) .

    Note: 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);
    
  • 128

    我使用了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);
    
  • 16

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

相关问题