android 7.0 手机调用手机相机、相册常见的两个崩溃:FileUriExposedException、SecurityException

今天在写项目的时候,遇到两个与手机相机、相册相关的崩溃crash,现总结如下,以后少跳坑。

  • android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()
  • java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri

一、android.os.FileUriExposedException: file:///storage/emulated/0/test.txt exposed beyond app through Intent.getData()

Android7.0 调用相机时出现错误:

1)问题详情:

在这里插入图片描述

2)问题分析:

为了提高私有目录的安全性,防止应用信息的泄漏,从 Android 7.0 开始,应用私有目录的访问权限被做限制。具体表现为,开发人员不能够再简单地通过 file:// URI 访问其他应用的私有目录文件或者让其他应用访问自己的私有目录文件。对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。

3)解决方案:

两种思路:

1、在 onCreate 方法中添加如下代码:(不推荐)

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

2、使用FileProvider(官方建议)

FileProvider官网

下面是我写的一个对 FileProvider 类的封装:

package com.example.zhangruirui.utils;

import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;

import java.io.File;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.FileProvider;

/**
 * Utility class 配合 {@link android.support.v4.content.FileProvider} 使用的工具类
 */
@SuppressWarnings({"unused", "WeakerAccess"})
public final class FileProviders {

  /**
   * 要在应用间共享文件,应发送一项 content:// URI,并授予 URI 临时访问权限。
   * 进行此授权的最简单方式是使用 FileProvider 类,该字符串即在 manifest 文件中声明的 authority
   */
  private static final String FILE_PROVIDER_AUTHORITY = "com.xxxxx.fileprovider";

  /**
   * 获取某个文件相应的 Content URI. 并且授权访问:读写
   *
   * @see #getUriForReadFile(Context, File, Intent) 读
   * @see #getUriForWriteFile(Context, File, Intent) 写
   */
  public static Uri getUriForFile(@NonNull Context context, @NonNull File file,
      @Nullable Intent intent) {
    return getUriForFile(context, file, intent,
        FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
  }

  /**
   * 获取某个文件相应的 Content URI. 并且授权访问:只读
   *
   * @see #getUriForWriteFile(Context, File, Intent) 写
   * @see #getUriForFile(Context, File, Intent) 读写
   */
  public static Uri getUriForReadFile(@NonNull Context context, @NonNull File file,
      @Nullable Intent intent) {
    return getUriForFile(context, file, intent, FLAG_GRANT_READ_URI_PERMISSION);
  }

  /**
   * 获取某个文件相应的 Content URI. 并且授权访问:写
   *
   * @see #getUriForFile(Context, File, Intent) 读写
   * @see #getUriForReadFile(Context, File, Intent) 读
   */
  public static Uri getUriForWriteFile(@NonNull Context context, @NonNull File file,
      @Nullable Intent intent) {
    return getUriForFile(context, file, intent, FLAG_GRANT_WRITE_URI_PERMISSION);
  }

  /**
   * 检查当前的进程和用户 是否有某个 Content Uri 的访问权限:读写
   *
   * @see #checkUriReadPermission(Context, Uri) 读
   * @see #checkUriWritePermission(Context, Uri) 写
   */
  public static boolean checkUriPermission(@NonNull Context context, @NonNull Uri uri) {
    return checkUriPermission(context, uri,
        FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
  }

  /**
   * 检查当前的进程和用户 是否有某个 Content Uri 的访问权限:读
   *
   * @see #checkUriPermission(Context, Uri) 读写
   * @see #checkUriWritePermission(Context, Uri) 写
   */
  public static boolean checkUriReadPermission(@NonNull Context context, @NonNull Uri uri) {
    return checkUriPermission(context, uri, FLAG_GRANT_READ_URI_PERMISSION);
  }

  /**
   * 检查当前的进程和用户 是否有某个 Content Uri 的访问权限:写
   *
   * @see #checkUriPermission(Context, Uri) 读写
   * @see #checkUriReadPermission(Context, Uri) 读
   */
  public static boolean checkUriWritePermission(@NonNull Context context, @NonNull Uri uri) {
    return checkUriPermission(context, uri, FLAG_GRANT_WRITE_URI_PERMISSION);
  }

  private static boolean checkUriPermission(@NonNull Context context, @NonNull Uri uri,
      int permissionFlags) {
    return !isAtLeastNougat() || context.checkCallingUriPermission(uri,
        permissionFlags) == PackageManager.PERMISSION_GRANTED;
  }

  /**
   * 撤销某个 Uri 的访问授权: 读写
   *
   * @see #revokeUriReadPermission(Context, Uri) 撤销 读
   * @see #revokeUriWritePermission(Context, Uri) 撤销 写
   */
  public static void revokeUriPermission(@NonNull Context context, @NonNull Uri uri) {
    revokeUriPermission(context, uri,
        FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
  }

  /**
   * 撤销某个 Uri 的访问授权: 读
   *
   * @see #revokeUriPermission(Context, Uri) 撤销 读写
   * @see #revokeUriWritePermission(Context, Uri) 撤销 写
   */
  public static void revokeUriReadPermission(@NonNull Context context, @NonNull Uri uri) {
    revokeUriPermission(context, uri, FLAG_GRANT_READ_URI_PERMISSION);
  }

  /**
   * 撤销某个 Uri 的访问授权: 写
   *
   * @see #revokeUriPermission(Context, Uri) 撤销 读写
   * @see #revokeUriReadPermission(Context, Uri) 撤销 读
   */
  public static void revokeUriWritePermission(@NonNull Context context, @NonNull Uri uri) {
    revokeUriPermission(context, uri, FLAG_GRANT_WRITE_URI_PERMISSION);
  }

  private static void revokeUriPermission(@NonNull Context context, @NonNull Uri uri,
      int permissionFlags) {
    if (isAtLeastNougat()) {
      context.revokeUriPermission(uri, permissionFlags);
    }
  }

  @NonNull
  private static Uri getUriForFile(@NonNull Context context, @NonNull File file,
      @Nullable Intent intent, int permissionFlags) {
    final Uri uri;
    // 为了减少 App 启动时间耗时,仅在系统版本大于等于 Android Nougat(7.0-24) 时启用 FileProvider
    if (isAtLeastNougat()) {
      try {
        uri = FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file);
      } catch (Throwable e) {
        return getUriForFile(file);
      }
      if (intent != null) {
        intent.addFlags(permissionFlags);
      } else {
        // This grants temporary access permission for the content URI to the specified package
        // The permission remains in effect until you revoke it by calling revokeUriPermission()
        // or until the device reboots.
        context.grantUriPermission(context.getPackageName(), uri,
            permissionFlags);
      }
    } else {
      uri = getUriForFile(file);
    }
    return uri;
  }

  @NonNull
  private static Uri getUriForFile(@NonNull File file) {
    try {
      return Uri.fromFile(file);
    } catch (Throwable e) {
      return Uri.EMPTY;
    }
  }

  private FileProviders() {
    // LEFT-DO-NOTHING
  }

}
4)参考链接:

关于FileProvider

FileProvider官网

Android7.0 权限变更

Stack Overflow解释

二、java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri 。。。

1)问题分析:

