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