权限和标识符最佳做法

来源:互联网 发布:事业单位做题软件 编辑:程序博客网 时间:2024/04/28 03:51

Android 6.0 Marshmallow 引入了一个全新的权限模式,此模式让应用可以在运行时而不是在安装之前向用户请求权限。支持这个新模式的应用会在应用确实需要相关服务或这些服务保护的数据时请求权限。尽管这不会(不一定)改变整体应用行为,但它确实会使处理敏感用户数据的方式产生一些变化:
增加情境背景:如需获得访问这些权限组所涵盖功能的权限,则运行时在应用的背景中提示用户。用户将对请求权限的背景更加敏感,如果您请求的权限和您的应用用途不匹配,则一定要向用户详细解释您为什么请求此权限;您应尽可能在请求权限时以及后续对话框中(如果用户拒绝请求)提供请求权限的原因。

在授予权限时更加灵活:用户可以在收到请求时也可以在设置中拒绝访问具体权限的请求,但功能因此中断仍会让他们感到诧异。最好可以监控有多少用户拒绝权限请求,以便您重构应用,从而避免依赖该权限,或更好地解释您的应用需要此权限才能正常工作的原因。您也应确保您的应用可以处理用户拒绝权限请求或在设置中关闭权限时产生的异常。

增加事务负担:系统将要求用户单独授予权限组的访问权限,而不是以集合的形式授予。这样一来,最大程度降低请求的权限数量就变得非常重要,因为数量多会增加用户授予权限的负担,并增大了至少有一个请求被拒绝的概率。

避免请求不必要的权限例如如下几种情况:

  1. 如果您需要偶尔访问设备的相机或联系信息,并且不介意每次需要访问时都向用户要求权限,显示请求权限的对话框。那么您可以使用一个基于 intent 的请求。例如,您可以使用 MediaStore.ACTION_IMAGE_CAPTURE 或 MediaStore.ACTION_VIDEO_CAPTURE 的 intent 操作类型采集图像或视频,无需直接使用 Camera 对象(或请求权限)。在这种情况下,每次采集图像时系统 intent 将代表您向用户要求权限。
  2. 丢失音频焦点后在后台运行:当用户接听来电时您的应用需要转入后台,并且仅在来电停止时重新聚焦。出现此类情况(例如,媒体播放器在手机来电期间静音或暂停)时,通常采用的方法是使用 PhoneStateListener 或侦听 android.intent.action.PHONE_STATE 的广播,以此侦听来电状态有无变化。这种解决方法的问题是它需要 READ_PHONE_STATE 权限,这将强制用户授予广泛的敏感数据(如用户的设备和 SIM 硬件 ID 以及来电的电话号码)访问权限。
    您可以通过为应用请求 AudioFocus 避免这个问题,该功能不需要显式权限(因为它不访问敏感信息)。只需在 onAudioFocusChange() 事件处理程序中将所需代码放入音频后台,当操作系统转换其音频焦点时它将自动运行。在你的应用程序开始播放任何音频之前,它应该持有它所使用的流的音频焦点.。你可以调用requestaudiofocus(),当返回audiofocus_request_granted时说明您的请求是成功的。您必须指定您正在使用的流,以及是否需要瞬间或永久音频焦点.。当您希望只在短时间内播放音频时(例如播放导航指令)时,请请求瞬间聚焦。当你计划在可预见的将来播放音频时(例如,播放音乐时)请求永久音频焦点。下面代码请求永久焦点:
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);...// Request audio focus for playbackint result = am.requestAudioFocus(afChangeListener,                                 // Use the music stream.                                 AudioManager.STREAM_MUSIC,                                 // Request permanent focus.                                 AudioManager.AUDIOFOCUS_GAIN);if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {    am.registerMediaButtonEventReceiver(RemoteControlReceiver);    // Start playback.}

一旦你完成了播放一定要调用abandonaudiofocus()。通知系统,你不再需要焦点并且注销相关audiomanager.onaudiofocuschangelistener。

// Abandon audio focus when playback completeam.abandonAudioFocus(afChangeListener);

