从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法能够简化应用安装过程,由于用户在安装或更新应用时不须要授予权限。它还让用户能够对应用的功能进行更多控制;例如,用户能够选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户能够随时进入应用的设置页面修改权限。html
iPhone上的App都是默认下载安装的,而后运行App时须要什么权限就弹窗向用户申请,这对用户来讲就很是好。由于用户不想给App权限就不给,而Android 6.0之前是这样的,我下载了一个App安装,系统就弹出这个App须要使用的所有的权限,就给我看一下,我须要这个App 的话,只能赞成全部的权限都给这个App,要么我不安装这个App。前端
系统权限分为两类:java
表 1. 危险权限和权限组。android
权限组 | 权限 |
---|---|
CALENDAR | |
CAMERA | |
CONTACTS | |
LOCATION | |
MICROPHONE | |
PHONE | |
SENSORS | |
SMS | |
STORAGE |
从上图中咱们能够看到,Android系统把危险权限分了9大组,这样也是为了简化权限的申请机制。若是你申请了android.permission.READ_CONTACTS读取联系人的权限,那么6.0 系统就会把这一组中其余的权限也打包给你。我以为这个和iOS的隐私管理机制很是类似,在iOS系统设置的“隐私->通信录”中能够看到,若是你给一个App通信录的权限,那么这个App既能够读也能够写的数组
Android 6.0里面只有危险权限才须要运行时获取的app
例如如下图片中用户在android6.0的版本的设置中把权限关闭,此时你的权限就用不了了。那么程序须要考虑对6.0及以上版本的兼容,具体参考下面的(android.support.v4.content.PermissionChecker)。异步
值得注意的是Android系统有一套自动权限调整的机制,咱们知道android每次sdk升级有可能会加入新的权限,而你的app已经发布到用户手机上安装了,除了升级不可能修改Androidmanifest文件了,此时你可能担忧本身的app可以在这些新的sdk版本的手机上运行正常吗,其实android已经考虑了这种场景,Android 将根据为 targetSdkVersion 属性提供的值决定应用是否须要权限。若是该值低于在其中添加权限的版本,则 Android 会为App自动添加该权限。ide
例如,API 级别 4 中加入了 WRITE_EXTERNAL_STORAGE 权限,用以限制访问共享存储空间。若是您的 targetSdkVersion 为 3 或更低版本,则会向更新 Android 版本设备上的应用添加此权限。ui
申请权限过程包括如下几个步骤:this
若是您的应用须要危险权限,则每次执行须要这一权限的操做时您都必须检查本身是否具备该权限。用户始终能够自由调用此权限,所以,即便应用昨天使用了相机,它不能假设本身今天仍具备该权限,由于用户可能在设置里面关闭了。
要检查您是否具备某项权限,请调用 ContextCompat.checkSelfPermission() 方法。例如,如下代码段显示了如何检查 Activity 是否具备在日历中进行写入的权限:
1
2
3
|
// 假设thisActivity是当前屏幕最前端正在和用户交互的activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
|
若是应用具备此权限,方法将返回 PackageManager.PERMISSION_GRANTED,而且应用能够继续操做。若是应用不具备此权限,方法将返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。
也可使用ActivityCompat,它们两个的checkSelfPermission方法是同一个,由于ActivityCompat继承自ContextCompat,而checkSelfPermission是ContextCompat中的方法。
检查权限会有一些特别的问题须要注意,主要有如下两个:
android.support.v4.content.PermissionChecker能够帮咱们解决这个问题。这个类的文档是这么描述的:
For apps targeting API lower than android.os.Build.VERSION_CODES.M these permissions are always granted as such apps do not expect permission revocations and would crash. Therefore, when the user disables a permission for a legacy app in the UI the platform disables the APIs guarded by this permission making them a no-op which is doing nothing or returning an empty result or default error.
翻译一下是:
当app的targetsdkversion小于23的时候,这些权限默认都会自动给当前app,但若是app没有考虑在6.0设备中被用户主动撤销该权限的场景,那么可能形成app的崩溃。因而app在使用该权限过程当中系统权限检查时若是这个权限被用户撤销了,那么对应请求的API会什么都不作或者返回一个空的结果,或者出错。
PermissionChecker.checkSelfPermission方法就是用于检查App自身有没有某一个权限,这个方法的返回结果只有三种:
PERMISSION_DENIED和PERMISSION_DENIED_APP_OP都表示没有被受权,可是它们的区别就在于targetSdkVersion的值,若是targetSdkVersion小于23,就返回PERMISSION_DENIED_APP_OP,不然就返回PERMISSION_DENIED
所以,若是你的App的targetSdkVersion小于23,可是运行在Android 6.0及之后的系统上,你能够用下面的代码来检测app是否有某个权限:
1
|
PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker. PERMISSION_DENIED_APP_OP
|
国产不少手机在google以前已经作了本身的权限管理,例如小米,因此即便运行在6.0的手机系统上面并使用ContextCompat的checkSelfPermission方法即使返回 PackageManager.PERMISSION_GRANTED 也可能不许确。若是出现这种状况咱们须要作一次特殊处理,此时咱们须要用到android的隐藏API — AppOpsManager
AppOpsManager官方的解释是系统内部使用,不提供给APP开发者使用。一个小米设备兼容判断的代码以下:
1
2
3
4
5
6
7
8
9
10
|
@TargetApi(Build.VERSION_CODES.M)
private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
String op = AppOpsManager.permissionToOp(permission);
if (!TextUtils.isEmpty(op)) {
int checkOp = appOpsManager.checkOp(op, Process.myUid(), context.getPackageName());
return checkOp == AppOpsManager.MODE_ALLOWED && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
}
return true;
}
|
如上面所说小米这样的手机已经在android 6.0 以前已经有了本身的权限管理,若是你也想判断小于6.0的小米手机是否赋予了某个权限也是能够作到的,仍是用到AppOpsManager,可是因为AppOpsManager在API 19才引入的,因此使用时须要指定 @TargetApi(19),示例代码以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@TargetApi(19)
private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {
Object opsManager = context.getSystemService(Context.APP_OPS_SERVICE);
Class<?> clazz = opsManager.getClass();
try {
Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
int checkOp = (Integer) dispatchMethod.invoke(opsManager, new Object[] {AppOpsChecker.OP_CAMERA, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });
return checkOp == AppOpsManager.MODE_ALLOWED && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return true;
}
|
这个 AppOpsChecker.OP_CAMERA 你就须要从源码中拷贝出来了,在API 19里面是引用不到的。
若是您的应用须要应用manifest文件中列出的危险权限,那么,它必需要求用户授予该权限。Android 为您提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框,不过,您不能对它们进行自定义。
若是上面的权限检查步骤中结果是应用尚无所需的权限,则应用必须调用一个 requestPermissions() 方法,以请求适当的权限。应用将传递其所需的权限,以及您指定用于识别此权限请求的整型
提示用户授予或拒绝权限的系统对话框以下:
如下代码能够检查应用是否具有读取用户联系人的权限,并根据须要请求该权限:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 这里的 thisActivity 是当前屏幕最前端正在和用户交互的activity
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);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS 这个是app定义的整形常量,用于标识一个请求,回调方法中会得到这个请求对应的结果
}
}
|
注:当您的应用调用 requestPermissions() 时,系统将向用户显示一个标准对话框。您的应用
当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应。您的应用必须覆写该方法,以了解是否已得到相应权限。回调会将您传递的相同请求代码传递给 requestPermissions()。例如,若是应用请求 READ_CONTACTS 访问权限,则它可能采用如下回调方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@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;
}
// 其它的'case' 代码去处理其它的权限请求回调
}
}
|
注意:
当系统要求用户授予权限时,用户能够选择指示系统再也不要求提供该权限(即勾选对话框里的不在提示)。这种状况下,不管应用在何时使用 requestPermissions() 再次要求该权限,系统都会当即拒绝此请求。系统会调用您的 onRequestPermissionsResult() 回调方法,并传递 PERMISSION_DENIED
在某些状况下,您可能须要帮助用户了解您的应用为何须要某项权限。例如,若是用户启动一个摄影应用,用户对应用要求使用相机的权限可能不会感到吃惊,但用户可能没法理解为何此应用想要访问用户的位置或联系人。在请求权限以前,不妨为用户提供一个解释。请记住,您不须要经过解释来讲服用户;若是您提供太多解释,用户可能发现应用使人失望并将其移除。
您能够采用的一个方法是仅在用户已拒绝某项权限请求时提供解释。若是用户继续尝试使用须要某项权限的功能,但继续拒绝权限请求,则可能代表用户不理解应用为何须要此权限才能提供相关功能。对于这种状况,比较好的作法是显示解释。
为了帮助查找用户可能须要解释的情形,Android 提供了一个实用程序方法,即 shouldShowRequestPermissionRationale()。若是应用以前请求过此权限但用户拒绝了请求,此方法将返回 true。
注:若是用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,此方法将返回 false。若是设备规范禁止应用具备该权限,此方法也会返回 false。