EasyPermission:一句代码解决动态权限的申请和回执(带权限提示信息)

效果展示

216x384.gif

以上是演示请求一个相机权限的过程:

首次申请(顶部提醒)-拒绝-再次申请(顶部提醒)-再次拒绝(并勾选禁止再次询问)-再次申请(中部弹窗引导)-在设置页不授权-返回-再次申请(中部弹窗引导)-在设置页将相机权限设为允许-返回app

在此过程中,任意一次拒绝和允许,都可以及时的拿到允许/拒绝的结果,进而在页面展示授权状态或者执行自己授权后的逻辑。以上的这些所有逻辑,只需要一句代码就能搞定,你相信吗?

是的,EasyPermission她来了,在项目中集成之后,安卓权限的检查、申请、提示、结果回执,一句代码就可以搞定。

EasyPermission.build()
    .mRequestCode(RC_CODE_PERMISSION)//请求code,自己定义
    .mPerms(Manifest.permission.CAMERA)//权限,可支持多个
    .setAutoOpenAppDetails(true)//默认true
    .mAlertInfo( new PermissionAlertInfo("**需要申请摄像头权限",
                                         "**需要申请摄像头拍摄权限,以便您能够通过扫一扫实现扫描二维码;通过拍照更换您帐号的头像;拍照上传一些注册帐号需要的证件信息。拒绝或取消授权将影响以上功能,不影响使用其他服务"))
    .mResult(new EasyPermissionResult() {
        @Override
        public void onPermissionsAccess(int requestCode) {
            super.onPermissionsAccess(requestCode);
            //权限已通过
        }

        @Override
        public void onPermissionsDismiss(int requestCode, @NonNull List<String> permissions) {
            super.onPermissionsDismiss(requestCode, permissions);
            //权限被拒绝
        }
).requestPermission();

接下来我们就看下它是怎么实现的。

需求来源

最近国家工信部对手机隐私安全越来越重视,权限不能滥用,不能随意申请(有些应用在启动时就申请一堆权限),更不能强制申请(有些权限不通过就不让进入应用的,已经被下架了)。最近公司又接到上头通知:公司某款app中有申请定位权限时没有对用户解释说明,需要及时整改。

和产品沟通后借鉴小红书、京东,类似这种在请求权限的时候,在底下弹出系统弹窗时,同时在顶部浮出说明信息;如果权限被禁止了,就在中部弹出说明弹窗,引导去设置页面中完成授权。

image.png
image.png

方案分析

咨询了隔壁IOS的现状、权限在项目中,权限的申请可以也必须配置说明文案,那么在使用申请权限时系统就会弹出信息说明。

可是安卓的动态权限管理比较松散(无情的吐槽),请求权限真的是干巴巴的请求,就像上面图1底部的系统弹窗。要像京东、小红书那样实现申请弹窗时提示信息,收到权限或者拒绝时隐藏,权限有可能被禁止(拒绝并勾选不再提示),禁止时需要弹窗提醒,引导去设置页返回时又得不到有效的回调(是的,京东从设置页回来的时候,授权后不会继续执行申请权限的逻辑,我们的EasyPermission却完全可以做到)。

根据大致的分析,其实流程也是挺清晰的,实现以上效果的流程图如下(最终的EasyPermission库的最终实现思路也是基于此做的):

image.png

方案中需要考虑的问题

看到以上流程图,其实实现起来感觉还好,但是有一些问题:

  • 安卓的权限申请、回调函数、弹窗都是依赖于界面的,也就是跟activity相关。那么在后台服务中、或者某个View中,如果用到权限怎么办?
  • 一个页面中用到多个权限,这些回调、弹窗是不是就串在一起了很乱?
  • 假如在好多页面都用到某个权限,全要实现一遍这样的逻辑?
  • 公司有几个app,每个app都要去大量的处理权限逻辑和弹窗?

寻找成熟的方案

发现几个用的比较多的,RxPermissions,easypermissions(googlesamples),两个库都有几千的star,说明还是挺多人用的。但是有个问题没有解决(activity的关联)。

想起之前封装的EasyPermission,虽然只是简单的实现了权限的请求和回调,最终还是决定拿她孵化一下,进化进化,实现新的设想。

最终确实想到了解决方案,通过在初始化时,注册一个ActivityLifecycleCallback,监听activity的变动,这样在请求权限和弹窗时就可以随时取用最顶层的activity。

当然封装包括后面试用时,还是发现了不少问题,比如单例的页面ActivityLifecycleCallback回调的比较迟,可能导致拿到的activity是无效的;安卓没有直接查询权限是否被禁止的方法等,终是一一克服了。最终是上传到了jetpack上,可以直接引用。接下来就看下怎么使用一句话来实现权限的请求、弹窗、回调。

集成引导

第一步. 添加依赖

1. 根build.gradle中添加maven仓库的依赖.

        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

2. 项目主Module的bulid.gradle添加工具库最新版本依赖

            implementation 'com.gitee.zhang-yanqiang:easypermission:v2.0.12'
    }

