Android 6.0适配权限申请

在Android 6.0 (API 23) 发布之前, 所有的权限都在安装应用的时候显示给用户(在清单文件中声明),用户确认安装则表示全部接受这些权限, 之后无法撤销对这些权限的授权.
Android 6.0开始, 一部分比较危险的权限需要在程序运行时显式弹框,显式请求用户授权。什么时候弹这个框,由应用程序自己决定.
对于其他认为不是很危险的权限,仍然保持原来的做法,在用户安装应用程序时就予以授权。
同时在设置中,对于应用的危险权限,用户可以选择性地进行授权或者关闭。

Permission介绍

官方文档-系统权限
所有需要使用的权限都需要在清单文件中声明:

1
2
3
4
5
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>

如果您的应用在其清单中列出正常权限(即,不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出危险权限(即,可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。
Android 发出请求的方式取决于系统版本,而系统版本是应用的目标:

  • 如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。如需了解有关在应用中请求权限的详细信息,请参阅使用系统权限培训指南。
  • 如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。

因此有些app可以通过设置targetSDK为22以下来一次性获取很多权限。

权限等级

系统权限分为几个保护级别。需要了解的两个最重要保护级别是正常权限和危险权限:

  • 正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。如需当前正常权限的完整列表,请参阅正常权限
  • 危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,能够读取用户的联系人属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。

权限组

所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:

  • 如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组,系统只告诉用户应用需要的权限组,而不告知具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
  • 如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限。

即用户同意权限组中的任意一个权限等同于同意整个权限组。

危险权限和权限组列表:

权限列表

动态申请权限

官方文档-申请权限教程

动态申请权限的大致步骤为:判断当前是否拥有该权限->如果没有则向用户申请权限->在onRequestPermissionsResult()方法中接收用户是否授予了权限,并进行相应操作。

检查是否拥有权限:

1
2
3
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);

对于拥有或没有权限做出不同的反应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {

// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.

} else {

// No explanation needed, we can request the permission.

ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);

// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}

onRequestPermissionsResult中接收用户请求的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
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.
}
return;
}

// other 'case' lines to check for other
// permissions this app might request
}
}

EasyPermission封装使用

引入依赖

1
2
3
4
5
6
7
8
dependencies {
// For developers using AndroidX in their applications
implementation 'pub.devrel:easypermissions:3.0.0'

// For developers using the Android Support Library
implementation 'pub.devrel:easypermissions:2.0.1'
}

申请权限:

1
2
3
4
5
6
7
8
9
10
11
12
@AfterPermissionGranted(RC_CAMERA_AND_LOCATION)
private void methodRequiresTwoPermission() {
String[] perms = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION};
if (EasyPermissions.hasPermissions(this, perms)) {
// Already have permission, do the thing
// ...
} else {
// Do not have permissions, request them now
EasyPermissions.requestPermissions(this, getString(R.string.camera_and_location_rationale),
RC_CAMERA_AND_LOCATION, perms);
}
}

接收回调方法:

1
2
3
4
5
6
7
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);

// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}

activity需要实现EasyPermissions.PermissionCallbacks接口,然后实现接口方法处理授予权限和拒绝权限的情况:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onPermissionsGranted(int requestCode, List<String> list) {
// Some permissions have been granted
// ...
}

@Override
public void onPermissionsDenied(int requestCode, List<String> list) {
// Some permissions have been denied
// ...
}