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 既可以读也可以写的
CALENDAR : READ_CALENDAR , WRITE_CALENDAR
CAMERA : CAMERA
CONTACTS : READ_CONTACTS , WRITE_CONTACTS , GET_ACCOUNTS
LOCATION : ACCESS_FINE_LOCATION , ACCESS_COARSE_LOCATION
MICROPHONE : RECORD_AUDIO
PHONE READ_PHONE_STATE , CALL_PHONE , READ_CALL_LOG , WRITE_CALL_LOG , ADD_VOICEMAIL , USE_SIP , PROCESS_OUTGOING_CALLS
SENSORS : BODY_SENSORS
SMS : SEND_SMS , RECEIVE_SMS , READ_SMS , RECEIVE_WAP_PUSH , RECEIVE_MMS
STORAGE : READ_EXTERNAL_STORAGE , WRITE_EXTERNAL_STORAGE
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 是否具有在日历中进行写入的权限:
如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED ,并且应用可以继续操作.如果应用不具有此权限,方法将返回 PERMISSION_DENIED ,且应用必须明确向用户要求权限.
// 假设 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
问题二:
国产很多手机在 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() .
提示用户授予或拒绝权限的系统对话框如下:
以下代码可以检查应用是否具备读取用户联系人的权限,并根据需要请求该权限:
注:当您的应用调用 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 定义的整形常量,用于标识一个请求,回调方法中会获得这个请求对应的结果
}
}
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.
来源: http://www.jianshu.com/p/a939e7510cc2