Android 6.0 运行时权限详解

来源:互联网 发布:神话软件下载 编辑:程序博客网 时间:2024/06/08 11:15

一. 运行时权限

运行时权限是 Android 6.0 开始引入的,这一特性不仅增加了应用的安全性,同时又改善了用户的使用体验。

二. Android 6.0 之前权限处理

在 Android 6.0 以前的系统,运行时权限仅当设置targetSdkVersion 等于 23才起作用。

在 Android6.0 之前的设备依然使用旧的权限系统。权限处理通过很粗暴的方式处理,App 在 AndroidManifest.xml 添加相应权限,App 安装时会提示用户此 App需要使用这些权限,但用户不能单独对某项权限进行授权或拒绝,只要用户选择了安装,即表示用户接受了这些权限,用户只能在安装应用和拒绝权限之间二选一,选择拒绝权限就意味着不能使用此应用,这样做的代价太大,和用户下载此应用的初衷相违背,大多数时候用户只能选择妥协,而安装了应用则意味着将个人隐私信息完全暴露给了应用。

同一组的任何一个权限被授权了,其他权限也自动被授权。

三. Android 6.0 之后权限处理

1. 权限的分类

Android 中有很多权限,但并非所有的权限都是敏感权限,于是6.0系统就对权限进行了分类,一般为下述几类

  • 正常(Normal Protection)权限
  • 危险(Dangerous)权限
  • 特殊(Particular)权限
  • 其他权限(一般很少用到)

正常权限 (normal permissions)

也就是普通权限,例如访问网络,创建快捷方式,开启闪光灯等 ,这类权限一般不涉及用户隐私,对normal permissions,开发者只需要在AndroidManifest中配置即可,应用安装时提示用户所需的权限,用户同意安装即表示授权应用使用这些权限
一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;

正常权限特点
1. 对用户隐私没有较大影响;
2. 不会有太大安全问题;
3. 安装后就会默认赋予这些权限,不需要显示提醒用户,用户也不能取消这些权限。

特殊权限

特殊权限就是一些特别敏感的权限,在Android系统中,主要由两个:

SYSTEM_ALERT_WINDOW   //设置悬浮窗WRITE_SETTINGS        //修改系统设置

上面两个特殊权限的授权,做法是使用startActivityForResult启动授权界面来完成。

危险权限 (dangerous permissions)

危险权限一般是涉及到用户隐私的,需要用户进行授权(动态申请),比如读取SIM卡状态、访问通讯录、SD卡读写等。

危险权限实际上才是运行时权限主要处理的对象,这些权限可能引起隐私问题或者影响其他程序运行。例如拨打电话,读取通讯录,读取短信,获取地理位置等。对dangerous permissions这类涉及用户隐私的权限,不仅需要在AndroidManifest中配置,还需要在运行时请求用户授权,用户这时可以单独允许或拒绝某项权限。当用户选择了拒绝某项权限时,应用将无法执行该权限对应的api。
通过引入这套新的权限管理机制,用户在权限管理上有了更高的自由度,用户不再需要为了限制某项信息不被获取而舍弃整个应用的使用权。对涉及用户隐私的这类操作,用户可以选择拒绝,而应用的其他功能又不受影响。

Android中的危险权限可以归为以下几个分组:

Permission Group Permissions android.permission-group.CALENDAR android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR android.permission-group.CAMERA android.permission.CAMERA android.permission-group.CONTACTS android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS android.permission-group.LOCATION android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION android.permission-group.MICROPHONE android.permission.RECORD_AUDIO android.permission-group.PHONE android.permission.READ_PHONE_STATE
android.permission.CALL_PHONE
android.permission.READ_CALL_LOG
android.permission.WRITE_CALL_LOG
com.android.voicemail.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.PROCESS_OUTGOING_CALLS android.permission-group.SENSORS android.permission.BODY_SENSORS android.permission-group.SMS android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_WAP_PUSH
android.permission.RECEIVE_MMS
android.permission.READ_CELL_BROADCASTS android.permission-group.STORAGE android.permission.READ_EXTERNAL_STORAGE
android.permission.WRITE_EXTERNAL_STORAGE

