ROLL

登    录

注    册

重置密码

博主-ROLL圈圈 博主-ROLL圈圈 2020年10月12 12375 字

【需求解决系列之五】一行代码实现Android 6.0以上动态权

前言

自从Android6.0以后,运行时权限申请就成为了一大痛点!动态申请吧,说它难吧,它又不难,说它不难吧,申请起来贼复杂;不申请吧,还要给你整闪退。

其实现在有很多权限申请框架了,那么为什么我还在造轮子呢?其实原因很简单,可能是年纪大了,开始喜欢简约风了,能少写一行的我绝对不想多写一个字母!说到底还是觉得现有的很多框架,用起来不是那么顺手,配置相对复杂,所以萌生了再整一个一行代码实现权限申请的想法。

系列

在工作之余,打算将一些常用的逻辑页面,模块,功能点做成library库,这样当有相似需求的时候,可以做到插拔式开发!现在系列中有以下内容

Github地址

具体的实现demo已经放到Github了,效果图什么的也在上面,上面也有写好的demo apk提供下载尝试,如果你觉得有用,记得star哦!哈哈哈,就是这么不要脸~~~

Github地址

正文

实现思路

权限申请无非三板斧,第一:检查是否已授权,第二:发起授权申请,第三:处理授权结果!第一步和第二步是固定的操作,只不过我们可以将繁琐的步骤封装起来。

第三步才是不好处理的核心,正常来说,当我们发起权限申请之后,必须要重写onRequestPermissionsResult方法来对授权结果进行处理,这就导致了无论你怎么封装,还是必须侵入到每个Activity的onRequestPermissionsResult中去,这就意味着不管怎样,你都至少要在两个地方写上对应的代码才能实现授权功能。但是作为调用者来说,我希望的操作只有两个,一,提供我需要授权的权限,二,回调给我授权结果。

如何实现通过回调的方式反馈给调用者授权结果呢?最开始想法是写一个透明宽高各1像素大小的Activity,将权限申请和授权结果的操作都放在里面处理,处理完之后关闭此Activity,对于用户来说其实也是无感知的。但是有一个最大的问题就是,当在这个Activity处理完onRequestPermissionsResult之后,如何才能通过回调的方式告知调用方呢?由于打开新的Activity之后,调用方Activity和处理逻辑的Activity是两个不同的页面,在不借助其他技术的情况下,是不好实现类似接口回调的方式进行通讯的,所以摒弃了这个方案。

后来想到,既然Activity不能胜任这份工作,可以尝试一下它的兄弟Fragment,Fragment中也可以发起授权申请并处理授权结果,最重要的是,Fragment是依附于Activity的,所以我们可以在同一个Activity下进行权限的申请和处理,并且不会污染到宿主Activity的逻辑,美滋滋。