当我们设置 targetSdkVersion 为23和23以上的应用才会出现异常,在使用危险权限的时候系统必须要获得用户的同意才能使用,要不然应用就会崩溃。

现在对于新版本的权限变更应该有了基本的认识,那么,是不是所有权限都需要去进行特殊处理呢?当然不是,只有那些危险级别的权限才需要,如列表所示
在这里插入图片描述

2)解决方案:

首先进行动态权限申请

int hasWriteContactsPermission = ContextCompat.checkSelfPermission(this, Manifest.permission
        .WRITE_EXTERNAL_STORAGE);
    if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
      ActivityCompat.requestPermissions(this, new String[]{Manifest.permission
              .WRITE_EXTERNAL_STORAGE},
          CODE_FOR_WRITE_PERMISSION);
    } else {
      hasWritePermimssion = true;
    }

然后监听用户是否授权,然后进行我们的业务逻辑操作

@Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull
      int[] grantResults) {
    switch (requestCode) {
      case CODE_FOR_WRITE_PERMISSION: {
        // If request is cancelled, the result arrays are empty.
        if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
          hasWritePermimssion = true;
        } else {

          // permission denied, boo! Disable the
          // functionality that depends on this permission.
          hasWritePermimssion = false;
        }
      }
    }
  }
3)参考链接

系统权限:正常的和危险的

运行时请求权限

android permission权限与安全机制解析

------至所有正在努力奋斗的程序猿们!加油!!
有码走遍天下 无码寸步难行
1024 - 梦想,永不止步!
爱编程 不爱Bug
爱加班 不爱黑眼圈
固执 但不偏执
疯狂 但不疯癫
生活里的菜鸟
工作中的大神
身怀宝藏,一心憧憬星辰大海
追求极致,目标始于高山之巅
一群怀揣好奇,梦想改变世界的孩子
一群追日逐浪,正在改变世界的极客
你们用最美的语言,诠释着科技的力量
你们用极速的创新,引领着时代的变迁

——乐于分享,共同进步,欢迎补充
——Treat Warnings As Errors
——Any comments greatly appreciated
——Talking is cheap, show me the code
——诚心欢迎各位交流讨论!QQ:1138517609
——GitHub:https://github.com/selfconzrr

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BugFree_张瑞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值