当请求瞬间音频焦点时你有附加的选择:你是否要启用“回避”。通常情况下,当一个好的音频应用程序失去焦点就立即沉默其播放的音频。通过请求一个瞬间音频焦点允许回避,你告诉其他音频应用程序,让他们继续播放是可以接受的,只要他们降低音量直到焦点返回到他们。

// Request audio focus for playbackint result = am.requestAudioFocus(afChangeListener,                             // Use the music stream.                             AudioManager.STREAM_MUSIC,                             // Request permanent focus.                             AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {    // Start playback.}

当别的应用请求焦点时,你的应用会失去焦点,当音频焦点变化时,会回调你注册的监听方法onAudioFocusChange() 。一般来说,音频焦点的短暂(临时)丢失会导致应用程序沉默它的音频流,但保持相同的状态不变.。你应该继续监测音频焦点的变化,并准备从暂停的位置恢复播放。如果音频焦点丢失是永久性的,它假定另一个应用程序现在被用于监听音频,并且您的应用程序应该有效地结束自己。在实际应用中,这意味着停止播放,删除媒体按钮监听器允许新的音频播放器完全监听这些按钮事件,并放弃您的音频焦点。此时,用户再次点击播放按钮才能再次播放。下面的代码片段,我们将在短暂失去焦点时暂停播放或媒体播放器对象,当我们恢复焦点时,我们继续播放。