实现过程

  • 配置并申请权限

    对于调用者来说,他需要做的就是配置他需要申请的权限列表。所以我们需要提供一个入口给他。

    FanPermissionUtils.with(MainActivity.this)
                        //添加所有你需要申请的权限
                        .addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        .addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
                        .addPermissions(Manifest.permission.CALL_PHONE)
                        .addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
                        .addPermissions(Manifest.permission.CAMERA)
                        ...

    我们通过FanPermissionUtils.with方法构造出一个FanPermissionUtils工具类,使用链式方法让用户配置他需要申请的所有权限。FanPermissionUtils的内部维护了权限列表,存储所有需要授权的权限。

    调用者配置好权限之后,调用startCheckPermission开始进行权限申请。

    FanPermissionUtils.with(MainActivity.this)
                        //添加所有你需要申请的权限
                        .addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        .addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
                        .addPermissions(Manifest.permission.CALL_PHONE)
                        .addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
                        .addPermissions(Manifest.permission.CAMERA)
                        //开始授权
                        .startCheckPermission();

    此时,我们会构造一个没有页面的FanPermissionFragment来处理授权相关的逻辑,并将构造出的Fragment添加到宿主Activity中。

    //构造FanPermissionFragment并将自己添加到宿主Activity中
    FanPermissionFragment.newInstance(permissions).start(mContext);
    
    /**
     * 开始请求
     */
    public void start(Activity activity) {
        if (activity != null) {
            mContext = activity;
            if (Looper.getMainLooper() != Looper.myLooper()) {
                return;
            }
            activity.getFragmentManager().beginTransaction().add(this, activity.getClass().getName()).commit();
        }
    }

    一旦FanPermissionFragment被构造,就会对调用方提供的权限列表进行逐一排查,罗列出还未授权的权限列表,并对这些未授权的权限发起二次权限申请。

    //记录未授权的权限
        List<String> deniedPermissions = new ArrayList<>();
        for (String permission : permissions) {
            int check = ContextCompat.checkSelfPermission(getActivity(), permission);
            if (check == PackageManager.PERMISSION_GRANTED) {
                //授权通过了已经 do nothing
            } else {
                deniedPermissions.add(permission);
            }
        }
        if (deniedPermissions.size() != 0) {
            //有权限没有通过
            requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), PERMISSION_REQUEST_CODE);
        } else {
            //授权全部通过
    
        }

    这样我们就完成了第一步和第二步。

  • 对授权结果进行处理

    对于调用者来说,最佳的体验就是回调的方式反馈授权结果。先定义一个回调接口。

    public interface FanPermissionListener {
        /*
         * 授权全部通过
         */
        void permissionRequestSuccess();
    
        /*
         * 授权未通过
         * @param grantedPermissions 已通过的权限
         * @param deniedPermissions 拒绝的权限
         * @param forceDeniedPermissions 永久拒绝的权限(也就是用户点击了不再提醒的那些权限)
         */
        void permissionRequestFail(String[] grantedPermissions, String[] deniedPermissions, String[]     forceDeniedPermissions);
    }

    调用者在构造FanPermissionUtils的时候,设置回调监听即可。

        FanPermissionUtils.with(MainActivity.this)
                        //添加权限申请回调监听 如果申请失败 会返回已申请成功的权限列表,用户拒绝的权限列表和用户点击了不再提醒的永久拒绝的权限列表
                        .setPermissionsCheckListener(new FanPermissionListener() {
                            @Override
                            public void permissionRequestSuccess() {
                                //所有权限授权成功才会回调这里
                            }
    
                            @Override
                            public void permissionRequestFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions) {
                                //当有权限没有被授权就会回调这里
                            }
                        });

    我们在FanPermissionUtils中会保存调用者设置的回调接口,然后在FanPermissionFragment被构造的时候将这个回调接口传给FanPermissionFragment。

     FanPermissionFragment.newInstance(permissions).setPermissionCheckListener(listener).start(mContext);

    然后在FanPermissionFragment中去处理申请授权之后的授权回调操作。将授权回调的结果通过调用者设置的回调监听返回给调用者。来实现侵入性最小的权限申请方案。

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            //记录点击了不再提醒的未授权权限
            List<String> forceDeniedPermissions = new ArrayList<>();
            //记录点击了普通的未授权权限
            List<String> normalDeniedPermissions = new ArrayList<>();
            List<String> grantedPermissions = new ArrayList<>();
            for (int i = 0; i < grantResults.length; i++) {
                int grantResult = grantResults[i];
                String permission = permissions[i];
                if (grantResult == PackageManager.PERMISSION_GRANTED) {
                    //授权通过
                    grantedPermissions.add(permission);
                } else {
                    //授权拒绝
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(mContext, permission)) {
                        //用户勾选了不再提示
                        forceDeniedPermissions.add(permission);
                    } else {
                        //用户仅仅点击了拒绝 未勾选不再提示
                        normalDeniedPermissions.add(permission);
                    }
                }
            }
            if (forceDeniedPermissions.size() == 0 && normalDeniedPermissions.size() == 0) {
                //全部授权通过
                requestPermissionsSuccess();
            } else {
                //授权未通过
                for (String permission : this.permissions) {
                    if (grantedPermissions.contains(permission)
                            || normalDeniedPermissions.contains(permission)
                            || forceDeniedPermissions.contains(permission)) {
    
                    } else {
                        //如果三者都不包含他 包名这个权限不是隐私权限 直接给就完事了 所以要放到已授权的权限列表里面去
                        grantedPermissions.add(permission);
                    }
                }
                requestPermissionsFail(grantedPermissions.toArray(new String[grantedPermissions.size()]),
                        normalDeniedPermissions.toArray(new String[normalDeniedPermissions.size()]),
                        forceDeniedPermissions.toArray(new String[forceDeniedPermissions.size()]));
            }
        }
    }
    
    //授权全部通过
    private void requestPermissionsSuccess() {
        if (permissionCheckListener != null) {
            permissionCheckListener.permissionRequestSuccess();
        }
        mContext.getFragmentManager().beginTransaction().remove(this).commit();
    }
    
    //授权未通过
    private void requestPermissionsFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions) {
        if (permissionCheckListener != null) {
            permissionCheckListener.permissionRequestFail(grantedPermissions, deniedPermissions, forceDeniedPermissions);
        }
        mContext.getFragmentManager().beginTransaction().remove(this).commit();
    }
    

