有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

安卓操作系统。FileUriExposedException:file:///storage/emulated/0/test.txt通过意图暴露在应用程序之外。getData()

当我试图打开文件时,应用程序正在崩溃。它在安卓牛轧糖下工作,但在安卓牛轧糖上崩溃。只有当我试图从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

日志:

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

编辑:

针对Android Nougat时,不再允许使用^uri。我们应该改用content://uri。但是,我的应用程序需要打开根目录中的文件。有什么想法吗


共 (6) 个答案

  1. # 1 楼答案

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

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

    • <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="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\u 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.

  2. # 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,可以在突出显示文件路径后点击Alt+Enter并选择创建xml资源选项)

    在文件路径文件中的下一步,输入

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

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

  3. # 3 楼答案

    如果您的应用程序以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://意图而瘫痪,而这一点在很多应用程序中是不理解的。例如,使用content://scheme打开mp3文件提供的应用程序比使用file://scheme打开mp3文件提供的应用程序少得多。我不想通过限制我的应用程序的功能来为谷歌的设计缺陷买单

    谷歌希望开发者使用content scheme,但系统并没有为此做好准备,多年来,应用程序使用的是文件而不是“内容”,文件可以编辑和保存,而通过content scheme提供的文件则不能(可以吗?)

  4. # 4 楼答案

    如果您的targetSdkVersion为24或更高,you can not use ^{} ^{} values in ^{} on Android 7.0+ devices

    你的选择是:

    1. targetSdkVersion降到23或更低,或

    2. 将您的内容放入内部存储,然后use ^{}有选择地将其提供给其他应用程序

    例如:

    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

  5. # 5 楼答案

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

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

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

    方法

    builder.detectFileUriExposure()
    

    启用文件暴露检查,这也是未设置VmPolicy时的默认行为

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

    更新:

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

  6. # 6 楼答案

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

    创建一个xml文件(路径:res\xml)提供程序路径。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,则文件提供程序路径应为:

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

    你可以走了。希望能有帮助