最新版本查看链接:https://jitpack.io/#com.gitee.zhang-yanqiang/easypermission

第三步. 初始化配置

1.在Application的onCreate中完成初始化

public void onCreate() {
    super.onCreate();
    //首次使用权限申请之前完成初始化,建议放在Application onCreate()中完成
    EasyPermissionHelper.getInstance().init(this);
    }

2.将要使用EasyPermission的Activity中的onRequestPermissionsResultonActivityResult,

  • 在对应的Activity调用EasyPermissionHelper.getInstance().onRequestPermissionsResult和onActivityResult即可;
  • 如果你有BaseActivity,那么只需要在BaseActivity中设置一次即可。
  • 这两个方方法为了实现授权结果的自动回调,如果不需要回调可以不配置
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //使用EasyPermissionHelper注入回调(授权弹窗回调)
        EasyPermissionHelper.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //使用EasyPermissionHelper注入回调(系统设置返回使用)
        EasyPermissionHelper.getInstance().onActivityResult(requestCode, resultCode, data);
    }

功能使用

接下来看下怎么使用。

1.检测权限

只需要调用EasyPermission的hasPermission方法,支持多个权限同时传入。

EasyPermission.build().hasPermission(Manifest.permission.CAMERA);

2.申请权限

如果你在应用启动时需要申请权限,而且并不关注权限的结果, 只需要调用EasyPermission的requestPermission方法,支持多个权限传入。

EasyPermission.build().requestPermission(Manifest.permission.CAMERA);

3.需要权限的结果

  • 如果你需要知道申请权限后用户的选择结果,同时去执行自己的方法myVoid(),
  • 那么在onPermissionsAccess中去做就可以了,
  • onPermissionsDismiss是用户拒绝了权限的反馈。
EasyPermission.build()
                .mRequestCode(RC_CODE_PERMISSION)//请求code,自己定义
                .mPerms(Manifest.permission.CAMERA)//权限,可支持多个
                .mResult(new EasyPermissionResult() {//回调
                    @Override
                    public void onPermissionsAccess(int requestCode) {
                        super.onPermissionsAccess(requestCode);
                        //权限已通过
                    }

                    @Override
                    public void onPermissionsDismiss(int requestCode, @NonNull List<String> permissions) {
                        super.onPermissionsDismiss(requestCode, permissions);
                        //权限被拒绝
                    }
                }).requestPermission();

4.有时用户拒绝了权限,而且禁止了弹出询问,我该怎么办?想要在申请权限时弹窗告知用户权限的必要性怎么办?

  • 事实上,在新版本只需要通过mAlertInfo设置了提示文本,现在已经默认处理了弹窗的展示,也就是说不需要去重写onDismissAskopenAppDetails方法了
  • 如果想要自己处理弹窗逻辑,可以通过setAutoOpenAppDetails=false关闭自动处理的逻辑
  • 只要在onDismissAsk中,就可以得到被禁止的结果,同时你要注意onDismissAsk默认返回false
  • 如果你自己修改return true,将视为已经处理了禁止结果,将不再回调onPermissionsDismiss这个方法
  • 调用openAppDetails方法,可以弹窗引导用户去设置界面设置权限,成功后会自动回调
EasyPermission.build()
        .mRequestCode(RC_CODE_PERMISSION)//请求code,自己定义
        .mPerms(Manifest.permission.CAMERA)//权限,可支持多个
        .setAutoOpenAppDetails(true)//默认true
        .mAlertInfo( new PermissionAlertInfo("**需要申请摄像头权限",
        "**需要申请摄像头拍摄权限,以便您能够通过扫一扫实现扫描二维码;通过拍照更换您帐号的头像;拍照上传一些注册帐号需要的证件信息。拒绝或取消授权将影响以上功能,不影响使用其他服务"))
        .mResult(new EasyPermissionResult() {
            @Override
            public void onPermissionsAccess(int requestCode) {
                super.onPermissionsAccess(requestCode);
                //权限已通过
            }

            @Override
            public void onPermissionsDismiss(int requestCode, @NonNull List<String> permissions) {
                super.onPermissionsDismiss(requestCode, permissions);
                //权限被拒绝
            }

            @Override
            public boolean onDismissAsk(int requestCode, @NonNull List<String> permissions) {
                //权限被拒绝并禁止再次询问
                return super.onDismissAsk(requestCode,permissions);//这里true表示拦截处理,不再回调onPermissionsDismiss;
            }
            @Override
            public void openAppDetails() {
                //弹出默认的权限详情设置提示弹出框,在设置页完成允许操作后,会自动回调到onPermissionsAccess()
                super.openAppDetails();
                //如果样式不满意,可以弹出自定义明弹窗,在用户确认时调用 goToAppSettings();完成跳转设置页
            }).requestPermission();

5.弹窗样式自定义

权限库用起来蛮方便的,但是弹窗的文字颜色需要改一下,又不像大动干戈地每次自己去写弹窗,能不能设置一下文字大小、颜色?没问题,咱们支持弹窗自定义样式。

setDialogStyle在初始化方法init()之后任意时刻调用,设置样式后全局生效。

·使用默认经典样式(类似京东、小红书)

EasyPermissionHelper.getInstance().setDialogStyle(new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_DEFAULT));

