权限请求保护设备提供的敏感信息,并且只有在访问信息对于您的应用的运行是必要的时才应使用。本文档提供了有关如何在不需要访问此类信息的情况下实现相同(或更好)功能的提示; 这并不是关于权限在Android操作系统中如何工作的详尽讨论。
有关Android权限的更一般性信息,请参阅权限概述。有关如何在代码中处理权限的详细信息,请参阅请求应用程序权限。
使用Android权限的原则
我们建议您在使用Android权限时遵循以下原则:
#1:只使用您的应用程序工作所需的权限。根据您使用权限的方式,可能有另一种方法可以在不依赖访问敏感信息的情况下完成所需的任务(系统意图,标识符,电话背景)。
#2:注意图书馆所需的权限。当你包含一个库时,你也继承了它的许可要求。您应该了解您的内容,所需的权限以及这些权限的用途。
#3:保持透明。当您提出权限请求时,请清楚您正在访问的内容以及原因,以便用户可以做出明智的决定。使此信息与权限请求一起提供,包括安装,运行时或更新权限对话。
#4:明确系统访问。当您访问敏感功能(例如相机或麦克风)时,提供连续指示可以在您收集数据时清楚地向用户显示,并避免您意图暗中收集数据。
本指南的其余部分在开发Android应用程序的上下文中详细阐述了这些规则。
Android 6.0+的权限
Android 6.0 Marshmallow引入了一个新的权限模型,它允许应用程序在运行时从用户请求权限,而不是在安装之前。当应用程序实际需要受服务保护的服务或数据时,支持新模型请求权限的应用程序。虽然这不会(必然)改变整体应用程序行为,但它确实会创建与敏感用户数据处理方式相关的一些更改:
情境上下文增加:在运行时,会在应用程序上下文中提示用户有权访问这些权限组涵盖的功能。用户对请求权限的上下文更加敏感,如果您要求的内容与应用的目的不匹配,则更重要的是要向用户提供详细的解释,说明您为什么要求允许; 只要有可能,您应该在请求时提供您的请求的解释,并在用户拒绝请求时在后续对话框中提供解释。
授予权限的灵活性更大:用户可以在请求和设置时拒绝对个人权限的访问, 但当功能因此而中断时,他们可能仍会感到惊讶。监控有多少用户拒绝权限(例如使用Google Analytics)是一个好主意,以便您可以重构应用以避免取决于该权限,或更好地解释为什么您需要获得应用的权限才能正常工作。您还应该确保您的应用处理在用户拒绝权限请求或关闭设置中的权限时创建的异常。
交易负担增加:将要求用户单独授予权限组的访问权限,而不是一组权限。这对于最小化请求的权限数量非常重要,因为它增加了授予权限的用户负担,并增加了至少拒绝一个请求的可能性。
避免请求不必要的权限
每当您要求获得许可时,都会强制用户做出决定。你应该尽量减少你提出这些请求的次数。如果用户运行的是Android 6.0(API级别23)或更高版本,则每次用户尝试某个需要权限的新应用程序功能时,该应用程序都必须使用权限请求中断用户的工作。如果用户正在运行早期版本的Android,则用户必须在安装应用程序时授予每个应用程序的权限; 如果列表太长或看起来不合适,用户可能决定根本不安装您的应用程序。出于这些原因,您应该尽量减少应用程序需要的权限数量。
本节提供了常见使用案例的替代方法,这些使用案例将帮助您限制您制作的权限请求的数量。由于与其他请求较少权限的类似应用程序相比,请求的用户表面权限的数量和类型会影响下载,因此最好避免为不必要的功能请求权限。
改为使用意图
在很多情况下,您可以选择两种方式让应用执行任务。您的应用程序要求自己执行任务的权限,或者可以使用意图让另一个应用程序执行任务。
例如,假设您的应用需要能够使用设备相机拍照。您的应用可以请求CAMERA
权限,这可以让您的应用直接访问摄像头。然后,您的应用将使用相机API来控制相机并拍摄照片。这种方法可让您的应用完全控制摄影过程,并让您将摄像头UI整合到您的应用中。
但是,如果您对访问用户数据的要求不频繁 - 换句话说,每次需要访问数据时,用户都会看到一个运行时对话并不会造成无法接受的干扰 - 您可以使用基于 意图的请求。Android提供了一些应用程序无需权限即可使用的系统意图,因为用户选择在发出基于意图的请求时与应用程序共享什么(如果有的话)。
例如,意图动作类型MediaStore.ACTION_IMAGE_CAPTURE
或MediaStore.ACTION_VIDEO_CAPTURE
可用于捕获图像或视频,而无需直接使用Camera对象(或需要许可)。在这种情况下,每次捕获图像时,系统意图都会代表您请求用户的许可。
同样,如果您需要拨打电话,访问用户的联系人等,则可以通过创建适当的意图来实现,或者您可以直接请求权限并访问适当的对象。每种方法都有优点和缺点。
如果您使用权限:
- 执行操作时,您的应用可全面控制用户体验。但是,如此广泛的控制会增加代码的复杂性,因为您需要设计合适的UI。
- 系统会在运行时或安装时提示用户授予一次权限(具体取决于用户的Android版本)。之后,您的应用可以执行操作,而无需用户进行额外的交互。但是,如果用户未授予权限(或稍后撤销权限),则应用程序将失去执行操作的能力。
如果您使用意图:
- 您不必为操作设计UI。处理意图的应用程序提供UI。
- 用户可以使用他们的首选应用程序来完成任务。例如,用户可以选择他们最喜欢的照片应用拍照。
- 如果用户没有该操作的默认应用程序,系统会提示用户选择一个应用程序。如果用户没有指定默认处理程序,则每次执行操作时可能需要经过额外的对话框。
不要压倒用户
如果用户运行Android 6.0(API级别23)或更高版本,则用户必须在运行应用程序时授予您的应用程序权限。如果您一次性向用户提出大量权限请求,您可能会压倒用户并导致他们退出您的应用。相反,您应该根据需要请求权限。
在某些情况下,一个或多个权限可能对您的应用程序绝对重要。应用程序启动后立即要求提供所有这些权限可能很有意义。例如,如果您制作摄影应用程序,该应用程序需要访问设备摄像头。当用户第一次启动应用程序时,他们会被要求获得使用相机的权限并不感到惊讶。但是,如果同一个应用程序还具有与用户的联系人共享照片的功能,那么您首次启动时可能不会要求获得READ_CONTACTS
许可。相反,请等到用户尝试使用“共享”功能并要求获得许可。
如果您的应用程序提供了教程,那么在教程序列结尾处请求应用程序的基本权限可能是有意义的。
失去音频焦点后暂停媒体
在这种情况下,您的应用程序需要在用户接到电话时进入后台,并且只有在呼叫停止后才能重新调整焦点。
在这些情况下,通常采用的方法 - 例如,媒体播放器在通话期间静音或暂停 - 是使用PhoneStateListener
或监听广播 的通话状态的变化android.intent.action.PHONE_STATE
。该解决方案的问题在于它需要READ_PHONE_STATE
权限,这会强制用户授予访问大量敏感数据的权限,例如设备和SIM硬件ID以及来电的电话号码。
您可以通过请求AudioFocus
您的应用程序来避免这种情况,该应用程序不需要显式权限(因为它不访问敏感信息)。只需将代码置于 onAudioFocusChange()
事件处理程序中所需的代码, 并在操作系统移动其音频焦点时自动运行。关于如何做到这一点的更详细的文档可以在这里找到。
确定您的实例正在运行的设备
在这种情况下,您需要一个唯一标识符来确定应用程序实例在哪个设备上运行。
应用程序可能具有特定于设备的偏好或消息(例如,为云中的用户保存设备特定的播放列表,以便他们可以为他们的汽车和家中有不同的播放列表)。一个常见的解决方案是利用设备标识符Device IMEI
,但这需要Device ID and call information
权限组(PHONE
M +)。它还使用无法重置并在所有应用程序之间共享的标识符。
使用这些类型的标识符有两种选择:
- 使用
com.google.android.gms.iid
InstanceID API。 将为您的应用程序实例返回唯一的设备标识符。结果是一个应用程序实例作用域标识符,可以在存储有关应用程序的信息时用作键,并在用户重新安装应用程序时重置。getInstance(Context context).getID()
- 使用基本的系统功能创建您自己的标识符,该标识符的范围与您应用的存储空间有关
randomUUID()
。
为广告或用户分析创建唯一标识符
在这种情况下,您需要一个唯一标识符来为未登录应用的用户构建配置文件(例如,针对定位或测量转化的广告)。
为广告和用户分析构建配置文件有时需要在其他应用程序之间共享的标识符。通常的解决方案涉及利用设备标识符,例如Device IMEI
需要Device ID
and call information
权限组(PHONE
API级别23+)且不能被用户重置的设备标识符。在任何这些情况下,除了使用不可重置的标识符并要求用户看起来不寻常的权限外,您还将违反Play开发者计划政策。
不幸的是,在这些情况下,使用 com.google.android.gms.iid
InstanceID API或系统函数来创建应用程序范围的ID不是合适的解决方案,因为ID可能需要跨应用程序共享。另一种解决方法是通过该 方法使用课程中的Advertising Identifier
可用内容。您可以使用该方法创建一个对象并调用该 方法以使用该标识符。注意这个方法是阻塞的,所以你不应该从主线程调用它; 这个方法的详细解释可以在这里找到。AdvertisingIdClient.Info
getId()
AdvertisingIdClient.Info
getAdvertisingIdInfo(Context)
getId()
了解你正在使用的库
有时,您在应用中使用的库需要权限。例如,广告和分析库可能需要访问 Location
或Identity
权限组才能实现所需的功能。但从用户的角度来看,权限请求来自你的应用程序,而不是库。
正如用户选择相同功能使用较少权限的应用程序一样,开发人员应该查看其库并选择未使用不必要权限的第三方SDK。例如,尽量避免需要Identity
权限组的图书馆,除非有明确的面向用户的原因,为什么应用程序需要这些权限。特别是,对于提供位置功能的库,FINE_LOCATION
除非您使用基于位置的定位功能,否则请确保不需要请求权限。
解释为什么你需要权限
当你打电话时系统显示的权限对话框requestPermissions()
说明你的应用需要 什么权限,但没有说明原因。在某些情况下,用户可能会感到困惑。向用户解释为什么你的应用程序在调用之前需要权限是一个好主意requestPermissions()
。
研究表明,如果用户知道应用程序需要它们的原因,那么用户对权限请求会更加舒适。用户研究表明:
...用户愿意授予给定移动应用程序的权限受到与此权限相关的目的的强烈影响。例如,用户愿意授予他或她的位置的访问权限将取决于请求是否需要支持应用程序的核心功能,或者是否要与广告网络或分析公司共享此信息。1
根据他所在小组的研究,CMU教授Jason Hong总结道:
...当人们知道应用程序为什么使用与其位置一样敏感的内容时(例如,针对有针对性的广告),这会让他们比简单地告诉应用程序使用其位置更舒服。1
因此,如果您仅使用属于某个权限组的API调用的一小部分,则有助于明确列出您正在使用哪些权限以及原因。例如:
- 如果您只使用粗略位置,请让用户在应用说明中或在有关您的应用的帮助文章中了解这一点。
如果您需要访问短信以接收验证码,以保护用户免遭欺诈,请让用户在您的应用说明中和/或第一次访问数据时了解相关信息。
注意:如果您的应用目标为Android 8.0(API级别26)或更高,请不要申请该 权限作为验证用户凭证的一部分。相反,使用生成特定于应用程序的令牌 ,然后将此令牌传递给可发送验证SMS消息的其他应用程序或服务。
READ_SMS
createAppSpecificSmsToken()
在某些情况下,让用户实时了解敏感数据访问也是有利的。例如,如果您正在访问摄像头或麦克风,最好让用户知道应用程序中某处的通知图标或通知托盘中的通知图标(如果应用程序在后台运行),那么通常是一个好主意似乎并不像你偷偷收集数据。
最终,如果您需要申请在您的应用中制作某些内容的权限,但其原因尚不清楚,请找到让用户知道为什么需要最敏感的权限的方法。
测试两种权限模式
从Android 6.0(API级别23)开始,用户在运行时授予和撤销应用程序权限,而不是在安装应用程序时这样做。因此,您必须在更广泛的条件下测试您的应用。在Android 6.0之前,您可以合理地假设,如果您的应用程序正在运行,它具有在应用程序清单中声明的所有权限。从Android 6.0开始,用户可以为任何 应用程序打开或关闭权限,即使是目标API级别为22或更低的应用程序。您应该测试以确保您的应用程序正常运行,而不管它是否具有任何权限。
以下提示将帮助您在运行API级别23或更高的设备上发现与权限相关的代码问题:
- 确定您的应用的当前权限和相关的代码路径。
- 测试用户跨越权限保护的服务和数据。
- 使用授予或撤销的权限的各种组合进行测试。例如,相机应用可能会列出
CAMERA
,READ_CONTACTS
以及ACCESS_FINE_LOCATION
在其清单。您应该测试每个这些权限打开和关闭的应用程序,以确保应用程序可以正常处理所有权限配置。 - 使用adb工具从命令行管理权限:
- 按组列出权限和状态: $ adb shell pm列表权限-d -g
- 授予或撤销一个或多个权限: $ adb shell pm [grant | revoke] <permission-name> ...
- 分析您的应用中使用权限的服务。