RuntimePermission
文章主要基于API文档完成,并未对Android权限写的很详尽,更详细的Android权限相关内容还请移步官方文档。
一、前言
运行时权限是Android M (即6.0版本)引入了新的权限模式,用户能在运行时直接管理app权限。改善用户对权限的控制和可见性,同时为开发者简化了app安装和自动更新过程。用户可以在app安装之后单独授予或撤销权限。
二、系统权限
Android是一个权限分离的操作系统,每个应用程序都以不同的系统标识(Linux用户标识和组标识)运行。系统的一部分也被分成不同的身份。Linux从而将应用程序与系统隔离开来。
因为每个Android应用都在一个进程沙盒中运行,所以应用必须请求访问沙盒以外的资源和数据。他们通过声明所需的权限来访问该权限,获得沙盒不提供的额外功能。根据敏感程度,系统可能自动授权,或者提示用户批准活拒绝请求。
很多时候授权失败,应用会抛出SecurityException。但是,不能保证到处都会发生。例如,sendBroadcast(Intent)方法调用后,该方法在数据传递给每个接收方是检查权限,因此如果授权失败你将不会收到异常。但是,几乎所有的情况,权限失败都将记录在系统日志中。
Android提供的权限在Manifest.permission中可以找到。任何应用也可以定义和强制自己的权限,因此这不是一个全面的所有权限列表。
在应用运行期间,很多地方可以执行特定的权限:
- 再跳用系统是,阻止应用执行某些功能。
- 当启动一个activity,要阻止应用启动其他应用的activity。
- 发送和接受广播,控制谁能接收记得广播或谁能发送广播给你。
- 访问和操作ContentProvider。
- 绑定或启动一个service。
自动权限调整
随着时间的推移,新的限制可能被添加到平台,为了便于某些API,你的应用必须请求以前不需要的权限。因为现有的应用访问这些免费的权限,Android可能应用新的权限请求到应用的manifest中,避免在新的平台上破坏应用。Android根据targetSdkVersion属性来决定应用是否需要权限。如果值低于权限版本则Android会添加权限。
例如,WRITE_EXTERNAL_STORAGE权限已添加到API级别4中以限制对共享存储空间的访问。如果您的targetSdkVersion 值为3或更低,则会在新版本的Android中将此权限添加到您的应用程序中。
警告:如果你的应用自动添加了权限,即使你的应用可能不需要这些权限,Google Play也会列出来。(事实上限制各大市场也会检测)
为了避免这种情况,移除你不需要的权限,尽量保持targetSdkVersion在高版本。你能查看Build.VERSION_CODES档中每个版本添加的权限 。
安装未知应用所需权限
如果你的应用目标版本是Android 8.0(API level 26)或者更高版本,并使用安装API安装APK,你需要添加
REQUEST_INSTALL_PACKAGES权限。
<manifest>
<uses-permission
android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application>
...
</application>
</manifest>
只有使用此权限的应用才能出现在安装未知应用系统设置的屏幕上。参考install unknown apps。
普通权限和危险权限
系统权限分为几个保护级别。要了解最重要的两个保护级别就是普通和危险权限:
- 普通权限包括应用需要访问应用沙盒意外的数据或资源,但是用得隐私或其他应用的操作风险很小。例如,设置时区就是一个普通权限。如果应用声明一个它需要的普通权限,系统会自动授予应用。有关当前完整的普通权限列表参考普通权限。
- 危险权限包括应用需要涉及用户隐私的数据或资源,或可能影响用户存储的数据或其他应用的操作。例如,读取用户的通讯录就是一个危险权限。如果应用申明它需要的危险权限,用户必须明确授权。
特殊权限:有几个权限不具有普通和危险的权限。
SYSTEM_ALERT_WINDOW和WRITE_SETTINGS是特别敏感的,因此大多数应用不应该使用。如果应用需要其中之一,它必须在manifest中声明并发送intent请求用户授权。系统通过用户向用户显示纤细的管理屏幕来响应intent。具体参考SYSTEM_ALERT_WINDOW和WRITE_SETTINGS。
权限组
所有危险的Android系统权限都须臾权限组。如果设备运行Android 6.0并且应用的目标版本是23以上,当应用全球危险权限是遵循以下行为:
- 如果应用请求manifest中列出的危险权限,并且该应用没有权限组中的权限,系统会向描述应用程序要访问的权限组的用户显示一个对话框。该对话框不描述该组内的特定权限。
- 如果应用程序请求清单中列出的危险权限,并且应用程序已经在同一权限组中具有另一个危险权限,系统会立即授予权限,而不会与用户进行任何交互。
警告:未来的Android SDK可能将特定权限从一个组移动到另一个组。因此,不要将你的应用逻辑基于权限组的结构。
任何权限都可以属于权限组,包括由您的应用定义的普通权限和权限。但是,如果权限是危险的,权限组只会影响用户体验。您可以忽略正常权限的权限组。
如果设备运行Android 5.1(API级别22)或更低,或者应用程序的级别 targetSdkVersion为22或更低,则系统要求用户在安装时授予权限。系统再一次告诉用户应用程序需要什么权限组,而不是个人权限。
危险权限和权限组
暗含功能要求的权限
一些硬件和软件特征常量在相应的API之后被提供给应用程序; 例如android.hardware.bluetooth在Android 2.2(API级别8)中添加了该 功能,但在Android 2.0(API级别5)中添加了指向的Bluetooth API。正因为如此,有些应用程序才能够使用API,然后才能声明他们需要使用<uses-feature>系统的API 。
为了防止这些应用程序无意中出现,Google Play假定某些与硬件相关的权限表示默认情况下需要使用底层硬件功能。例如,使用蓝牙的应用程序必须BLUETOOTH 在<uses-permission>元素中请求权限- 对于旧版应用程序,Google Play假定权限声明意味着android.hardware.bluetooth应用程序需要基础功能,并根据该功能设置过滤。表2列出了暗示与<uses-feature>元素中声明的功能要求相同的权限 。
请注意,<uses-feature>声明(包括任何已声明的android:required属性)始终优先于表2中的权限所隐含的功能。对于这些权限中的任何一个,可以通过在<uses-feature>元素中明确声明隐含的功能来禁用基于隐含功能的过滤 一个 android:required="false"属性。例如,要禁用基于CAMERA权限的任何过滤,可以将此<uses-feature>声明添加 到清单文件中:
暗示设备硬件使用的设备权限
三、声明权限
每个Android应用在有限访问的沙盒中运行。如果应用需要用自己沙盒以外的资源或者信息,应用必须请求适当的权限。声明应用所需要的权限,在Manifest中列出这些权限。
根据权限的敏感程度,系统可能会自动授予权限,或者设备必须请求用户授予权限。例如,如果你的应用请求开始闪光灯权限,系统会自动授予权限。但是,如果你的应用需要读取用户的通讯录,系统会询问用户批准该权限。根据平台版本,在Android 5.1.1以及更低版本,用户在安装事授权,在Android 6.0以及更高版本在运行时授予权限。
确定你的App需要什么权限
开发App时,你应该注意App何时需要需要权限。通常,应用使用不是该应用创建的信息或资源,或应用执行需要设备(或其他应用)响应的行为。例如,如果应用需要访问网络,使用设备的相机,或者开关wifi应用都需要适当的授权。相关系统权限,请参考正常和危险权限。
你的应用只在直接执行操作是需要权限。如果你的应用请求其他应用执行任务或提供信息,则不需要权限。例如,你的应用需要用户的地址簿,应用需要 READ_CONTACTS 权限。但是,你的应用用以个intent请求用户通讯录信息,你的应用不需要任何权限,但是通讯录应用需要这个权限。更多信息,参考使用意图。
添加权限
声明应用需要的一个权限,在Manifest中<manifest>元素下添加一个<uses-permission>子元素。例如,应用需要发SMS消息,manifest中将有这样一行:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.SEND_SMS"/>
<application ...>
...
</application>
</manifest>
声明权限之后系统的行为取决于权限敏感程度。如果权限不影响用户隐私,系统会自动授予权限。如果权限可能授予访问用户敏感信息,系统会询问用户批准请求。更多权限信息,参考正常和危险权限。
四、运行时请求权限
从Android 6.0开始,当应用运行时用户授予权限给应用,而不是应用安装时。这个做法简化应用安装过程,因为安装或更新用影视用户不需要授予权限。让用户更好的控制应用的功能;例如,用户能选择给相机应用访问相机但是不能访问设备位置。在应的设置中,用户能随时撤销权限。
系统权限分为两类,正常和危险:
- 正常权限不会直接威胁用户隐私。如果你的应用在manifest中列出正常权限,系统将自动授予这些权限。
- 危险权限能使应用访问用户的机密数据。如果你的应用列出一个危险权限,用户必须给出明确批准。
在Android所有的版本中,应用需要声明正常权限和危险权限。但是,该声明的效果取决于系统版本和你应用的target SDK版本:
- 如果设备系统低于Android 5.1(包含5.1),或者你的应用target SDK低于22(包含22):manifest中的危险权限,用户必须要安装应用时授予;如果不授予权限,系统就不会安装该应用。
- 如果设备系统高于Android 6.0(包含6.0),并且你的应用target SDK高于23(包含23):应用必须在manifest中列出权限,并且在应用运行时必须请求每个危险权限。用户可以授予或拒绝每一个权限,即使用户拒绝权限请求应用也能继续以有限的权限运行。
注意:从Android 6.0(API level 23)开始,用户能随时撤销任何应用的权限,即使应用的target SDk版本较低。不管你的应用目标API是什么,你应该测试你的应用,以验证缺少所需权限时的行为正确。
检查权限
如果你的应用需要危险权限,每次执行需要权限的操作时你都必须检查是否有该权限。用户可以自有撤销权限,所以即使应用昨天使用了相机也不能认为今天仍然有权限。
要检查是否有权限,调用ContextCompat.checkSelfPermission()方法。例如,下面展示了如何检查活动是否有权限写入日历:
// 假设 thisActivity 是当前的 activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
如果应用有这个权限,该方法返回PackageMananger.PERMISSION_GRANTES,应用可以执行该操作。如果哦没有这个权限,该方法返回PREMISSION_DENIED,应用必须明确要求用户许可。
请求权限
如果你的应用需要在manifest中列出危险权限,则必须询问用户,授予权限。Android提供几种用来请求权限的方法。调用这些方法会弹出一个标准的dialog,你不能自定义。
图1.系统对话框提示用户授予或拒绝权限。
解释应用为什么需要权限
一些情况下,你可能想帮助用户理解,你的应用为什么需要权限。例如,如果用户运行一个相册应用,用户不会惊讶应用请求使用相机的权限,但是用户可能不理解为什么应用想访问用户的位置或者通讯录。在你请求一个全县之前,你应该考虑给用户一个解释。请记住,你不要想用解释镇压用户;如果你提供太多的解释,用户可能会泄气,并且卸载应用。
你可以在用户已经拒绝授权请求时提供一个说明。如果用户不断尝试需要权限的功能,但是一直拒绝权限请求,很可能表示用户不知道为什么应用需要给该功能提供权限。这种情况下,显示一个说明是个好主意。
为了帮助找出用户可能需要说的情况,Android提供了一个实用方法,shouldShowRequestPermissionRationale()。如果应用请求过此权限,用户拒绝了请求,该方法返回true。
注意:如果用户拒绝过权限请求,而且在请求权限的系统对话框中选择"不再询问"选项,该方法返回false。如果设备策略禁止应用拥有该权限,此方法也会放回
true。
请求你需要的权限
如果你的应用还没有需要的权限,应用必须要调用requestPermissions()方法请求适当权限。应用传递想要的权限,也指定一个整型的request code标识这个权限请求。该方法异步执行:用户响应对话框之后它马上返回,系统调用应用的回调方法给予结果,并传递与requestPermissions()方法中相同的request code。
以下代码检查应用是否有读取用户通讯录的权限,是否有必要请求权限:
//这里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 是应用定义的常量
// 这个回调方法获取请求结果
}
}
注意:当你的应用调用
requestPermissions()方法,系统向用户展示一个标准的对话框。应用无法配置或改变该对话框。如果你向用户需要提供任何信息或说明,你应该在调用requestPermissions()方法之前,如解释应用需要权限的原因中所述。
处理权限请求响应
当应用请求权限是,系统向用户展示一个对话框。当用户响应是,系统执行应用的onRequestPermissionsResult()方法来传递用户的响应。应用必须重写此方法,确定用户是否授予该权限。回调传递与你传入requestPermissions()方法相同的request code。例如,应用请求READ_CONTACTS访问,它可能有一下的回调方法:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
系统显示的对话框描述了应用需要访问的权限组;它没有列出特殊的权限。例如,如果你请求READ_CONTACTS权限,系统对话框只会说你的应用需要访问设备的通讯录。每个权限组用户只需要授予一次权限。如果你的应用请求该组中的任何其他权限在(已经在manifest中列出来),系统会自动授予它们权限。当请求权限是,如果用户通过系统对话框明确通过你的请求,系统会以相同的方式调用你的onRequestPermissionsResult()回调方法,并传递PERMISSION_GRANTED。
注意:你的应该用需要明确的请求每个需要的权限,即使用户已经授权同一权限组的其他的权限,另外,在Android将来的版本中,权限的分组可能会发生变化。你的代码不应该依赖与特定的权限是否在同一组中。
例如,假设你在应用的manifest中列出READ_CONTACTS和WRITE_CONTACTS权限。如果你请求READ_CONTACTS权限,并且用户授予权限,然后你请求WRITE_CONTACTS权限,系统会马上授予你权限而不会与用户交互。
如果用户拒绝了权限请求,你的应用应该采取适当的措施。例如,你的应用可以显示一个对话框,说明无法执行是因为需要用户操作该权限的请求。
当系统要询问用户授予权限时,用户可以选择该权限不再询问。这种情况下,应用任何时候再次调用requestPermissions()请求权限时,系统会马上拒绝。系统会马上调用onRequestPermissionsResult()回调方法,并传递PERMISSION_GRANTED。这意味着当你调用requestPermissions()方法时,你不能认为用户发生了直接的交互。
五、权限使用说明
应用很容易用权限请求逼迫用户,用户容易反感甚至是卸载应用。下面来看看如何避免不良的用户体验。
考虑使用Intetn
许多情况下,应用执行任务有两种方式。可以让应用自己请求执行所需的权限,也可以使用Intent让另一个应用执行任务。
假设你的应用需要通过相机获取一张图片。你的应用请求CAMERA权限允许应用直接访问相机。然后,应用能使用相机的API控制相机并获得图片。这种方式使你的应用完全控制摄影的整个过程,并让你把相机的UI整合到你应用中。
然而,你不需要完整的控制,你能用一个ACTION_IMAGE_CAPTURE的intent请求一个张图片。当你发送这个intent时,系统会提示用户选择相机应用(如果没有默认的相机应用)。用户通过所选择的相机获得图片,在应用的onActivityResult()方法中返回图片。
简单的说,如果你需要打电话,访问用户的通讯录等等,可以通过创建一个适当intent或者请求权限并直接访问相应对象。各种方式都有优缺点。
如果你使用权限:
- 执行操作时,应用完全控制用户体验。但是,这会怎么任务的复杂性,因为你需要设计适合的用户界面。
- 系统会在运行或者安装是提示用户授权一次(取决用户的Android版本)。之后,应用可以执行操作,无需影虎进行额外的交互。但是,如果用户不授予权限(或者稍后撤销),则应用无法执行操作。
如果你使用intent:
- 你不需要为操作设计UI。处理intent的应用提供UI。但是,这意味着你布恩那个完全控制用户体验。用户可能和你从未见过的应用交互。
- 如果用户没有该操作的默认应用,系统提示用户选择一个应用程序。如果用户没有默认处理程序,则每次执行操作会经过一个额外的对话框。
只询问你需要的权限
每次询问请求权限,你都会强迫用户做一个决定。你应该尽量减少请求次数。如果用户运行在Android 6.0或者更高的系统,每次用户尝试一些需要权限的新应用功能是,应用必须通过权限请求中断用户使用。如果用户使用更早的Andrdoi版本,当安装时用户必须授予每个应用的权限;如果列表太长或者看起来不合适,用户可能决定不安装你的应用。出于这些原因,你应该尽量减少应用需要权限的数量。
通常应用能避免通过intent请求权限。如果一个功能不是应用功能的核心部分,你应该考虑使用其他的应用处理。
不要压倒用户
在Android 6.0及其以上版本,用户必须要在运行是授予权限。如果用户一次面对太多权限,可能会压倒用户使用户退出应用。相反,你应该在需要是询问权限。
某项请款,一个或多个权限可能绝对重要。在应用启动后马上请求这些权限可能是有意义的。例如,你开发的摄影应用,应用需要访问相机。当用户第一次启动时,他们不会对请求使用相机权限感到惊讶。但是,如果相同的应用有通过通讯录分享图片的功能,你大概应该不会在第一次启动是询问READ_CONTACTS权限。而是等待用户尝试使用分享是在询问权限。
如果你的应用提供了一个教程,可能在教程结尾处请求应用的基本权限,这个可以有。
解释你需要权限的原因
当你调用requestPermissions()方法出现授权对话框,说需要什么权限,但不说原因。某些情况,用户可能困惑。在调用requestPermissions()方法前,解释你的应用为什么需要这个权限是一个好主意。
例如,一个摄影应用肯恩想使用定位服务,这样可以对相片进行地理标记。典型的,用户可能不明白一个相片可以包含位置信息,并会迷惑,为什么摄影应用想要知道定位。所以这种情况中,在程序调用requestPermissions()方法前向用户说明这个功能是一个好主意。
一种告知用户的方法时间这些请求合并到应用教程中。本教程能依次展示应用的功能,并可以解释需要什么权限。例如,摄影应用的教程可以演示其“与您的联系人共享照片”功能,然后告诉用户他们需要给应用程序权限以查看用户的联系人。应用程序可以调用requestPermissions()方法询问用户的访问权限。当然,并不是每个用户都会遵循这个教程,所以在应用程序的正常运行过程中,您仍然需要检查权限和请求权限。
测试两种权限模型
从Android 6.0(API级别23)开始,用户在运行时授予和撤销应用程序权限,而不是在安装应用程序时。因此,必须在更广泛的条件下测试您的应用程序。在Android 6.0之前,可以合理地假设,如果您的应用程序正在运行,则它拥有在应用程序清单中声明的所有权限。在新的权限模式下,您不能再做出这样的假设。
下列提示将帮助你识别运行API级别为23或更高的设备上与权限相关的代码问题:
- 确定应用当前权限和相关的菜吗路径
- 测试用户经过的全县保护服务和数据
- 测试授权和撤销权限的各种组合。例如,相机应用可能会列出
CAMERA,READ_CONTACTS以及ACCESS_FINE_LOCATION在其清单。您应该测试每个这些权限打开和关闭的应用程序,以确保应用程序可以处理所有权限配置优雅。请记住,从Android 6.0开始,用户可以为任何应用程序打开或关闭权限,甚至可以打开目标API级别为22或更低的应用程序。 - 使用adb工具从命令行管理权限:
- 按组列出权限
$ adb shell pm list permissions -d -g - 授予(或拒绝)一个(或多个)权限
$ adb shell pm [grant|revoke] <permission-name> ...
- 按组列出权限
- 分析您的应用程序中使用权限的服务。













网友评论