关于Android 6.0 运行时权限
Android 6.0 (API level 23) 已经发布很长一段时间了,其中一个很大的改进就是运行时权限。我之前就在知乎回答过一个问题iPhone 到底爽在哪里?
我说,iPhone上的App都是默认下载安装的,然后运行App时需要什么权限就弹窗向我申请,这对用户来说就非常好。因为我不想给App权限就不给,而Android 6.0以前是这样的,我下载了一个App安装,系统就弹出这个App需要使用的全部的权限,就给我看一下,我需要这个App 的话,只能同意所有的权限都给这个App,要么我不安装这个App
Android 6.0以前的权限管理应该是我对Android最不满的功能,6.0中 Google终于来解决这个问题了。
下面我就来好好聊聊这个运行时权限管理是怎么回事
权限分类
在6.0中Android把所有的权限从逻辑上分成了两类:常规(normal)和危险(dangerous)
- 常规权限指的是那些不会直接获取你隐私的权限,如果你在AndroidManifest.xml文件中列出了这些权限的话,系统会自动授权给你。这里是normal权限列表,有很多
- 危险权限就是那些能获取你隐私信息,或者可能会影响你的文件等的操作。比如读取你的联系人、使用你的摄像头和读取你的日历等等
权限组
这里是危险权限列表:
从上图中我们可以看到,Android系统把危险权限分了9大组,这样也是为了简化权限的申请机制。如果你申请了android.permission.READ_CONTACTS
读取联系人的权限,那么6.0 系统就会把这一组中其他的权限也打包给你。我觉得这个和iOS的隐私管理机制非常相似,在iOS系统设置的“隐私->通讯录”中可以看到,如果你给一个App通讯录的权限,那么这个App既可以读也可以写的
Android 6.0里面只有危险权限才需要运行时获取的
申请权限
tartgetSdkVersion
说到申请权限,先要说一下targetSdkVersion
这个字段,这个字段一般定义在build.gradle文件中的。这个对App来说很重要!但是是什么意思呢?
假如说targetSdkVersion 22
,安装好以后Android系统就知道了这个App在系统API 22以下都测试过了并且能正确运行的,但是在23以上并不可以正确运行的,假如说这个App运行在了Android 6.0系统上,那么Android就会对这个App很“照顾”,兼容它正确运行。比如,6.0系统会把App申请的所有权限都默认给这个App,处理的逻辑和6.0一下的系统是一样的
ActivityCompat
android.support.v4.app.ActivityCompat
这个类是App 向系统申请权限主要的工具,而且兼容了各种系统版本
ActivityCompat.requestPermissions
向系统申请一个或一组权限ActivityCompat.checkSelfPermission
App检查自己是否有某个权限ActivityCompat.shouldShowRequestPermissionRationale
判断弹出对话框中是否含有“不再询问”的选择框
申请权限的步骤:
- 你要有一个运行Android 6.0系统的设备
- 将App的
targetSdkVersion
设置为23 - 把
AndroidManifest.xml
中申请的并且是危险的所有权限都列出来,用ActivityCompat.requestPermissions
方法向系统申请权限 - 在所在的Activity中Override
onRequestPermissionsResult
方法接受系统权限申请的回调 - 处理回调,比如用户拒绝了某个权限,这时App可以弹出一个对话框描述一下App为何需要这个权限等等
targetSdkVersion 小于23
假如你的App的targetSdkVersion
小于23,但是安装到了Android 6.0系统上了,会怎么样呢?会崩溃吗?
别担心,Android开发团队已经考虑到这一点了,如果targetSdkVersion
小于23的话,就表示你的App并没有在新的运行时权限系统上测试过,此时Android系统会把你申请的全部权限都给你。
但是!!!用户依然可以进入App的设置界面把权限关闭!!
此时你还能用这个权限么?经过我的测试,是不可以了。
所以,如果App的targetSdkVersion
小于23并且运行在Android 6.0系统上,怎么去检测用户关闭了权限呢?伟大的stackoverflow告诉我们: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.
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及以后的系统上,你可以用PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker. PERMISSION_DENIED_APP_OP
来检查App是否有某一个权限
总结
如果App的targetSdkVersion
小于23(Android 6.0以前),那么ContextCompat#checkSelfPermission
和 Context#checkSelfPermission
方法的返回结果都是错误的,因为它们总是返回0(PERMISSION_GRANTED)。即使App运行在Android 6.0上并且用户在设置中关闭了App的权限,上面两个方法返回的结果也是0
上面也说到,Android 6.0系统上,用户是可以关闭App权限的,所以并不是说App的targetSdkVersion
小于23就可以不用关心权限问题了:
- Android < 6.0:系统会给App所有的权限
- Android >= 6.0 && targetSdkVersion < 23:系统会默认给予App所有的权限,但是用户可以去设置中关闭权限。这时你需要使用
PermissionChecker.checkSelfPermission
来检测App是否有某一个权限
public boolean selfPermissionGranted(Context context, String permission) {
// Android 6.0 以前,全部默认授权
boolean result = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (targetSdkVersion >= Build.VERSION_CODES.M) {
// targetSdkVersion >= 23, 使用Context#checkSelfPermission
result = context.checkSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
} else {
// targetSdkVersion < 23, 需要使用 PermissionChecker
result = PermissionChecker.checkSelfPermission(context, permission)
== PermissionChecker.PERMISSION_GRANTED;
}
}
return result;
}
获取App的targetSdkVersion值:
try {
final PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
targetSdkVersion = info.applicationInfo.targetSdkVersion;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
最后
不得不说,慢慢的从Android 5.0开始,Google慢慢的缩减了Android开放策略,以前的Android真的是可以为所欲为,监听系统各种变化,甚至一个App被切换到后台,它任然可以获取到当前正在运行的App(用户正在使用的),这个Api可以轻松的获取用户的隐私信息啊,太可怕了。
从Android 6.0开始,运行时权限、Doze模式以及App Standby,Android 7.0中对Doze模式加强,以及取消了很多比如CONNTENCTIVITY_ACTION
、ACTION_NEW_PICTURE
和ACTION_NEW_VIDEO
广播