通过 adb shell pm list permissions -d -g 可以查看 Dangerous Permission (以权限组形式)

这里写图片描述

Android 8.0之前的权限申请规则:

Dangerous Permission 一般以 Permission group 形式存在,在 Android 8.0 之前,只要 Permission group中某一个 permission 被Granted,则整个Permission group下的权限均被Granted, 如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。 。

Android 8.0 权限变更后的申请规则:

针对 Android 8.0 的应用,系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。

例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。

2. 权限检查及权限兼容

(1) targetSdkVersion >= 23,Android 6.0 及其以上系统

App 安装的时候不会获得权限,只在 App 运行具体功能时申请对应权限, 使用 Context.checkSelfPermisson 或者使用ContextCompat.checkSelfPermisson 检查权限;
一般检查流程如下:

  1. 判断是否有对应权限(ContextCompat.checkSelfPermisson)

  2. 判断是否需要解释对应权限用途
    ActivityCompat.shouldShowRequestPermissionRationale

  3. 直接请求对应权限(ActivityCompat.requestPermissions)

(2) targetSdkVersion < 23,Android 6.0 以下系统

在 App 安装时询问 AndroidManifest.xml 文件中的权限,如果要安装应用就必须接受这些权限, 如果不想接受某些权限就只能放弃安装该 App . 但是用户可以在设置列表中关闭该 App 的一些权限,这种情况可能会对 App 运行造成一定影响。

权限申请相关API

ContextCompat::checkSelfPermisson

使用 Context.checkSelfPermisson 或者使用ContextCompat.checkSelfPermisson 检查权限;

requestPermissions()

第一个参数是一个数组,可以一次申请多个权限。
如果一次申请了多个权限,权限请求对话框的弹出顺序是按照数组的顺序来的,数组前面的权限会先让用户确认。

如果一次申请了多个权限,只有所有的权限被用户处理(拒绝或接受)后,onRequestPermissionsResult()才会被回调,不是处理一个回调一次。

如果一次申请的权限中,部分权限没有在 AndroidManifest.xml 中声明,则不会弹出该权限的请求对话框,只会弹出那些在AndroidManifest.xml 中声明过的权限,等用户处理完后onRequestPermissionsResult() 被回调,那些未在 AndroidManifest.xml 中声明的权限请求结果一定是 PERMISSION_DENIED。

如果一次申请的权限中,所有的权限都没有在 AndroidManifest.xml 中声明,则不会弹出任何请求对话框,回调onRequestPermissionsResult()会被立刻执行, 所有权限请求结果都是 PERMISSION_DENIED。

如果App的 targetSdkVersion 设置为23 以下,当这个 App 在 Android 6.0系统上运行时,系统会自动为它授予 dangerous 的权限。不过用户仍然可以通过系统设置来取消某项权限。在取消授权时,和targetSdkVersion 设置为 23 的 App 不同的是,会多出一个警告提示,告知用户取消授权可能会导致应用异常。

如果一个 App 的 targetSdkVersion 设置为 23 以下,在Android 6.0系统上执行 checkSelfPermission() 检查是否有某项权限时,只要在AndroidManifest.xml 中声明了该权限,无论当前是否被授予了该权限,返回结果都是 PERMISSION_GRANTED。也就是说如果该权限没有在AndroidManifest.xml中声明,则checkSelfPermission()返回PERMISSION_DENIED,如果该权限在AndroidManifest.xml中声明了,即使用户手动禁止了该权限,checkSelfPermission()也会返回PERMISSION_GRANTED。所以,无法通过后checkSelfPermission()来判断用户是否禁止了某项权限。

如果一个app的targetSdkVersion设置为23以下,在Android 6.0系统上执行requestPermissions(),结果和targetSdkVersion设置为23的 App 差不多,唯一不同的对话框中没有下次不再提醒的复选框。这点和targetSdkVersion设置为23时申请已经被授予的权限的效果相同。原因应该也是系统认为所有申请的权限都已经被授予了。

