Android 6.0运行时权限详解

作者: 慕涵盛华 | 来源:发表于2017-03-16 09:02 被阅读173次

    Android 6.0之前,权限在应用安装过程中只询问一次,以列表的形式展现给用户,然而大多数用户并不会注意到这些,直接就下一步了,应用安装成功后就会被赋予清单文件中的所有权限,应用就可以在用户不知情的情况下进行非法操作(比如偷偷的上传用户数据)。

    Android 6.0版本中运行时权限的出现解决了这一问题,一些高危权限会在应用的运行过程中动态申请,这样用户就可以选择是否允许,并不是所有的权限都需要动态申请.

    Demo演示

    演示.gif

    权限分类

    Android 将系统权限分成了四个保护等级:

    • normal :普通级别
    • dangerous :危险级别
    • signature:签名级别
    • signatureOrSystem:系统签名级别

    而对于开发而言,关心的只有 普通权限 和 危险权限 两类
    其他两级权限,为高级权限,应用拥有platform级别的认证才能申请。

    当应用试图在没有权限的情况下做受限操作,应用将被系统杀掉以警示。
    所以权限的控制很重要,一个不留神,程序就会系统干掉,后果很严重~~

    普通权限 (normal permission)

    普通权限 会在App安装期间被默认赋予。这类权限不需要开发人员进行额外操作,开发者仅仅需要在AndroidManifext.xml上声明,那么应用就会被允许拥有该权限
    这类权限包括:

    ACCESS_LOCATION_EXTRA_COMMANDS
    ACCESS_NETWORK_STATE
    ACCESS_NOTIFICATION_POLICY
    ACCESS_WIFI_STATE
    BLUETOOTH
    BLUETOOTH_ADMIN
    BROADCAST_STICKY
    CHANGE_NETWORK_STATE
    CHANGE_WIFI_MULTICAST_STATE
    CHANGE_WIFI_STATE
    DISABLE_KEYGUARD
    EXPAND_STATUS_BAR
    FLASHLIGHT
    GET_PACKAGE_SIZE
    INTERNET
    KILL_BACKGROUND_PROCESSES
    MODIFY_AUDIO_SETTINGS
    NFC
    READ_SYNC_SETTINGS
    READ_SYNC_STATS
    RECEIVE_BOOT_COMPLETED
    REORDER_TASKS
    REQUEST_INSTALL_PACKAGES
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS
    SET_ALARM
    INSTALL_SHORTCUT
    

    危险权限

    这些权限是在开发6.0程序时,必须要注意的。
    这些权限处理不好,程序可能会直接被系统干掉。
    权限如下:

    我们会发现这些权限被分成了组。每个组里面包含了一些相近的权限。

    分组的作用:

    这些分组实际上是有一些特殊含义的。
    系统在动态赋予权利的时候,是按照组去赋予的。即:
    <b>如果允许了某一个权限,那么同组中的其他权限也会被直接赋予</b>
    对于申请时弹出的dialog上面的文本说明也是对整个权限组的说明,而不是对单个权限的说明。

    注意:
    不要对权限组过多的依赖,尽可能对每个危险权限都进行正常流程的申请,因为在后期的版本中这个权限组可能会产生变化。

    相关的方法

    • 权限检查

        //如果返回true表示已经授权了
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED)
      
    • 权限申请

        // 类似 startActivityForResult()中的REQUEST_CODE
        int REQUEST_CODE = 99;
        // 权限列表,将要申请的权限以数组的形式提交。
        // 系统会依次进行弹窗提示。
        // 注意:如果AndroidManifest.xml中没有进行权限声明,这里配置了也是无效的,不会有弹窗提示。
        String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
        ActivityCompat.requestPermissions(this,
             permissions,
             REQUEST_CODE);
      
    • 权限回调
      这个方法是Activity的一个回调方法。<b>每一次调用</b> <b>requestPermissions()</b>方法,都会回调一次这个方法。

        @Override
        public void onRequestPermissionsResult(int requestCode,
                                             String permissions[], int[] grantResults) {
          switch (requestCode) {
              case REQUEST_CODE: {
                  // grantResults是一个数组,和申请的数组一一对应。
                  // 如果请求被取消,则结果数组为空。
                  if (grantResults.length > 0
                          && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      // 权限同意了,做相应处理
                  } else {
                      // 权限被拒绝了
                  }
                  return;
              }
          }
        }
      
    • 权限再次申请
      当用户拒绝了某个权限时,我们可以再次去申请这个权限。但是这个时候,你应该告诉用户,你为什么要申请这个权限。

        //判断一个权限是否被用户拒绝了,true表示拒绝了
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
              Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
          // 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
        }
      

    注意:

    这个API有两种情况会返回false:

    • 用户申请的权限没有被用户拒绝。
    • 用户在系统权限弹窗中,选中了 不再提醒 选项。

    申请权限的正确流程

    • 1.先检查权限有没有授权过,如果授权过不申请,如果没有就去申请
    • 2.如果去申请用户拒绝了,发起二次请求,此时会有一个“不在提示的”选择
    • 3.在二次请求中,这里需要给用户解释一下我们为什么需要这个权限,否则用户可能会永久不在激活这个申请,方便用户理解我们为什么需要这个权限
    • 4.如果用户二次请求被拒绝或者选择了不在提示,我们引导用户到应用权限页面,让用户自己手动打开

    代码示例

    /**
     * 权限动态申请帮助类
     * 只需要将申请的权限放入mPermissionModels数组中即可
     */
    public class PermissionHelper {
    
        private static final String TAG = "PermissionHelper";
    
        /**
         * 小tips:这里的int数值不能太大,否则不会弹出请求权限提示,测试的时候,改到1000就不会弹出请求了
         */
        private final static int READ_PHONE_STATE_CODE = 101;
    
        private final static int WRITE_EXTERNAL_STORAGE_CODE = 102;
    
        private final static int REQUEST_OPEN_APPLICATION_SETTINGS_CODE = 12345;
    
        /**
         * 有米 Android SDK 所需要向用户申请的权限列表
         */
        private PermissionModel[] mPermissionModels = new PermissionModel[]{
                new PermissionModel("电话", Manifest.permission.READ_PHONE_STATE, "我们需要读取手机信息的权限来标识您的身份", READ_PHONE_STATE_CODE),
                new PermissionModel("存储空间", Manifest.permission.WRITE_EXTERNAL_STORAGE, "我们需要您允许我们读写你的存储卡,以方便我们临时保存一些数据",
                        WRITE_EXTERNAL_STORAGE_CODE)
        };
    
        private Activity mActivity;
    
        private OnApplyPermissionListener mOnApplyPermissionListener;
    
        public PermissionHelper(Activity activity) {
            mActivity = activity;
        }
    
        public void setOnApplyPermissionListener(OnApplyPermissionListener onApplyPermissionListener) {
            mOnApplyPermissionListener = onApplyPermissionListener;
        }
    
        /**
         * 这里我们演示如何在Android 6.0+上运行时申请权限
         */
        public void applyPermissions() {
            try {
                for (final PermissionModel model : mPermissionModels) {
                    if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(mActivity, model.permission)) {
                        ActivityCompat.requestPermissions(mActivity, new String[]{model.permission}, model.requestCode);
                        return;
                    }
                }
                if (mOnApplyPermissionListener != null) {
                    mOnApplyPermissionListener.onAfterApplyAllPermission();
                }
            } catch (Throwable e) {
                Log.e(TAG, "", e);
            }
        }
    
        /**
         * 对应Activity的 {@code onRequestPermissionsResult(...)} 方法
         *
         * @param requestCode
         * @param permissions
         * @param grantResults
         */
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            switch (requestCode) {
                case READ_PHONE_STATE_CODE:
                case WRITE_EXTERNAL_STORAGE_CODE:
                    // 如果用户不允许,我们视情况发起二次请求或者引导用户到应用页面手动打开
                    if (PackageManager.PERMISSION_GRANTED != grantResults[0]) {
    
                        // 二次请求,表现为:以前请求过这个权限,但是用户拒接了
                        // 在二次请求的时候,会有一个“不再提示的”checkbox
                        // 因此这里需要给用户解释一下我们为什么需要这个权限,否则用户可能会永久不在激活这个申请
                        // 方便用户理解我们为什么需要这个权限
                        if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, permissions[0])) {
                            AlertDialog.Builder builder =
                                    new AlertDialog.Builder(mActivity).setTitle("权限申请").setMessage(findPermissionExplain(permissions[0]))
                                            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
    
                                                @Override
                                                public void onClick(DialogInterface dialog, int which) {
                                                    applyPermissions();
                                                }
                                            });
                            builder.setCancelable(false);
                            builder.show();
                        }
                        // 到这里就表示已经是第3+次请求,而且此时用户已经永久拒绝了,这个时候,我们引导用户到应用权限页面,让用户自己手动打开
                        else {
                            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity).setTitle("权限申请")
                                    .setMessage("请在打开的窗口的权限中开启" + findPermissionName(permissions[0]) + "权限,以正常使用本应用")
                                    .setPositiveButton("去设置", new DialogInterface.OnClickListener() {
    
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            openApplicationSettings(REQUEST_OPEN_APPLICATION_SETTINGS_CODE);
                                        }
                                    }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            mActivity.finish();
                                        }
                                    });
                            builder.setCancelable(false);
                            builder.show();
                        }
                        return;
                    }
    
                    // 到这里就表示用户允许了本次请求,我们继续检查是否还有待申请的权限没有申请
                    if (isAllRequestedPermissionGranted()) {
                        if (mOnApplyPermissionListener != null) {
                            mOnApplyPermissionListener.onAfterApplyAllPermission();
                        }
                    } else {
                        applyPermissions();
                    }
                    break;
            }
        }
    
        /**
         * 对应Activity的 {@code onActivityResult(...)} 方法
         *
         * @param requestCode
         * @param resultCode
         * @param data
         */
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            switch (requestCode) {
                case REQUEST_OPEN_APPLICATION_SETTINGS_CODE:
                    if (isAllRequestedPermissionGranted()) {
                        if (mOnApplyPermissionListener != null) {
                            mOnApplyPermissionListener.onAfterApplyAllPermission();
                        }
                    } else {
                        mActivity.finish();
                    }
                    break;
            }
        }
    
        /**
         * 判断是否所有的权限都被授权了
         *
         * @return
         */
        public boolean isAllRequestedPermissionGranted() {
            for (PermissionModel model : mPermissionModels) {
                if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(mActivity, model.permission)) {
                    return false;
                }
            }
            return true;
        }
    
        /**
         * 打开应用设置界面
         *
         * @param requestCode 请求码
         * @return
         */
        private boolean openApplicationSettings(int requestCode) {
            try {
                Intent intent =
                        new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + mActivity.getPackageName()));
                intent.addCategory(Intent.CATEGORY_DEFAULT);
    
                // Android L 之后Activity的启动模式发生了一些变化
                // 如果用了下面的 Intent.FLAG_ACTIVITY_NEW_TASK ,并且是 startActivityForResult
                // 那么会在打开新的activity的时候就会立即回调 onActivityResult
                // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mActivity.startActivityForResult(intent, requestCode);
                return true;
            } catch (Throwable e) {
                Log.e(TAG, "", e);
            }
            return false;
        }
    
        /**
         * 查找申请权限的解释短语
         *
         * @param permission 权限
         * @return
         */
        private String findPermissionExplain(String permission) {
            if (mPermissionModels != null) {
                for (PermissionModel model : mPermissionModels) {
                    if (model != null && model.permission != null && model.permission.equals(permission)) {
                        return model.explain;
                    }
                }
            }
            return null;
        }
    
        /**
         * 查找申请权限的名称
         *
         * @param permission 权限
         * @return
         */
        private String findPermissionName(String permission) {
            if (mPermissionModels != null) {
                for (PermissionModel model : mPermissionModels) {
                    if (model != null && model.permission != null && model.permission.equals(permission)) {
                        return model.name;
                    }
                }
            }
            return null;
        }
    
        private static class PermissionModel {
    
            /**
             * 权限名称
             */
            public String name;
    
            /**
             * 请求的权限
             */
            public String permission;
    
            /**
             * 解析为什么请求这个权限
             */
            public String explain;
    
            /**
             * 请求代码
             */
            public int requestCode;
    
            public PermissionModel(String name, String permission, String explain, int requestCode) {
                this.name = name;
                this.permission = permission;
                this.explain = explain;
                this.requestCode = requestCode;
            }
        }
    
        /**
         * 权限申请事件监听
         */
        public interface OnApplyPermissionListener {
    
            /**
             * 申请所有权限之后的逻辑
             */
            void onAfterApplyAllPermission();
        }
    

    }

    然后在Activity中的相应方法中调用对应的方法

    //权限请求结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        mPermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    
    //在此方法中检测打开设置界面开启权限结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mPermis
    

    推荐:动态权限申请三方库

    关注微信公众号获取更多相关资源

    Android小先生

    相关文章

      网友评论

        本文标题:Android 6.0运行时权限详解

        本文链接:https://www.haomeiwen.com/subject/kgddnttx.html