AudioManager.OnAudioFocusChangeListener afChangeListener =    new AudioManager.OnAudioFocusChangeListener() {        public void onAudioFocusChange(int focusChange) {            if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT) {                // Pause playback            } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {                // Resume playback            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {                am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);                am.abandonAudioFocus(afChangeListener);                // Stop playback            }        }    };

有时候,短暂的失去焦点是允许的,你可以不用暂停播放,你只用降低音量duck一下就可以。Ducking是降低您的音频流音量使另一个应用程序不被你的音量干扰,使另一个应用程序更容易被听到,下面的代码会在我们暂时失去焦点时降低我们应用的音量,当我们再次获得焦点时,音量恢复到正常。

OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {    public void onAudioFocusChange(int focusChange) {        if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {            // Lower the volume        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {            // Raise it back to normal        }    }};

确定正在运行实例的设备
应用可能具有设备特定的首选项或消息(例如,在云中为用户保存设备特定的播放列表,以便他们在车上和家里可以有不同的播放列表)。常见的解决方法是有效利用设备标识符,例如 Device IMEI,但这需要 Device ID and call information 权限组(M+ 中的 PHONE)。它还使用一个无法重置的标识符,并在所有应用中共享。
下面两种方法可以替代使用这些类型的标识符:

  1. 使用 com.google.android.gms.iid InstanceID API。getInstance(Context context).getID() 将为您的应用实例返回一个唯一设备标识符。结果得到一个应用实例作用域标识符,在存储与应用有关的信息时可以将该标识符用作密钥,在用户重新安装应用时此标识符将重置。
  2. 使用 randomUUID() 之类的基本系统函数创建您自己的标识符,其作用域仅限于应用存储空间。
    为广告或用户分析创建唯一标识符
    为广告和用户分析构建配置文件有时需要一个与其他应用共享的标识符。此问题的常见解决方法包括有效利用设备标识符(例如 Device IMEI),设备标识符需要 Device ID and call information 权限组(API 级别 23+ 中的 PHONE),并且无法由用户重置。如果出现上述任何情况,除了使用不可重置的标识符并请求用户可能认为不寻常的权限外,还会违反 Play 开发者计划政策。遗憾的是,由于 ID 可能需要在各个应用中共享,在这些情况下使用 com.google.android.gms.iid InstanceID API 或系统函数创建应用作用域 ID 并不是适当的解决方法。一种替代解决方法是使用通过 getId() 方法从 AdvertisingIdClient.Info 类中获取的 Advertising Identifier。您可以使用 getAdvertisingIdInfo(Context) 方法创建一个 AdvertisingIdClient.Info 对象,并调用 getId() 方法来使用标识符。请注意,此方法正在冻结,因此,您不应从主线程调用它。
    了解您正在使用的库
    有时,您在应用中使用的库需要一些权限。例如,广告和分析库可能要求访问 Location 或 Identity 权限组以实现必需的功能。但从用户角度来说,权限请求来自于您的应用,而不是库。

由于用户会选择使用较少权限即可实现相同功能的应用,开发者应检查他们的库,并选择不使用非必要权限的第三方 SDK。例如,尽量避免使用需要 Identity 权限组的库,除非可以清楚地向用户解释应用为什么需要这些权限。具体而言,对于提供位置功能的库,请确保您不需要请求 FINE_LOCATION 权限,除非您正在使用基于位置的定位功能。
唯一标识符最佳做法
Android 标识符的使用原则:
原则 1:避免使用硬件标识符。您可以在大多数用例中避免使用 SSAID (Android ID) 和 IMEI 等硬件标识符,而必需功能也不会受到限制。

原则 2:只为用户分析或广告用例使用广告 ID。使用广告 ID 时,务必遵守限制广告追踪标记,确保标识符无法与个人可识别信息 (PII) 建立关联,并避免桥接广告 ID 重置。

原则 3:尽一切可能为防欺诈支付和电话以外的所有其他用例使用实例 ID 或私密存储的 GUID。对于绝大多数非广告用例,使用实例 ID 或 GUID 应该足矣。

原则 4:使用适合您的用例的 API 以尽量降低隐私权风险。为高价值内容保护使用 DRM API,为滥用预防使用 SafetyNet API。Safetynet API 是能够确定设备真伪而又不会招致隐私权风险的最简单方法。
Android 6.0+ 中的标识符
MAC 地址具有全局唯一性,无法由用户重置,在恢复出厂设置后也不会发生变化。一般不建议使用 MAC 地址进行任何形式的用户标识。因此,从 Android M 开始,无法再通过第三方 API 获得本地设备 MAC 地址(例如,WLAN 和蓝牙)。WifiInfo.getMacAddress() 方法和 BluetoothAdapter.getDefaultAdapter().getAddress() 方法都会返回 02:00:00:00:00:00。
此外,您还必须拥有下列权限,才能获取通过蓝牙和 WLAN 扫描获得的附近外部设备的 MAC 地址:

        方法/属性                   |所需权限        WifiManager.getScanResults()  | ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION        BluetoothDevice.ACTION_FOUND  | ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATIONBluetoothLeScanner.startScan(ScanCallback)|ACCESS_FINE_LOCATION 或ACCESS_COARSE_LOCATION

使用广告 ID
广告 ID 是一种可由用户重置的标识符,适用于广告用例,但在使用时需要牢记一些要点:
在重置广告 ID 时始终尊重用户的意图。在未经用户同意的情况下,请勿使用更持久的设备标识符或指纹将后续广告 ID 链接起来,对用户重置进行桥接。Google Play 开发者内容政策规定:

...重置后,在未获得用户明确许可的情况下,新的广告标识符不得与先前的广告标识符或由先前的广告标识符所衍生的数据建立关联。

始终遵守关联的个性化广告标记。广告 ID 是可配置的,用户可以限制与 ID 关联的跟踪数量。务必使用 AdvertisingIdClient.Info.isLimitAdTrackingEnabled() 方法来确保您没有忽视用户的愿望。Google Play 开发者内容政策规定:

...您必须遵循用户的“Opt out of interest-based advertising”或“Opt out of Ads Personalization”设置。如果用户已启用此设置,您不得出于广告目的而使用广告标识符创建用户个人资料,也不得使用广告标识符向用户投放个性化广告。允许的活动包括:内容相关广告定位、频次上限、转化跟踪、生成报表以及安全性和欺诈检测。

使用实例 ID 和 GUID
标识运行在设备上的应用实例最简单明了的方法就是使用实例 ID,在大多数非广告用例中,这是建议的解决方案。只有进行了针对性配置的应用实例才能访问该标识符,并且标识符重置起来(相对)容易,因为它只存在于应用的安装期。

因此,与无法重置的设备作用域硬件 ID 相比,实例 ID 具有更好的隐私权属性。它们还自带用于消息签名(及类似操作)的键对,并且在 Android、iOS 和 Chrome 上均可使用。如需了解详细信息,请参阅什么是实例 ID?帮助中心文档。

对于实例 ID 不实用的情况,您还可以使用自定义全局唯一 ID (GUID) 对应用实例进行唯一标识。最简单的方式是使用以下代码生成您自己的 GUID。

String uniqueID = UUID.randomUUID().toString();

由于该标识符具有全局唯一性,您可以使用它来标识特定应用实例。为了避免与跨应用关联标识符有关的问题,应将 GUID 存储在内部存储空间而不是外部(共享)存储设备内。
了解标识符特性
Android 操作系统提供了多种具有不同行为特性的 ID,您应该使用何种 ID 取决于以下这些特性适合您用例的程度。但这些特性还涉及到隐私权,因此必须了解这些特性如何共同发挥作用。
作用域
标识符作用域说明了哪些系统可以访问标识符。Android 标识符的作用域一般分为三种:

单一应用 - ID 仅限应用内部使用,其他应用无法访问。
一组应用 - ID 可供一组预先定义的相关应用访问。
设备 - ID 可供安装在设备上的所有应用访问。
向标识符授予的作用域越大,其作跟踪用途的风险就越大。相反,如果标识符只能由单一应用实例访问,就无法用于跨不同应用的事务跟踪设备。

重置性与持久性
重置性和持久性定义标识符的寿命并说明了如何对其进行重置。常见的重置触发器包括:应用内重置、通过 System Settings 重置、启动时重置以及安装时重置。Android 标识符可能具有不同的寿命,但寿命通常与 ID 的重置方式有关:

仅限会话期间 - 每次用户重新启动应用时使用新的 ID。
安装重置 - 每次用户卸载并重新安装应用时使用新的 ID。
FDR 重置 - 每次用户恢复设备出厂设置时使用新的 ID。
FDR 持久化 - ID 在恢复出厂设置后保持不变。
重置性让用户能够创建与任何现有个人资料信息断开关联的新 ID。这很重要,因为标识符持久存在得越久、越可靠(例如在恢复出厂设置等情况后继续存在),用户被长期跟踪的风险就越高。如果应用重新安装时标识符被重置,即使没有显式用户控件可以在应用或 System Settings 内将其重置,这样做也能缩短其持久性并提供一种重置 ID 的手段。

唯一性
唯一性表示在关联作用域内存在完全相同标识符的几率。在最高级别,全局唯一标识符永远不会有冲突项,即使在其他设备/应用上也是如此。唯一性级别取决于标识符的大小和用来创建它的随机性来源。例如,带有安装日期(例如 2015-01-05)的随机标识符的冲突几率要比带有 Unix 安装时间戳(例如 1445530977)的标识符高得多。

一般而言,您可以将用户帐户标识符视为具有唯一性(即,每个设备/帐户组合都具有一个唯一 ID)。另一方面,标识符在某一群体(例如,一群设备)内的唯一性越低,隐私权保护效果就越好,因为它用于跟踪个别用户的有效性会降低。

完整性保护和不可否认性
您可以使用难以欺诈或重播的标识符来证明关联的设备或帐户具有某些属性(例如,并非被垃圾邮件制作者利用的虚拟设备)。难以欺诈的标识符还能提供不可否认性。如果设备用密钥签署了一条消息,就难以辩称这条消息是由他人的设备发出的。不可否认性可能是用户需要的(例如,进行付款身份验证),也可能成为令人讨厌的属性(例如,用户可能会后悔发送消息)。

常见用例和适用标识符
此部分为大多数用例提供了使用 IMEI 或 SSAID 等硬件 ID 的替代方案。我们不建议依赖硬件 ID,因为用户无法重置它们,并且对这些 ID 集合的控制力通常也十分有限。

跟踪已注销用户的首选项
在此情况下,您要在服务器端保存每个设备的状态。

我们建议:实例 ID 或 GUID。

为何这样建议?

不建议让信息在重新安装后依然存在,因为用户可能想通过重新安装应用来重置其首选项。

跟踪已注销用户的行为
在此情况下,您已经根据用户在同一设备上不同应用/会话中的行为创建了他们的配置文件。

我们建议:广告 ID。

为何这样建议?

按照 Google Play 开发者内容政策,在广告用例中使用广告 ID 是强制性要求,因为用户可以重置它。

生成已注销/匿名用户的分析数据
在此情况下,您需要衡量已注销或匿名用户的使用情况统计信息和分析数据。

我们建议:实例 ID;如果实例 ID 无法满足需要,您还可以使用 GUID。

为何这样建议?

实例 ID 或 GUID 的作用域为创建它的应用,这样可以防止他人利用它们跟踪用户在不同应用中的行为。此外,您还可以通过清除应用数据或重新安装应用方便地对其进行重置。创建实例 ID 和 GUID 的过程简单明了:

创建实例 ID:String iid = InstanceID.getInstance(context).getId()
创建 GUID:String uniqueID = UUID.randomUUID().toString
请注意,如果您已告知用户您要收集的是匿名数据,就应该确保不将标识符关联到 PII 或其他可能关联到 PII 的标识符。

您还可以使用 Google Analytics for Mobile Apps,它提供了一种按应用进行分析的解决方案。

跟踪已注销用户的转化
在此情况下,您需要通过跟踪转化情况来确认营销策略是否成功。

我们建议:广告 ID。

为何这样建议?

这是一种与广告有关的用例,需要使用在不同应用中均可用的 ID,因此使用广告 ID 是最合适的解决方案。

处理多处安装
在此情况下,如果应用安装到同一用户的多台设备上,您需要识别正确的应用实例。

我们建议:实例 ID 或 GUID。

为何这样建议?

实例 ID 明确针对这一用途而设计;其作用域被限定在应用范围,这样就无法被用于跟踪用户在不同应用中的行为,并且在应用重新安装后它会被重置。如果遇到实例 ID 无法满足需要的罕见情况,您还可以使用 GUID。

防欺诈:强制执行免费内容限制/检测女巫攻击
在此情况下,您希望可以限制用户在设备上能够查看的免费内容(例如文章)的数量。

我们建议:实例 ID 或 GUID。

为何这样建议?

如果使用 GUID 或实例 ID,用户要想克服内容限制就必须重新安装应用,这已足以让大多数人打消这样的念头。如果这样的保护还不够,还可以利用 Android 提供的 DRM API 来限制对内容的访问。

管理电话和运营商功能
在此情况下,您的应用要与设备的电话和短信功能进行互动。

我们建议:IMEI、IMSI 和 Line1(需要 Android 6.0(API 级别 23)及更高版本中的 PHONE 权限组)。

为何这样建议?

如果使用电话/运营商相关功能有相应要求,则可以利用硬件标识符;例如,在切换手机运营商/SIM 卡插槽或通过 IP(适用于 Line1)发送短信时 - 基于 SIM 卡的用户帐户。但必须注意,在 Android 6.0+ 中,这些标识符只能通过运行时权限使用,并且用户可能会关闭该权限,因此您的应用应妥善处理这些异常。

滥用检测:发现自动程序和 DDoS 攻击
在此情况下,您希望发现多台正攻击后端服务的虚假设备。

我们建议:Safetynet API。

为何这样建议?

单纯标识符并不能有效说明设备的真伪。您可以利用 Safetynet API 的 SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce) 方法来验证发起请求的设备的完整性,借此验证请求来自真实的 Android 设备(而不是模拟器或仿冒其他设备的其他代码)。如需了解更多详细信息,请参阅 Safetynet 的 API 文档。

滥用检测:检测高价值被盗凭据
在此情况下,您希望发现是否在某一台设备上多次使用了高价值被盗凭据(例如,为了进行欺诈性支付)。

我们建议:IMEI/IMSI(需要 Android 6.0(API 级别 23)及更高版本中的 PHONE 权限组)。

为何这样建议?

通过被盗凭据,您可以利用设备将多个高价值被盗凭据(如令牌化信用卡)兑现。在这些情形下,软件 ID 可能会被重置以规避检测,因此可以使用硬件标识符。

0 0