前言
在TargetSdk<23时,可以不动态申请权限,但是如果用户禁止了某些权限,就会导致奔溃问题。现在越来越多的应用市场要求应用TargetSdk>=26,不得不处理动态权限了。日常开发中,能够区分普通权限和危险权限,了解动态权限请求和处理的流程,注意一些常见的BUG就可以了,使用的话就封装个工具类或使用第三方库,比如AndPermission、RxPermission。
1.权限的定义和级别
Android权限可以分为普通权限、危险权限、系统权限、自定义权限,其中最常用就是普通权限和危险权限。在AndroidManifest.xml中申请权限一般都是一句话,看源码的AndroidManifest.xml可以看到还有别的参数。
//正常开发使用的
<!--普通权限,访问网络-->
<uses-permission android:name="android.permission.INTERNET" />
<!--危险权限,存储-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
//源码的
<!--普通权限,访问网络-->
<permission android:name="android.permission.INTERNET"
android:description="@string/permdesc_createNetworkSockets"
android:label="@string/permlab_createNetworkSockets"
android:protectionLevel="normal|instant" />
<!--危险权限,存储-->
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardRead"
android:description="@string/permlab_sdcardRead"
android:protectionLevel="dangerous" />
1)permissionGroup
危险权限才有这个属性--权限组,共9组,分别是联系人、通话、日历、相机、传感器、定位、存储、音视频/录音、短信权限。某个权限授权了,则同一组的其他权限也会授权,可以说是对权限组授权了。但是也有文章说,Android8.0后,对某个权限授权,同一组的其他权限不会授权,还要再去发起请求。(目前没有遇到过,在项目中我都是把需要的权限都写上去的。)
2)protectionLevel
权限级别如下
normal:普通权限。
dangerous:危险权限,要动态申请。
signature: 只有和定义该权限者具有相同签名的应用才可以申请该权限,系统权限则需要和系统相同签名。
//下面4个在源码搭配signature使用。
privilegad: 有特权的,配合signature就是特权app(包括系统app)才能使用的权限。
development: 特殊开发工具的权限
instant: 瞬间、片刻,感觉应该是实时的意思,因为都是在定位、相机、网络等写的。
verifier: 验证
3)危险权限具体可以参考下图
<!--联系人-->
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<!--电话-->
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<!--日历-->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!--相机拍照-->
<uses-permission android:name="android.permission.CAMERA" />
<!--传感器-->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!--定位权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--存储-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--音频-->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!--短信-->
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
2.<uses-feature>是什么?
在AndroidManifest.xml中,经常发现有<uses-feature ../>,因为和<uses-permission ../>类似,以为和权限相关。其实<uses-feature>是对外提供了一组信息,表明它所依赖的硬件特征,这个信息是给应用市场使用的,应用市场读取use-feature的设置,在分发app时就自动过滤掉那些不支持的设备。比如app在use-feature声明需要摄像头,那么没有摄像头的设备就没法在应用市场搜索和下载该app。如果是通过别的渠道下载了app,那么也是可以正常安装打开的,但使用摄像头的功能肯定受到影响。
Google Play要求app必须显式声明所需要的features,也就是说<uses-permission>申请了相机,那么features也要声明,否则可能导致审核失败。国内的应用市场倒不会管这个。
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.Camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
3.动态权限请求与处理的流程
在AndroidManifest,xml申请权限,再在代码里动态请求危险权限。判断是否已授权,没授权就发起请求,在请求权限的回调方法里判断grantResults值,0是允许,1是拒绝。方法4不用也行。
//1.判断是否已经授权
ContextCompat.checkSelfPermission(context,permissions[i]) != PackageManager.PERMISSION_GRANTED)
//2.请求权限
ActivityCompat.requestPermissions(context,permissions,mRequestCode);
//3.请求权限的回调 grantResults值为0允许,1拒绝
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
//4.是否需要给用户一个权限请求的原因说明,在方法3中,如果grantResults返回1拒绝,则再调用这个方法,询问是否显示权限请求原因。
ActivityCompat.shouldShowRequestPermissionRationale(context,permissions[i])
对方法4的返回值的理解是,如果用户点了拒绝,那么最好给用户一个提示说明该权限用来做什么的,所以返回true。如果户允许了权限 or 点了不再询问,那么没必要再提示用户请求权限的原因了,所以返回false。
4.问题
1)动态申请<uses-permission android:name="android.permission.CALL_PHONE" />,发现 ContextCompat.checkSelfPermission(...) 总是返回 PackageManager.PERMISSION_GRANTED,无法检测这个权限是否授权,在用 new Intent(Intent.ACTION_CALL) 拨打电话时注意。
参考
动态权限申请和处理
https://www.jianshu.com/p/8e37e9cf20a5
权限大全
https://blog.csdn.net/qq_26440221/article/details/53097868
https://blog.csdn.net/xhaotianshenjian/article/details/81535259
与权限有关的bug
https://www.jianshu.com/p/dbc88c679449
https://blog.csdn.net/tuke_tuke/article/details/78569165