效果展示
以上是演示请求一个相机权限的过程:
首次申请(顶部提醒)-拒绝-再次申请(顶部提醒)-再次拒绝(并勾选禁止再次询问)-再次申请(中部弹窗引导)-在设置页不授权-返回-再次申请(中部弹窗引导)-在设置页将相机权限设为允许-返回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中有申请定位权限时没有对用户解释说明,需要及时整改。
和产品沟通后借鉴小红书、京东,类似这种在请求权限的时候,在底下弹出系统弹窗时,同时在顶部浮出说明信息;如果权限被禁止了,就在中部弹出说明弹窗,引导去设置页面中完成授权。
方案分析
咨询了隔壁IOS的现状、权限在项目中,权限的申请可以也必须配置说明文案,那么在使用申请权限时系统就会弹出信息说明。
可是安卓的动态权限管理比较松散(无情的吐槽),请求权限真的是干巴巴的请求,就像上面图1底部的系统弹窗。要像京东、小红书那样实现申请弹窗时提示信息,收到权限或者拒绝时隐藏,权限有可能被禁止(拒绝并勾选不再提示),禁止时需要弹窗提醒,引导去设置页返回时又得不到有效的回调(是的,京东从设置页回来的时候,授权后不会继续执行申请权限的逻辑,我们的EasyPermission却完全可以做到)。
根据大致的分析,其实流程也是挺清晰的,实现以上效果的流程图如下(最终的EasyPermission库的最终实现思路也是基于此做的):
方案中需要考虑的问题
看到以上流程图,其实实现起来感觉还好,但是有一些问题:
- 安卓的权限申请、回调函数、弹窗都是依赖于界面的,也就是跟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中的onRequestPermissionsResult和onActivityResult,
- 在对应的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设置了提示文本,现在已经默认处理了弹窗的展示,也就是说不需要去重写onDismissAsk和openAppDetails方法了
- 如果想要自己处理弹窗逻辑,可以通过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));
·使用系统自带弹窗样式
使用系统自带的AlertDialog样式,具体的展示效果每个机型不太一样
EasyPermissionHelper.getInstance().setDialogStyle(new EasyAppSettingDialogStyle(EasyAppSettingDialogStyle.DialogStyle.STYLE_SYSTEM));
·使用自定义弹窗样式
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("去打开"));
完全自定义弹窗
以上方式只需要在初始话后设置一次,全局生效。如果以上方式依然满足不了你胃口,那只能自己去控制弹窗啦。 在EasyPermissionResult中重写openAppDetails(),只会影响当前权限的请求弹窗。
@Override
public void openAppDetails() {
//在前往应用设置详情页展示自己的弹窗告知用户我们需要哪些权限打开
//在用户点击确认时调用easyPermission.goToAppSettings();完成跳转设置页
}
6.其它注意事项
- mAlertInfo不设置将不会自动弹出权限说明弹窗,为了满足当前的日益严格的隐私政策,请对认真对待每一个权限说明
- 权限的申请不建议在onNewIntent中获取
- 相关日志tag为"EasyPermissionLog",默认不输出太多信息,如果需要调试请打开EasyPermissionConfigs.setDebug(true)
- 增加setAutoOpenAppDetails,如果PermissionAlertInfo有值,则在被禁止时自动触发 openAppDetails()
- 如果openAppDetails()样式不满足,可以重写openAppDetails()自定义弹出内容,也可以直接在onDismissAsk()拦截
- 由于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支持以下方法:
- isLocationEnabled() 获取当前定位服务是否开启
- gotoAppSettings() 直接跳转到手机-设置-安全和隐私-定位服务开启/关闭的页面
· 通知服务管理 EasyNotificationTool
通知服务的权限在Android中也比较特殊,它不像其它权限那样去直接申请,像定位服务一样需要去系统设置中开启,所以也要去设置页:
- isNotificationEnabled() 获取当前APP的通知权限是否开启
- gotoAppSettings() 直接跳转到手机-设置-通知和状态栏-通知管理-APP通知设置页
· 悬浮窗权限管理 EasyFloatWindowTool
悬浮窗权限在Android中也比较特殊,它不像其它权限那样去直接申请,像定位服务一样需要去系统设置中开启,所以也要去设置页:
- isFloatWindowEnabled() 获取当前APP的是否有悬浮窗权限
- gotoAppSettings() 直接跳转到手机-设置-应用管理-特殊应用权限-显示在其他应用的上层-APP设置页
结束语
- 如果又更好的方案和思路,欢迎留言或者私信,可以git上提交解决方案或者issue
- 祝所有人平安幸福、家庭和睦、身体健康。
- 愿祖国早日完成统一大业,世界和平共处,繁荣发展。
- 有任何疑问,可以及时反馈给我;
- 如果你觉得还不错,请点赞o( ̄▽ ̄)d。
源代码
Gitee地址:https://gitee.com/zhang-yanqiang/easypermission
Github地址:https://github.com/githubZYQ/easypermission
由于Github访问不是很通畅,通常gitee更新效率会高一些