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
——CSDN:https://blog.csdn.net/u011489043
——简书:https://www.jianshu.com/u/4968682d58d1
——GitHub:https://github.com/selfconzrr

发布了79 篇原创文章 · 获赞 207 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/u011489043/article/details/86721895