1. Android 6.0 在运行时请求权限介绍
从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的设置页面修改权限。
1.1、为什么需要运行时请求权限
iPhone上的App都是默认下载安装的,然后运行App时需要什么权限就弹窗向用户申请,这对用户来说就非常好。因为用户不想给App权限就不给,而Android 6.0以前是这样的,我下载了一个App安装,系统就弹出这个App需要使用的全部的权限,就给我看一下,我需要这个App 的话,只能同意所有的权限都给这个App,要么我不安装这个App。
1.2、 Android权限介绍
系统权限分为两类:正常权限和危险权限:
正常权限不会直接给用户隐私权带来风险。如果您的应用在其manifest中列出了正常权限,系统将自动授予该权限。
危险权限会授予应用访问用户机密数据的权限。如果您的应用在其manifest中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限,也就是说manifest文件中定义的危险权限将不会随着安装自动授予。
表 1.危险权限和权限组。
权限组权限
从上图中我们可以看到,Android系统把危险权限分了9大组,这样也是为了简化权限的申请机制。如果你申请了android.permission.READ_CONTACTS读取联系人的权限,那么6.0 系统就会把这一组中其他的权限也打包给你。我觉得这个和iOS的隐私管理机制非常相似,在iOS系统设置的“隐私->通讯录”中可以看到,如果你给一个App通讯录的权限,那么这个App既可以读也可以写的
Android 6.0里面只有危险权限才需要运行时获取的
1.3、android系统对权限的处理场景分析
例如以下图片中用户在android6.0的版本的设置中把权限关闭,此时你的权限就用不了了。那么程序需要考虑对6.0及以上版本的兼容,具体参考下面的(android.support.v4.content.PermissionChecker)。
2、如何申请权限
申请权限过程包括以下几个步骤:
检查权限
请求权限
处理权限请求响应
解释应用为什么需要权限
2.1、检查权限
如果您的应用需要危险权限,则每次执行需要这一权限的操作时您都必须检查自己是否具有该权限。用户始终可以自由调用此权限,因此,即使应用昨天使用了相机,它不能假设自己今天仍具有该权限,因为用户可能在设置里面关闭了。
// 假设thisActivity是当前屏幕最前端正在和用户交互的activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
也可以使用ActivityCompat,它们两个的checkSelfPermission方法是同一个,因为ActivityCompat继承自ContextCompat,而checkSelfPermission是ContextCompat中的方法。
检查权限会有一些特别的问题需要注意,主要有以下两个:
如果App的targetSdkVersion小于23并且运行在Android 6.0系统上,怎么去检测用户关闭了权限呢?
国内有些手机厂商自己定制了手机权限管理(例如:小米),普通的checkSelfPermission已经不准确了,该如何处理?
问题一:
android.support.v4.content.PermissionChecker可以帮我们解决这个问题。这个类的文档是这么描述的:
For apps targeting API lower thanandroid.os.Build.VERSION_CODES.Mthese 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_GRANTED: 已授权
PERMISSION_DENIED: 没有被授权
PERMISSION_DENIED_APP_OP: 没有被授权
PERMISSION_DENIED和PERMISSION_DENIED_APP_OP都表示没有被授权,但是它们的区别就在于targetSdkVersion的值,如果targetSdkVersion小于23,就返回PERMISSION_DENIED_APP_OP,否则就返回PERMISSION_DENIED
因此,如果你的App的targetSdkVersion小于23,但是运行在Android 6.0及以后的系统上,你可以用下面的代码来检测app是否有某个权限:
PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker. PERMISSION_DENIED_APP_OP
问题二:
AppOpsManager官方的解释是系统内部使用,不提供给APP开发者使用。一个小米设备兼容判断的代码如下:
@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;}
2.2、请求权限
如果您的应用需要应用manifest文件中列出的危险权限,那么,它必须要求用户授予该权限。Android 为您提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框,不过,您不能对它们进行自定义。
提示用户授予或拒绝权限的系统对话框如下:
以下代码可以检查应用是否具备读取用户联系人的权限,并根据需要请求该权限:
// 这里的 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定义的整形常量,用于标识一个请求,回调方法中会获得这个请求对应的结果
}
}
2.3、处理权限请求响应
@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' 代码去处理其它的权限请求回调
}
}
注意:
2.4 解释应用为什么需要权限
在某些情况下,您可能需要帮助用户了解您的应用为什么需要某项权限。例如,如果用户启动一个摄影应用,用户对应用要求使用相机的权限可能不会感到吃惊,但用户可能无法理解为什么此应用想要访问用户的位置或联系人。在请求权限之前,不妨为用户提供一个解释。请记住,您不需要通过解释来说服用户;如果您提供太多解释,用户可能发现应用令人失望并将其移除。
您可以采用的一个方法是仅在用户已拒绝某项权限请求时提供解释。如果用户继续尝试使用需要某项权限的功能,但继续拒绝权限请求,则可能表明用户不理解应用为什么需要此权限才能提供相关功能。对于这种情况,比较好的做法是显示解释。
注:如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了Don't ask again选项,此方法将返回false。如果设备规范禁止应用具有该权限,此方法也会返回false。