剑客
关注科技互联网

Android 6.0运行时请求权限

Android权限简介

从Android 6.0开始,部分 危险权限 需要在运行时用户动态授权,因为一个Android应用默认情况下是不拥有任何权限的。在开发的时候,我们会在AndroidManifest.xml中静态地声明相应的权限,如果没有声明该权限却使用了相应的权限,程序会崩溃,抛出异常,例如,如果没有在程序中声明网络权限当我们使用网络的时候,就会抛出如下异常,而且一般不会try catch该异常:

Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)

Android 6.0运行时请求权限

但是在Android6.0上面部分权限不但要求开发者在使用之前在AndroidManifest.xml声明,还需要用户在使用的时候进行授权才可以使用,例如用户想使用拍照上传图片,虽然已经声明了使用相机的权限,如果用户拒绝使用相机权限,还是会抛出异常:

Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE cmp=com.android.camera/.Camera }…

这时候如果程序员没有对Android6.0手机进行额外权限的处理,程序就会直接崩掉了,给用户带来很不好的体验。

刚刚看到一篇新闻,《Android 7.1.1将于12月5日正式登陆Nexus设备》,Android7.x的手机都要问世了,针对Android M以上的手机动态权限确实需要开发者认真研究一下了。

正常权限和危险权限

事实上Android系统对权限声明有四个等级,主要通过protectionLevel来设置,具体可以参考 https://developer.android.com/guide/topics/manifest/permission-element.html

<permissionandroid:description="string resource"
 android:icon="drawable resource"
 android:label="string resource"
 android:name="string"
 android:permissionGroup="string"
 android:protectionLevel=["normal" | "dangerous" |
 "signature" | "signatureOrSystem"]/>

签名相关的两个权限并不常用,在开发中我们真正需要针对Android6.0以上系统进行特殊处理的都是危险权限,更为详细的介绍可以参看 https://developer.android.com/guide/topics/security/permissions.html

系统权限分为两类: 正常权限危险权限

  • 正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
  • 危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。

之所以在这里列出来 正常权限危险权限 ,主要就是因为Android6.0在这里做了一个分割线:

  • 如果设备运行的是 Android 5.1 或更低版本, 或者 应用的目标 SDK 为 22 或更低:如果您在清单中列出了危险权限,则用户必须在安装应用时授予此权限;如果他们不授予此权限,系统根本不会安装应用。
  • 如果设备运行的是 Android 6.0 或更高版本, 或者 应用的目标 SDK 为 23 或更高:应用必须在清单中列出权限, 并且 它必须在运行时请求其需要的每项危险权限。用户可以授予或拒绝每项权限,且即使用户拒绝权限请求,应用仍可以继续运行有限的功能。

权限组

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

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

任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。再次强调,系统只告诉用户应用需要的权限组,而不告知具体权限。

权限组 权限
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

动态权限处理逻辑

Android6.0动态权限处理一般有三个步骤:

  1. 检查权限;
  2. 请求权限;
  3. 处理请求相应回调。

虽然Android6.0已经新增了针对动态权限的相关实现方法,但是为了使用方便我们使用support支持看,这样免去了判断Android版本的繁琐,针对Activity和Fragment支持库都提供了相应的方法。

int ContextCompat.checkSelfPermission(Contextcontext, String permission)
void ActivityCompat.requestPermissions(Activityactivity, String[] permissions, int requestCode)
boolean ActivityCompat.shouldShowRequestPermissionRationale(Activityactivity, String permission)
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
//fragment
void requestPermissions(String[] permissions, int requestCode)

检查权限

当我们开发的应用在操作是需要一个危险的权限,在执行操作时应该检查一下是否具有该权限。检查是否具有某项权限,可以调用 ContextCompat.checkSelfPermission() 方法。如果应用具有该权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。

下面是一个调用相机的权限:

int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (permission == PackageManager.PERMISSION_GRANTED) {
 //执行拍照
} else {//请求权限
 ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA }, PHOTO);
}

请求权限

在第一步中我们检查权限,如果没有相应的权限即去请求权限,请求权限时,我们可以同时请求多个权限,也可以单个权限执行请求。调用请求权限时系统会弹出来一个标准的Android对话框,开发者不能进行自定义。

