runtime-permission与AOP适配
前言
Android M(6.0)开始,一个非常重要的改动,就是权限系统
Android M占比越来越高,最近仔细调研了新权限系统的特点、申请权限的流程,记录如下。
新权限系统特点
运行时授权
在旧系统中安装app,会弹出一个安装确认的界面,让用户进行授权。
不仅不授权就无法安装这个应用,而且只能对所有权限一起授权。 如下图中左边
在Android M及以后的系统中安装new app,安装过程中是不需要用户确认权限的。对应的授权方式类似iPhone,是在使用过程中,让用户针对单个权限去授权。如下图中右边是demo app在申请权限
new app: 打包时,targetSDKVersion>=23
用户随时可修改权限
Android M以后的系统,用户随时可以去系统设置中,打开或关闭某个权限
old app
Android 6.0为了保证兼容性,对于old app,沿用了旧系统的处理方式,且安装成功后,old app会自动获得所声明的所有权限。
那么是否old app就可以不适配新权限系统?
不是的,因为用户仍然可以去系统设置中,关闭old app的某个权限。
当old app使用被关闭的权限,并不会崩溃,不过相关行为会被系统忽略。
如果行为是获取数据,获取到的结果,会是null。
old app: 打包时,targetSDKVersion\<=22
new app
对于new app,使用未授权的系统接口,会崩溃:
权限声明
声明app已经支持新权限系统
只需要在打包的时候,将targetSdkVersion 设置为23
声明要使用的权限
无论是new app还是old app,都需要在manifest里面,声明要使用的权限
1 | <uses-permission android:name="android.permission.READ_PHONE_STATE"/> |
如果是普通权限,只要声明在manifest中,就可以自动获授权。
如果是涉及用户隐私的敏感权限,需要app主动调用系统接口,然后系统会弹出确认页面,让用户授权。
敏感权限
如果app获取了某个敏感权限,同组的权限会自动获取到。
比如app获取了 读取联系人 的权限,那么app在申请 修改联系人 的权限时,会自动授权通过。
敏感权限 如下表格:
权限组 | 权限列表 |
---|---|
通讯录 | 修改您的通讯录, 查找设备上的帐户, 读取您的通讯录 |
电话 | 读取通话记录, 读取手机状态和身份, 使用即时通讯通话服务, 直接拨打电话号码, 写入通话记录, 拨打/接听SIP电话, 重新设置外拨电话的路径, 添加语音邮件 |
日历 | 读取日历活动和机密信息, 添加或修改日历活动,并在所有者不知情的情况下向邀请对象发送电子邮件 |
相机 | 拍摄照片和视频 |
安全 | 读取手机黑名单, 更改手机黑名单 |
身体传感器: | 人体传感器(如心跳速率检测器), 使用指纹硬件 |
位置信息 | 精确位置(基于GPS和网络), 大致位置(基于网络) |
存储空间 | 读取您的USB存储设备中的内容, 修改或删除您的USB存储设备中的内容 |
麦克风 | 录音 |
短信 | 读取您的讯息(短信或彩信), 接收讯息 (WAP), 接收讯息(彩信), 接收讯息(短信), 发送和查看短信, 读取小区广播消息 |
权限申请流程
需要权限的业务代码
以获取用户设备id举例例,tm.getDeviceId需要 READ_PHONE_STATE 这个权限。
1 | private void readDeviceId() { |
检查是否已经拥有权限
我们需要使用系统接口checkSelfPermission
检查是否拥有该权限。
在确认已经获得之后,才能调用业务代码,否则app会崩溃。
无论从前是否成功获得过授权,这个检查都是必须的。因为即使app在运行状态,用户也可以去设置中,修改允许的权限。
1 | checkSelfPermission(Manifest.permission.READ_PHONE_STATE) |
申请权限
如果没有该权限,我们需要调用requestPermissions
进行权限申请,第一个参数,是要申请的权限列表。第二个参数是请求id,在回调的时候用到。
1 | if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) |
处理回调
权限申请的回调,是通过覆写父类函数来处理的。(权限申请页面,其实是一个activity)
我们需要在activity或fragment中,覆写onRequestPermissionsResult
,然后根据requestCode判断是哪次权限申请的结果,来进行相应的处理:
1 | @Override |
处理“不再询问”
如果用户之前拒绝过,当我们再次发起权限申请的时候,系统对话框会多出一个勾选框 不再询问 ,如下图是前后对比:
一旦用户 勾选 了不再询问,我们以后的权限申请,会被系统 忽略 。
所以,在进行第二次权限申请前,需要先向用户解释——为啥我们需要这个权限,当用户接受我们的解释之后,再去进行第二次权限申请
系统提供了一个接口 shouldShowRequestPermissionRationale
,用来获取这个解释的时机。如果它返回true,我们要显示一个界面(一般为对话框),向用户解释
1 | //检查是否已经拥有权限 |
showExplanationDialog
是我们自定义的弹出解释对话框,比如,显示一些描述性的文字, 当用户做出正面选择后(比如下面左图中的“去打开权限”),我们就弹出系统权限申请对话框(如下右图)
相关代码为:
1 | new AlertDialog.Builder(TasksActivity.this) |
不要在onResume中申请权限
测试代码的时候,发现一个现象:
当用户勾选不再询问,且拒绝授权后,onResume会死循环。
原因是申请权限的对话框,其实是一个activity。用户默认拒绝的情况下,每次申请权限,就会启动它,由于默认拒绝,它会自动关闭,它关闭后会触发我们onResume中的申请权限,然后又启动这个activity,然后它关闭后又触发我们申请权限。。。
最后形成了死循环。
旧机型兼容
上述系统接口,都是在sdk 23及以上才提供,为适配旧机型,只需把相关接口替换为support库中的ActivityCompat.xxx
即可,比如ActivityCompat. requestPermissions
特殊权限申请
显示悬浮窗 在老版本中,只需要声明以下权限即可:
android.permission.SYSTEM_ALERT_WINDOW
在Android 6.0以后, 需要补充使用特殊的方式,进行申请的,会打开系统的设置页面:
1 | //检查是否有显示悬浮窗的权限 |
效果如下图
否则,没有此权限的new app,会直接崩溃:
Unable to add window. Permission denied for this window type
AOP权限申请方案
可以发现,为了适配Android 6.0的权限系统,需要在Activity或Fragment里面加入一大堆冗余代码,对原本的业务造成了很大污染。
AOP是为了解决这种痛点而生的。在Android中,比较优秀的AOP技术,是aspectj。
介绍一套基本无侵入的权限方案。
用法
在需要权限的方法上面,打上Annotation即可:
1 | @RequestPermission(Manifest.permission.READ_PHONE_STATE) |