起因

我们的截图应用在Android O 上使用分享功能的时候crash了,错误关键词:FileUriExposedException。 Google 一下发现这个问题从Android N 开始出现的,当你给使用 file:/// Uri 分享文件的时候会抛出这个异常。 但是奇怪的是我们在Android N 上使用分享功能的时候并没有出现问题,不管怎样有问题就要解决。 一句话概括,我们要做的就是使用 content:// Uri 代替 file:/// Uri

解决过程

报错代码:

    public void shareScreenshot() {
        final File imageFile = new File(mScreenshotPath);
        // Create a shareScreenshot intent
        Intent sharingIntent = new Intent(Intent.ACTION_SEND);
        sharingIntent.setType("image/png");
        sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
        startActivity(Intent.createChooser(sharingIntent, getResources().getText(R.string.share)));
    }

当我们使用Uri.fromFile(imageFile)的时候获得了 file:/// Uri,就是使用这个 file:/// Uri 的时候报的错。 下面我们分几步把file:/// Uri 替换成 content:// Uri

  1. 指定FileProvider 在AndroidManifest.xml 中定义FileProvider。 关注两个值,android:authoritiesandroid:resource,前者是包名跟.fileprovider, 后者是定义你想要访问的目录的文件,下面会细说。
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp">
        <application
            ...>
            <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="com.ckt.screenshot.provider"
                android:grantUriPermissions="true"
                android:exported="false">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/provider_paths"/>
            </provider>
            ...
        </application>
    </manifest>
    
  2. 指定可共享的目录 在res/xml/创建和上面*android:resource *对应的文件provider_paths.xml。

    <paths>
        <files-path path="images/" name="myimages" /> <!--仅作举例-->
        <external-path name="external_files" path="Pictures/Screenshots/"/>
    </paths>
    

    在这个例子中files-path 标签代表了应用内部存储files/子目录下的文件, external-path 标签代表外置存储根目录。其他标签请查看FileProvider 介绍。

    • path 代表的是子目录。它是标签代表的目录的子目录。比如上例中的两个目录,第一个是应用内部存储files/下的images/ 子目录,另一个是存储卡根目录下的Pictures/Screenshots/ 子目录。 注意: 不能用指定文件名的方式分享特定文件,也不能用通配符的方式指定一些文件。
    • name 是content Uri 中的一段,它可以说是path的别名,为了加强安全性,用name的值代替实际的path 路径添加到content Uri中;
  3. 修改java 代码 使用FileProvider.getUriForFile 获得content Uri。 ```java
    • Uri imageUri = FileProvider.getUriForFile(mContext,
    • BuildConfig.APPLICATION_ID + “.provider”,
    • imageFile); // Create a shareScreenshot intent Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType(“image/png”);
    • sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
    • sharingIntent.putExtra(Intent.EXTRA_STREAM, imageUri); ``` 实际文件路径 imageFile /storage/emulated/0/Pictures/Screenshots/Screenshot_20180224-104038.png 而我们获得 imageUri content://com.ckt.screenshot.provider/external_files/Screenshot_20180224-104038.png

    结合provider_paths.xml 对比可以看出Pictures/Screenshots(path 实际路径)替换成了external_files(name 别名)。

不使用Uri.fromFile() 的几点理由

  • Does not allow file sharing across profiles.
  • 要求分享文件的应用有WRITE_EXTERNAL_STORAGE 的权限(Android 4.4 及以下)
  • 要求接收文件的应用有 READ_EXTERNAL_STORAGE的权限,分享文件给没有这些权限的应用会失败,比如Gmail就没有这个权限。 最重要的一点,在Android N及以上会出现Crash。

参考连接

  1. FileUriExposedException
  2. FileProvider
  3. Setting Up File Sharing
  4. android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
  5. 解决 Android N 7.0 上 报错:android.os.FileUriExposedException