从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的“Settings”屏幕调用权限。
GitHub地址:https://github.com/AndroidSupport/LightPermission
权限分类
在6.0后,请求权限分为两种,一种是正常权限,另一种为危险权限
- 正常权限:不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
- 危险权限: 涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。
正常权限
从Android 9(API级别28)
开始,以下权限 Normal Permission(API 28)分类为正常权限
危险权限
权限组 | 权限 |
---|---|
CALENDAR | READ_CALENDAR,WRITE_CALENDAR |
CALL_LOG | READ_CALL_LOG,WRITE_CALL_LOG,PROCESS_OUTGOING_CALLS |
CAMERA | CAMERA |
CONTACTS | READ_CONTACTS,WRITE_CONTACTS,GET_ACCOUNTS |
LOCATION | ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION |
MICROPHONE | RECORD_AUDIO |
PHONE | READ_PHONE_STATE,CALL_PHONE,READ_CALL_LOG,WRITE_CALL_LOG,ADD_VOICEMAIL,USE_SIP,PROCESS_OUTGOING_CALLS |
SENSORS | BODY_SENSORS |
SMS | SEND_SMS,RECEIVE_SMS,READ_SMS,RECEIVE_WAP_PUSH,RECEIVE_MMS |
STORAGE | READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE |
- 如果设备运行Android 6.0(API级别23)且应用程序
targetSdkVersion
为23或更高,则当您的应用请求危险权限时,以下系统行为适用:- 如果应用程序当前在权限组中没有任何权限,系统会向用户显示描述应用程序要访问的权限组的权限请求对话框。该对话框未描述该组中的特定权限。如果用户授予批准,系统将为应用程序提供其请求的权限。
- 如果应用程序已在同一权限组中被授予另一个危险权限,则系统会立即授予权限,而不与用户进行任何交互。
- 如果设备运行的是Android 5.1(API级别22)或更低版本,或者应用程序
targetSdkVersion
为22或更低,则系统会要求用户在安装时授予权限。
判断权限
由于Android有targetSdkVersion
的概念,所以在编写代码的时候,不仅需要考虑手机自身的版本,也需要考虑targetSdkVersion
的版本(emmmm。。。。好像说的有点绕-。-)
- 当手机系统版本低于6.0时,系统会给出所有在
AndroidManifest.xml
中列出来的权限 - 当Android >= 6.0 && targetSdkVersion < 23时,由于targetSdkVersion 的原因,系统会给出所有在
AndroidManifest.xml
中列出来的权限;但因为Android >= 6.0,因此用户可以自己去设置关操作更改权限。因为需要使用PermissionChecker.checkSelfPermission(context, permission)
进行判断 - 当 targetSdkVersion >= 23时,通过
ContextCompat.checkSelfPermission(context, permission)
判断权限
public static boolean hasPermissions(@NonNull Context context, @NonNull @Size(min = 1) String... permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
} else if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) {
for (String permission : permissions) {
if (PermissionChecker.checkSelfPermission(context, permission)
!= PermissionChecker.PERMISSION_GRANTED) {
return false;
}
}
} else {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
申请权限
- Activity
ActivityCompat.requestPermissions(activity, permissions, requestCode);
- Fragment
fragment.requestPermissions(permissions, requestCode)
解释应用为什么需要权限
查阅API源码,我们会发现有一个shouldShowRequestPermissionRationale
方法
/**
* Gets whether you should show UI with rationale for requesting a permission.
* You should do this only if you do not have the permission and the context in
* which the permission is requested does not clearly communicate to the user
* what would be the benefit from granting this permission.
* <p>
* For example, if you write a camera app, requesting the camera permission
* would be expected by the user and no rationale for why it is requested is
* needed. If however, the app needs location for tagging photos then a non-tech
* savvy user may wonder how location is related to taking photos. In this case
* you may choose to show UI with rationale of requesting this permission.
* </p>
*
* @param activity The target activity.
* @param permission A permission your app wants to request.
* @return Whether you can show permission rationale UI.
*
* @see #checkSelfPermission(android.content.Context, String)
* @see #requestPermissions(android.app.Activity, String[], int)
*/
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
@NonNull String permission) {
if (Build.VERSION.SDK_INT >= 23) {
return activity.shouldShowRequestPermissionRationale(permission);
}
return false;
}
大意是说在某些时刻(比如为什么调用相机需要定位权限),应该向用户解释为什么需要这个权限。
如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
注:
1.如果用户拒绝权限的同时,并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。
2.当请求权限而用户选择拒绝时,shouldShowRequestPermissionRationale
一定返回true
因此如果开发者想向用户解释为什么需要权限时,完全可以在拒绝权限的回调中解释,将该方法作为是否勾选Don't ask again
的一个判断
因为在该情况下,请求权限时系统不会弹出对话框,而且直接拒绝权限请求。该过程对于用户来说是不可知的,因此我们可以在这时提示用户,或者直接跳转到app的权限设置页
结果回调
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
}
由于国内手机各种厂家定制以及一些奇奇怪怪的设置,以上结果回调即使返回 PackageManager.PERMISSION_GRANTED
,也不一定是真的拥有该权限。如果在这种情况下去使用应该要有权限的操作,基本上是要挂的。
以下是笔者曾经踩过并有记录的一个坑,感兴趣的童鞋可以看一下
关于小米系列手机权限设置无效的报告
此处推广以下自己写的权限管理库 LightPermission
大致就是对以上流程的一个封装,也有对国内厂家定制所引起的返回有权限但实际没有权限的方案修改。
目前而言只是一个轻量级的权限判断库,1.1.0版本已经适配了androidx
后续会补充链式调用方法,感兴趣的同学也可以在下面或者issue中提拱建议~~
网友评论