如果一个app的targetSdkVersion设置为23以下,在Android 6.0系统上调用一个需要权限的api时,如果这个权限被用户手动取消了,不会抛出异常。但是该api将什么也不做,如果有返回值的话会返回null或者0。
requestPermissions()的第二个参数requestCode是一个int类型的整数,用来标识一次请求过程,在onRequestPermissionsResult()中可以通过requestCode来区分此次返回的是哪一次请求的结果。如果在一个Activity类中有多次请求不同权限的操作,则需要区分requestCode,一般来说可以随意可以随机取一个整数,也可以用某个固定的整数。需要注意的是,requestCode必须是一个大于等于0的整数。如果传入了一个小于0的整数,虽然不会有异常,但是也不会有任何效果。不会弹出请求对话框,onRequestPermissionsResult()也不会被执行。因此,如果用随机整数的话一定要选择大于等于0的随机数,如果用固定整数的话,一定不要用小于0的整数。例如,用0xFF000001作为requestCode是不会有任何效果的。
WRITE_SETTINGS和SYSTEM_ALERT_WINDOW权限申请

从normal permissions 和dangerous permissions 列表中可以看到,这两个列表中都没有包含WRITE_SETTINGS和SYSTEM_ALERT_WINDOW这两个权限,也就是说这两个权限既不属于normal permission,也不属于dangerous permission。
这是因为Android认为这两个权限非常敏感,已经超出了dangerous permissions的程度,一般app中都不应该使用这两个权限,因此将这两个权限单独分成一类,称为special permissions。
这两个权限在Android6.0系统上同样需要在运行时申请,不过针对dangerous permissions的运行时权限申请方法对这两个权限是不适用的,Android单独制作了一个activity作为这两个权限的用户授权界面,必须通过指定intent,然后通过startActivity(intent)的方式来申请。
special permissions运行时的权限申请主要用到如下几个api。

Settings.System.canWrite(Context context) 检查是否被授予了WRITE_SETTINGS权限
Settings.canDrawOverlays(Context context) 检查是否被授予了SYSTEM_ALERT_WINDOW权限
startActivityForResult(Intent intent, in requestCode) 打开用户授权界面
onActivityResult(int requestCode, int resultCode, Intent data) 权限申请结果回调此外还用到两个字符串常量
Settings.ACTION_MANAGE_WRITE_SETTINGS申请WRITE_SETTINGS权限对应的intent action
Settings.ACTION_MANAGE_OVERLAY_PERMISSION申请SYSTEM_ALERT_WINDOW权限对应的intent action
前两个API和两个字符串常量同样是从Android 6.0系统(API Level 23)才开始有的,因此使用前都需要判断当前系统的版本是否是Android 6.0以上。

这里同样有几个需要注意的地方
special permission同样需要先在AndroidManifest中配置,如果未在AndroidManifest中配置,执行startActivityForResult()后仍然会显示用户授权界面,不过文字和按钮都是灰色的,用户无法更改。
申请的权限如果已经被授予,执行startActivityForResult()后仍然会显示用户授权界面,用户可以选择取消授权,所以在执行startActivityForResult()前一定要先判断是否已经授予了权限。
多次执行startActivityForResult(),会按照startActivityForResult()的执行顺序依次弹出多个用户授权界面。即使申请的是同一个权限,也是如此。
和onRequestPermissionsResult()中的第三个参数grantResults的作用不同,onActivityResult()中的resultCode不能用来判断权限申请的结果,无论用户是否授予了权限,resultCode始终为0。
对这两个special permission,无论是否已经授予权限,checkSelfPermission()返回的都是PERMISSION_DENIED,如果试图使用requestPermissions()来申请权限,不会弹出任何权限确认的界面,不过onRequestPermissionsResult()回调方法仍然会被执行,grantResults的结果始终都是PERMISSION_DENIED。因此,试图用申请dangerous permission的方法来检查和申请这两个special permission是没有任何效果的。