畅想优化

此外还有一种需求,当用户不授权,就不给用。这种情况下我们需要在申请权限的时候,当用户点击拒绝的时候,一直无限申请权限,直到用户点击允许为止,对,就是这么流氓!

细心的同学在上面的代码中已经看到了一些逻辑,对应的就是另外一种一种情况,用户在你连续弹出几次申请权限的弹窗之后,会选择勾选不再提醒,这个时候你再去申请权限的时候,系统会直接回调给你拒绝,并且不会弹出授权弹窗,这个时候我们需要调用ActivityCompat.shouldShowRequestPermissionRationale(mContext, permission)方法来判断下用户是否勾选了不再提醒的按钮,如果用户勾选了,那么我们就需要指引用户去设置页面手动授权,因为我们在app内部已经无能为力了。

if (normalDeniedPermissions.size() != 0) {
    //还有普通拒绝的权限可以弹窗
    requestPermission();
} else {
    //所有没有通过的权限都是用户点击了不再提示的 我擦 这里本来是想把未授权的所有权限的名称列出来展示的 后来想想觉得配置有点麻烦
    new AlertDialog.Builder(mContext)
            .setTitle(mContext.getString(R.string.permissions_check_warn))
            .setMessage(checkConfig == null ? forceDeniedPermissionTips : checkConfig.getForceDeniedPermissionTips())
            .setCancelable(false)
            .setPositiveButton(mContext.getString(R.string.permissions_check_ok), new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    openSettingPage();
                }
            }).show();
}

牛逼回吹

之前不是说一行代码实现吗?下面就是整个申请权限的功能,的确只有一个结尾的分号,我说一行不算吹牛吧!~~~

FanPermissionUtils.with(MainActivity.this)
    //添加所有你需要申请的权限
    .addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    .addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
    .addPermissions(Manifest.permission.CALL_PHONE)
    .addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
    .addPermissions(Manifest.permission.CAMERA)
    //添加权限申请回调监听 如果会返回已申请成功的权限列表,用户拒绝的权限列表和用户点击了不再提醒的永久列表
    .setPermissionsCheckListener(new FanPermissionListener() {
        @Override
        public void permissionRequestSuccess() {
            //所有权限授权成功才会回调这里
            ((TextVfindViewById(R.id.tv_result)).setText("授权结果\n\n所有权限都授权成;
            Toast.makeText(MainActivity.this, "所有权限都授权Toast.LENGTH_SHORT).show();

        @Override
        public void permissionRequestFail(String[] grantedPermissions, StrideniedPermissions, String[] forceDeniedPermissions) {
            //当有权限没有被授权就会回调这里
            StringBuilder result = new StringBuilder("授权结果\n\n授权失败\n\n");
            result.append("授权通过的权限:\n");
            for (String grantedPermission : grantedPermissions) {
                result.append(grantedPermission + "\n");
            }
            result.append("\n临时拒绝的权限:\n");
            for (String deniedPermission : deniedPermissions) {
                result.append(deniedPermission + "\n");
            }
            result.append("\n永久拒绝的权限:\n");
            for (String forceDeniedPermission : forceDeniedPermissions) {
                result.append(forceDeniedPermission + "\n");
            }
            ((TextView) findViewById(R.id.tv_result)).setText(result);
            Toast.makeText(MainActivity.this, "授权失败", Toast.LENGTH_SHORT).show();
        }
    })
    //开始授权
    .startCheckPermission();

结语

**简书首页**,链接是 https://www.jianshu.com/u/123f97613b86

**掘金首页**,链接是 https://juejin.im/user/1099167356171918

**Github首页**,链接是 https://github.com/MZCretin

**CSDN首页**,链接是 http://blog.csdn.net/u010998327

我是Cretin,一个可爱的小男孩。