Android 运行时权限

前言

 Android 是一个权限分隔的操作系统,在默认情况下任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读取或写入用户的私有数据(例如联系人或电子邮件)、读取或写入其他应用程序的文件、执行网络访问、使设备保持唤醒状态等。

 Android 引入权限机制来管控基本沙盒未提供的额外功能。应用以静态方式声明它们需要的权限,然后 Android 系统提示用户同意。

 在Android 6.0(Api 23)之前,我们想使用某个功能,只需要在Manifest中声明,在安装程序包时,系统会自动提示用户授权。但是 6.0 时系统行为发生了变更,安装程序时不再进行权限提示,一些普通权限只要进行声明会默认授予,但一些危险权限(例如读取联系人)除了声明外,还需要动态请求用户授权,否则可能导致功能不可用或产生crash。

变更概览

  • 手机系统版本 < 23 : 只需要在 Manifest 中声明即可
  • 手机系统版本 >=23 但应用 targetSdkVersion < 23 : 系统会认为你未适配好运行时权限,还是只需声明了即可
  • 手机系统版本 >=23 且应用 targetSdkVersion >=23 : 既需要声明,又需要动态申请

权限声明

 在 AndroidManifest 中 使用 <uses-permission>标签,比如写存储权限:

  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

权限分类

 系统权限分为几个保护级别

  • 正常权限
    涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。如需当前正常权限的完整列表,请参阅正常权限

  • 危险权限
    涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。

  • 特殊权限
    有许多权限其行为方式与正常权限及危险权限都不同。SYSTEM_ALERT_WINDOWWRITE_SETTINGS 特别敏感,因此大多数应用不应该使用它们。如果某应用需要其中一种权限,必须在清单中声明该权限,并且发送请求用户授权的 intent。系统将向用户显示详细管理屏幕,以响应该 intent。

    《Android 运行时权限》 特殊权限.jpg

权限组

所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:

  • 如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
  • 如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限(马上进行成功回调)。

需要注意的是:

  • 我们不应该依赖权限组,而要对具体权限进行判断及申请,因为具体权限可能在后续版本中产生分组变动
  • 系统在展示权限说明时,只会展示权限组的解释,并不会解释某个具体权限,所以在请求前弹出一个详细的说明界面是很有必要的,以防用户产生误解

最后附上危险权限的列表:

《Android 运行时权限》 危险权限.png

动态请求权限

 首先我们需要了解几个方法

  • 判断是否有某个权限
    /**
     * 检查用户是否授予了某个权限
     *
     * @param permission 待检查的权限
     * @return true:{@link PackageManager#PERMISSION_GRANTED} false:{@link PackageManager#PERMISSION_DENIED}
     */
    private boolean hadPermission(String permission) {
        return ContextCompat.checkSelfPermission(this, permission)
                == PackageManager.PERMISSION_GRANTED;
    }
  • 请求权限
    /**
     * 动态请求权限 系统会弹个框
     *
     * @param permissions 待用户授权的权限集
     */
    private void requestPermission(String[] permissions) {
        ActivityCompat.requestPermissions(this, permissions, REQ_CODE);
    }
  • 是否被拒绝过一次
    /**
     * 是否应该展示一下申请权限的理由
     *
     * @param permission 待授权的权限
     * @return 如果用户拒绝过一次权限请求就会返回 true
     */
    private boolean showRationale(String permission) {
        return ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
    }

比如我们现在要使用写存储的权限,结合起来就是:

    private void writeText2Storage() {

        File file = new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                "permission.txt");
        if (!file.exists()) {

            if (hadPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                //授予了权限
                onPermissionGranted(file);
            } else {

                if (showRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    //被用户拒绝过一次请求
                    toast("需要我们展示申请权限的理由");
                } else {
                    //请求权限弹窗
                    requestPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE });
                }
            }
        }
    }

然后我们在 Activity 和 Fragment 中的 onRequestPermissionsResult 中可以收到请求权限的回调

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQ_CODE) {

            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                //权限被允许
                toast("允许了权限");
            } else {

                //权限被拒绝 我们需要展示请求权限的理由 并且提供一键跳转应用权限管理界面
                onPermissionDenied();
            }
        }
    }

使用框架

 可以看到,虽然动态权限的流程很简单,但每次请求权限时都写这些代码还是很麻烦的,好在Github上有很多封装的库,我一直在使用 AndPermission 也推荐给大家,这个库除了危险权限外,还对URI权限,特殊权限也进行了封装,使用起来很方便

《Android 运行时权限》 AndPermission Feature.png

工作中遇到的坑

 华为手机在运行中,跳到权限管理界面,勾选拒绝之前授予的正在使用的权限,然后回到应用,华为默认会销毁重建这个Activity,并且只保存了一些和Activity相关的东西,导致Activity抽风..
 比如我的游戏界面里有语音连麦功能,用户在玩游戏时手动跳到应用权限管理界面,然后拒绝录音权限,再回到应用时,游戏Activity会销毁重建…

参考资料

    原文作者:double_so3
    原文地址: https://www.jianshu.com/p/efbc33b7c6f3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