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.危险权限和权限组。
权限组权限
CALENDAR : READ_CALENDAR , WRITE_CALENDAR
CONTACTS : READ_CONTACTS , WRITE_CONTACTS , GET_ACCOUNTS
LOCATION : ACCESS_FINE_LOCATION , ACCESS_COARSE_LOCATION
PHONE READ_PHONE_STATE,CALL_PHONE,READ_CALL_LOG, WRITE_CALL_LOG,ADD_VOICEMAIL,USE_SIP,PROCESS_OUTGOING_CALLS
SMS : SEND_SMS,RECEIVE_SMS,READ_SMS,RECEIVE_WAP_PUSH,RECEIVE_MMS
STORAGE : READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
从上图中我们可以看到,Android系统把危险权限分了9大组,这样也是为了简化权限的申请机制。如果你申请了android.permission.READ_CONTACTS读取联系人的权限,那么6.0 系统就会把这一组中其他的权限也打包给你。我觉得这个和iOS的隐私管理机制非常相似,在iOS系统设置的“隐私->通讯录”中可以看到,如果你给一个App通讯录的权限,那么这个App既可以读也可以写的
Android 6.0里面只有危险权限才需要运行时获取的
1.3、android系统对权限的处理场景分析
如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的targetSdkVersion是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。
如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的targetSdkVersion是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。
如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的targetSdkVersion是 22 或更低版本,此时Android系统会把你申请的全部权限都给你。用户依然可以进入App的设置界面把权限关闭!
例如以下图片中用户在android6.0的版本的设置中把权限关闭,此时你的权限就用不了了。那么程序需要考虑对6.0及以上版本的兼容,具体参考下面的(android.support.v4.content.PermissionChecker)。
值得注意的是Android系统有一套自动权限调整的机制,我们知道android每次sdk升级有可能会加入新的权限,而你的app已经发布到用户手机上安装了,除了升级不可能修改Androidmanifest文件了,此时你可能担心自己的app能够在这些新的sdk版本的手机上运行正常吗,其实android已经考虑了这种场景,Android 将根据为targetSdkVersion属性提供的值决定应用是否需要权限。如果该值低于在其中添加权限的版本,则 Android 会为App自动添加该权限。
例如,API 级别 4 中加入了WRITE_EXTERNAL_STORAGE权限,用以限制访问共享存储空间。如果您的targetSdkVersion为 3 或更低版本,则会向更新 Android 版本设备上的应用添加此权限。
2、如何申请权限
申请权限过程包括以下几个步骤:
检查权限
请求权限
处理权限请求响应
解释应用为什么需要权限
2.1、检查权限
如果您的应用需要危险权限,则每次执行需要这一权限的操作时您都必须检查自己是否具有该权限。用户始终可以自由调用此权限,因此,即使应用昨天使用了相机,它不能假设自己今天仍具有该权限,因为用户可能在设置里面关闭了。
要检查您是否具有某项权限,请调用ContextCompat.checkSelfPermission()方法。例如,以下代码段显示了如何检查 Activity 是否具有在日历中进行写入的权限:
// 假设thisActivity是当前屏幕最前端正在和用户交互的activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
如果应用具有此权限,方法将返回PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回PERMISSION_DENIED,且应用必须明确向用户要求权限。
也可以使用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
问题二:
国产很多手机在google之前已经做了自己的权限管理,例如小米,所以此时使用ContextCompat的checkSelfPermission方法即便返回PackageManager.PERMISSION_GRANTED 也可能不准确。如果出现这种情况我们需要做一次特殊处理,此时我们需要用到android的隐藏API --AppOpsManager
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 对话框,不过,您不能对它们进行自定义。
如果上面的权限检查步骤中结果是应用尚无所需的权限,则应用必须调用一个requestPermissions()方法,以请求适当的权限。应用将传递其所需的权限,以及您指定用于识别此权限请求的整型请求代码。此方法异步运行:它会立即返回,并且在用户响应对话框之后,系统会使用结果调用应用的回调方法,将应用传递的相同请求代码传递到requestPermissions()。
提示用户授予或拒绝权限的系统对话框如下:
以下代码可以检查应用是否具备读取用户联系人的权限,并根据需要请求该权限:
// 这里的 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()时,系统将向用户显示一个标准对话框。您的应用无法配置或更改此对话框。如果您需要为用户提供任何信息或解释,您应在调用requestPermissions()之前进行,如解释应用为什么需要权限。
2.3、处理权限请求响应
当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的onRequestPermissionsResult()方法,向其传递用户响应。您的应用必须覆写该方法,以了解是否已获得相应权限。回调会将您传递的相同请求代码传递给requestPermissions()。例如,如果应用请求READ_CONTACTS访问权限,则它可能采用以下回调方法:
@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
2.4 解释应用为什么需要权限
在某些情况下,您可能需要帮助用户了解您的应用为什么需要某项权限。例如,如果用户启动一个摄影应用,用户对应用要求使用相机的权限可能不会感到吃惊,但用户可能无法理解为什么此应用想要访问用户的位置或联系人。在请求权限之前,不妨为用户提供一个解释。请记住,您不需要通过解释来说服用户;如果您提供太多解释,用户可能发现应用令人失望并将其移除。
您可以采用的一个方法是仅在用户已拒绝某项权限请求时提供解释。如果用户继续尝试使用需要某项权限的功能,但继续拒绝权限请求,则可能表明用户不理解应用为什么需要此权限才能提供相关功能。对于这种情况,比较好的做法是显示解释。
为了帮助查找用户可能需要解释的情形,Android 提供了一个实用程序方法,即shouldShowRequestPermissionRationale()。如果应用之前请求过此权限但用户拒绝了请求,此方法将返回true。
注:如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了Don't ask again选项,此方法将返回false。如果设备规范禁止应用具有该权限,此方法也会返回false。
That's all 感谢阅读,原文地址http://www.huahuaxie.com/android-6-0-runtime-permission/