Android 6.0运行时请求权限

当我们同时请求多个权限时,会在系统单个对话框中顺序显示多个权限授权操作,下面代码是一次请求多个权限:

String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };
ActivityCompat.requestPermissions(this, permissions, PHOTO);

Android 6.0运行时请求权限

处理请求相应回调

当我们响应系统请求权限对话框时,系统将调用应用的 onRequestPermissionsResult() 方法,在回调中继续处理我们的逻辑操作,如果用户拒绝了我们要求的权限,我们可以在这里进行必要的解释,告诉用户为什么需要此权限。当然了不是每条权限都需要解释,太多的解释反而会降低用户体验。

为了帮助开发者提供需要解释的情形,Android提供了 shouldShowRequestPermissionRationale()方法,部分手机上面该方法不起作用,主要是由于Android手机深度定制导致的。

  • 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
  • 如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,此方法将返回 false。
  • 如果设备规范禁止应用具有该权限,此方法也会返回 false。

Android 6.0运行时请求权限

下面是一个对执行拍照请求权限后的回调:

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
 switch (requestCode) {
 case PHOTO:
 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 //拍照
 } else {
 if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
 Manifest.permission.CAMERA)) {
 //提供必要的解释,为什么需要该权限
 }else{
 Toast.makeText(MainActivity.this, "授权失败!", 0).show();
 }
 }
 break;
 default:
 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 break;
 }
}

权限拒绝后跳转至应用的设置界面

在动态请求权限处理时,有一种简单粗暴的方式,我们一下将应用中涉及到的危险权限一次性全部请求,如果用户不同意,直接提示用户需要相应的权限才可以使用该应用,然后跳转到应用的设置界面,当然了由于Android手机种类多样,下下面这种方法不一定总是可行,不过在小米、魅族、华为和三星手机上验证了部分手机都是可行的。

IntentlocalIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(localIntent);

在Android6.0之前手机设置界面基本是这样的,所有申请的权限如下排列:

Android 6.0运行时请求权限

在Android6.0之后新增了权限相关的设置信息:

Android 6.0运行时请求权限

Fragment处理权限请求回调

在使用Fragment处理回调是不要使用ActivityCompat.requestPermissions方法,虽然使用该方法不会报错,直接使用Fragment中的requestPermissions方法即可,否则就会将回调返回到相应的Activity。

如果Fragment嵌套了子Fragment,在子Fragment中使用requestPermissions方 法,onRequestPermissionsResult不会回调回来,建议使用 getParentFragment().requestPermissions方法,这个方法会回调到父Fragment中的onRequestPermissionsResult,加入以下代码可以把回调透传到子Fragment。

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {  
 super.onRequestPermissionsResult(requestCode, permissions, grantResults);  
 List<Fragment> fragments = getChildFragmentManager().getFragments();  
 if (fragments != null) {  
 for (Fragmentfragment : fragments) {  
 if (fragment != null) {  
 fragment.onRequestPermissionsResult(requestCode,permissions,grantResults);  
 }  
 }  
 }  
}

权限的最佳做法

这里所说的最佳做法来自 https://developer.android.com/training/permissions/best-practices.html

  • 考虑使用 intent
  • 仅要求您需要的权限
  • 不要让用户感到无所适从
  • 测试两种权限模式

另外可以参考google在github上的动态请求权限的示例或者使用第三方的库来处理权限:

android-RuntimePermissions: https://github.com/googlesamples/android-RuntimePermissions

easypermissions: https://github.com/googlesamples/easypermissions

PermissionsDispatcher: https://github.com/hotchemi/PermissionsDispatcher

RxPermissions: https://github.com/tbruyelle/RxPermissions

Grant: https://github.com/anthonycr/Grant

参考资料

Android M Permission 运行时权限 学习笔记

聊一聊Android 6.0的运行时权限

权限最佳做法

系统权限

在运行时请求权限

安卓6.0新特性在Fragment申请运行时权限

android 6.0权限全面详细分析和解决方案

Android 6.0 运行时权限处理

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址