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)
这里是危险权限列表:
从上图中我们可以看到,Android系统把危险权限分了9大组,这样也是为了简化权限的申请机制。如果你申请了 android.permission.READ_CONTACTS
读取联系人的权限, 那么6.0 系统就会把这一组中其他的权限也打包给你 。我觉得这个和iOS的隐私管理机制非常相似,在iOS系统设置的“ 隐私->通讯录 ”中可以看到,如果你给一个App通讯录的权限,那么这个App既可以读也可以写的
说到申请权限,先要说一下 targetSdkVersion
这个字段,这个字段一般定义在build.gradle文件中的。这个对App来说很重要!但是是什么意思呢?
假如说 targetSdkVersion 22
,安装好以后Android系统就知道了这个App在系统API 22以下都测试过了并且能正确运行的,但是在23以上并不可以正确运行的,假如说这个App运行在了Android 6.0系统上,那么Android就会对这个App很“照顾”,兼容它正确运行。比如,6.0系统会把App申请的所有权限都默认给这个App,处理的逻辑和6.0一下的系统是一样的
android.support.v4.app.ActivityCompat
这个类是App 向系统申请权限主要的工具,而且兼容了各种系统版本
ActivityCompat.requestPermissions
向系统申请一个或一组权限 ActivityCompat.checkSelfPermission
App检查自己是否有某个权限 ActivityCompat.shouldShowRequestPermissionRationale
判断弹出对话框中是否含有“不再询问”的选择框
申请权限的步骤:
targetSdkVersion
设置为23 AndroidManifest.xml
中申请的并且是危险的所有权限都列出来,用 ActivityCompat.requestPermissions
方法向系统申请权限 onRequestPermissionsResult
方法接受系统权限申请的回调 假如你的App的 targetSdkVersion
小于23,但是安装到了Android 6.0系统上了,会怎么样呢?会崩溃吗?
别担心,Android开发团队已经考虑到这一点了,如果 targetSdkVersion
小于23的话,就表示你的App并没有在新的运行时权限系统上测试过, 此时Android系统会把你申请的全部权限都给你 。
此时你还能用这个权限么?经过我的测试,是不可以了。
所以,如果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就可以不用关心权限问题了:
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
广播