image.png

·使用系统自带弹窗样式

使用系统自带的AlertDialog样式,具体的展示效果每个机型不太一样

EasyPermissionHelper.getInstance().setDialogStyle(new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_SYSTEM));
image.png

·使用自定义弹窗样式

EasyPermissionHelper.getInstance().setDialogStyle(
        new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_CUSTOM)
        .setTitleGravity(Gravity.CENTER)//设置居中
        .setTitleSize(17)//设置标题
        .setTitleColor("#333333")
        .setMessageSize(14)//设置内容
        .setMessageColor("#666666")
        .setButtonTextSize(14)//设置按钮
        .setButtonThemeColor("#FF0000")
        .setCancelText("取消")//设置文本
        .setConfirmText("去打开"));
image.png

完全自定义弹窗

以上方式只需要在初始话后设置一次,全局生效。如果以上方式依然满足不了你胃口,那只能自己去控制弹窗啦。 在EasyPermissionResult中重写openAppDetails(),只会影响当前权限的请求弹窗。

 @Override
public void openAppDetails() {
      //在前往应用设置详情页展示自己的弹窗告知用户我们需要哪些权限打开
      //在用户点击确认时调用easyPermission.goToAppSettings();完成跳转设置页
      }

6.其它注意事项

  1. mAlertInfo不设置将不会自动弹出权限说明弹窗,为了满足当前的日益严格的隐私政策,请对认真对待每一个权限说明
  2. 权限的申请不建议在onNewIntent中获取
  3. 相关日志tag为"EasyPermissionLog",默认不输出太多信息,如果需要调试请打开EasyPermissionConfigs.setDebug(true)
  4. 增加setAutoOpenAppDetails,如果PermissionAlertInfo有值,则在被禁止时自动触发 openAppDetails()
  5. 如果openAppDetails()样式不满足,可以重写openAppDetails()自定义弹出内容,也可以直接在onDismissAsk()拦截
  6. 由于EasyPermission在init初始化时使用ActivityLifecycleCallbacks开始监听activity变化,所以在launchMode="singleTask" onNewIntent中如果需要请求权限,需要重新设置activity。 可以使用两种方式完成。
  • 方式一:
EasyPermissionHelper.getInstance().updateTopActivity(mContext); 
easyPermission.requestPermission();
  • 方式二:
easyPermission.mContext(mContext).requestPermission();

7.其它工具

· 定位服务管理 EasyLocationTool

Android 9.0以后即使已经获得了用户授权定位权限,由于GPS定位服务未打开,依然获取不到定位,所以还需要对定位服务进行处理,LocationTool支持以下方法:

  1. isLocationEnabled() 获取当前定位服务是否开启
  2. gotoAppSettings() 直接跳转到手机-设置-安全和隐私-定位服务开启/关闭的页面

· 通知服务管理 EasyNotificationTool

通知服务的权限在Android中也比较特殊,它不像其它权限那样去直接申请,像定位服务一样需要去系统设置中开启,所以也要去设置页:

  1. isNotificationEnabled() 获取当前APP的通知权限是否开启
  2. gotoAppSettings() 直接跳转到手机-设置-通知和状态栏-通知管理-APP通知设置页

· 悬浮窗权限管理 EasyFloatWindowTool

悬浮窗权限在Android中也比较特殊,它不像其它权限那样去直接申请,像定位服务一样需要去系统设置中开启,所以也要去设置页:

  1. isFloatWindowEnabled() 获取当前APP的是否有悬浮窗权限
  2. gotoAppSettings() 直接跳转到手机-设置-应用管理-特殊应用权限-显示在其他应用的上层-APP设置页

结束语

  • 如果又更好的方案和思路,欢迎留言或者私信,可以git上提交解决方案或者issue
  • 祝所有人平安幸福、家庭和睦、身体健康。
  • 愿祖国早日完成统一大业,世界和平共处,繁荣发展。
  • 有任何疑问,可以及时反馈给我;
  • 如果你觉得还不错,请点赞o( ̄▽ ̄)d。

源代码

Gitee地址:https://gitee.com/zhang-yanqiang/easypermission

Github地址:https://github.com/githubZYQ/easypermission

由于Github访问不是很通畅,通常gitee更新效率会高一些

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容