引言

Android 6.0 (API 23) 开始引入了运行时权限检查 (Permissions at Run Time),用户不需要在安装时同意授予应用权限,而是在应用运行时动态去申请所需要的权限,由用户决定是否授予权限,这样可以让用户更灵活的控制授予应用的权限,而不是必需在应用安装时就授予或者不授予应用请求的所有权限。例如一个应用同时申请了使用相机和定位服务的权限,用户可以只授该应用访问相机的权限,而不授予定位的权限。

权限的分类

不是所有权限都必须动态申请的,系统权限被分为两类:普通权限 (Normal Permissions),敏感权限(Dangerous permissions)。

  • 普通权限:不涉及到用户敏感信息,应用只需要在 AndroidManifest 声明,用户同意安装应用,系统就会自动授予相应的权限,和 API 23 以前版本保持一致。
  • 敏感权限:能够访问用户的敏感信息,如使用相机、联系人等权限。申请敏感权限时不仅需要在 AndroidManifest 声明,还需要在运行时申请,用户同意后才能获取到该权限。

不同 Android 版本对权限的处理

对于普通权限,Android 6.0 的处理方式是一样,即只要在 AndroidManifest 声明,用户安装了应用就会获取到权限。但对于敏感权限的处理就有所不同了,由于运行时权限是在 Android 6.0 才开始引入的,所以那些运行在老版本上的应用是没有对运行时权限做处理的,为了兼容旧版本的应用,Android 6.0 必须做不同处理。

  • 在 Android 5.1 或者版本更低的设备上,Android 对普通和敏感权限的处理都是一样的,必须在 AndroidManifest 声明,用户也必须在安装时就授予相应的权限。
  • 在 Android 6.0 或者更高版本的设备上,要看应用的 target SDK 版本,如果应用的 target SDK 版本是 23 或者更高,那么应用就必须对运行时权限做处理,如果要申请敏感权限在 AndroidManifest 中声明的同时,还需要在运行时动态请求相应的敏感权限,否则将无法获取敏感权限;如果应用的 target SDK 版本是 22 或者更低,并且在 AndroidManifest 中声明了敏感权限,那么用户必须在安装是就授予权限,运行时 Android 会默认应用拥有敏感权限,但这并不是说 target SDK 22 以下的应用就不用处理运行时敏感权限了,用户还是随时可以在应用的权限管理界面关掉相应的权限的,如果权限被用户手动关掉,那么运行时应用是没有权限的,所以应用在运行时还是要判断是否获得了所需要的权限。

如何检查应用是否有所需的敏感权限

如果应用需要敏感权限,那么在应用每次运行的时候都需要检查是否拥有权限,因为用户可以随时在权限管理界面关掉权限,运行以下代码检查应用是否具有权限:

int permission = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_CALENDAR);

如果 permission 等于 PackageManager.PERMISSION_GRANTED 则代表应用已经拥有权限了,反之如果 permission 等于 PackageManager.PERMISSION_DENIED 则代表应用没有获得权限,需要再次申请。

如何在运行时申请敏感权限

这里以读取联系人权限为例:

if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {

    // 是否要显示问什么要获取权限的解释界面
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,Manifest.permission.READ_CONTACTS)) {
      // 显示解释权限用途的界面,然后再继续请求权限
    } else {
      // 没有权限,直接请求权限
        ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS},
          MY_PERMISSIONS_REQUEST_READ_CONTACTS);
    }
}

代码执行时会弹出系统默认的请求权限对话框

请求权限对话框.png

上面一段有几处需要注意的:

  1. shouldShowRequestPermissionRationale 函数是用来判断是否需要显示解释为什么需要该权限的界面,该函数在应用第一次安装的时候会返回 false,因此你可以直接请求任何需要的权限。如果用户以前拒绝了一个请求,这个方法将返回 true,那样的话你应该考虑在再次触发权限对话框之前显示一个解释请求用途之类的信息。当应用完全没有机会被授权的时候,该函数也会返回 false,比如用户在权限对话框中选择了”不再显示”,结果为 false 说明用户明确不想授权,再弹解释界面也是没用。
  2. ActivityCompat.requestPermissions 这里我们调用的是 ActivityCompat 的 requestPermissions,用户选择完成后会回调该 Activity 的 onRequestPermissionsResult,但如果在 Fragment 处理请求最好调用 FragmentCompat.requestPermissions (需要 android.support.v13),这样处理结果会回调到 Fragment 的 onRequestPermissionsResult。
  3. MY_PERMISSIONS_REQUEST_READ_CONTACTS 是一个 int 型值,是权限请求的标志,会在 onRequestPermissionsResult 中返回,用来标志该回调是对应哪个请求的。

处理权限请求结果

如上面所说的,用户选择之后的结果会回调到 onRequestPermissionsResult 函数,根据 requestCode 和 grantResults 判断结果:

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
              // 权限申请通过!
            } else {
              // 悲剧了,用户不给权限
            }
            return;
        }
    }
}