Android SDK - 数据隐私

遵守数据隐私法律

通过向 Singular 告知用户针对 GDPR、CCPA 及其他消费者隐私法规的同意选择,实施符合隐私要求的数据采集。

当用户同意或拒绝与第三方共享其信息时,使用 Singular 的隐私方法传达其选择。这可确保符合 California Consumer Privacy Act (CCPA) 等法规,并使合作伙伴能够尊重用户的隐私偏好。

了解更多: 有关 Singular 如何处理隐私同意的详细信息,请参阅 用户隐私和限制数据共享


限制数据共享

控制第三方数据共享

使用 limitDataSharing 方法告知 Singular 用户是否同意与第三方合作伙伴共享其个人数据。

方法签名:

public static void limitDataSharing(boolean shouldLimitDataSharing);

参数:

  • false: 用户已选择加入并同意共享其数据
  • true: 用户已选择退出,不同意共享其数据

重要: 虽然此方法为可选,但会影响归因数据共享。某些合作伙伴仅在被明确告知用户已选择加入后才会共享完整的归因信息。

使用示例

Kotlin Java
// User has opted in to share their data
Singular.limitDataSharing(false)

// User has opted out and declined to share their data
Singular.limitDataSharing(true)

// Example: Set based on user preference
fun handlePrivacyConsent(userConsented: Boolean) {
    // Pass inverse: false = opted in, true = opted out
    Singular.limitDataSharing(!userConsented)

    Log.d("Privacy", "Data sharing: ${if (userConsented) "Enabled" else "Limited"}")
}

工作原理:

Singular 会在 用户隐私回调 中使用此设置,并将其传递给出于法规合规需要它的合作伙伴。


按事件级别限制数据共享

覆盖单一事件的数据共享

通过将 Attributes.sngAttrLimitDataSharing 归因与其他事件参数一起传递,可以为单个事件或自定义收入调用覆盖全局 limitDataSharing 设置。当用户在执行某项操作时同步更新同意,并希望该特定事件立即反映新的选择,而不更改后续事件的 SDK 全局设置时,这非常有用。

接受的值:

  • false: 仅针对此事件用户选择加入
  • true: 仅针对此事件用户选择退出

重要: 该归因必须为 Boolean 。非布尔值输入将被忽略,请求将回退到全局 limitDataSharing 设置。无论哪种情况,在请求发送之前,该归因都会从事件参数中移除,因此永远不会出现在事件载荷中。

使用示例

Kotlin Java
// Example 1: Standard event, opt out only for this event
val args = JSONObject()
    .put(Attributes.sngAttrLimitDataSharing.toString(), true)
    .put("order_id", "12345")

Singular.event("checkout_completed", args.toString())

// Example 2: Custom revenue, opt in only for this event
val attributes = mutableMapOf<String, Any>().apply {
    put(Attributes.sngAttrLimitDataSharing.toString(), false)
    put("sku", "premium_monthly")
}

Singular.customRevenue("premium_purchase", "USD", 9.99, attributes)

工作原理:

当提供有效的布尔值时,SDK 会从事件参数中移除该归因,并将其写入请求的 data_sharing_options.limit_data_sharing 字段 —— 仅对该单一请求覆盖全局 limitDataSharing 设置。全局设置和 getLimitDataSharing 的结果不会被修改,因此任何未提供该归因的后续事件将继续使用全局选择。


GDPR 合规方法

管理用户追踪同意并控制 SDK 功能,以遵守 GDPR(《通用数据保护条例》)和其他隐私法规。

追踪同意管理

trackingOptIn

通过向 Singular 服务器发送 GDPR 选择加入事件,记录用户明确的追踪同意。

方法签名:

Singular.trackingOptIn()

使用时机:

  • GDPR 合规: 在 GDPR 监管区域内,当用户明确同意追踪时调用
  • 同意记录: 在 Singular 系统中将用户标记为已提供 GDPR 同意
  • 默认行为: 未调用此方法时,SDK 仍会继续追踪,但不会专门记录同意
Kotlin Java
// User accepted tracking consent
Singular.trackingOptIn()

// Example: Call after consent dialog
fun onUserAcceptedTracking() {
    Singular.trackingOptIn()
    Log.d("GDPR", "User opted in to tracking")
}

追踪控制方法

stopAllTracking

完全禁用此设备上当前用户的所有 SDK 追踪活动。

方法签名:

Singular.stopAllTracking()

重要警告: 在调用 resumeAllTracking() 之前,此方法会永久禁用 SDK。即使应用重启,该禁用状态仍会保持,只能通过编程方式恢复。

行为:

  • 立即生效: 立即停止所有追踪、事件上报和数据采集
  • 持久状态: 即使应用关闭后重新打开,仍保持禁用
  • 无自动重置: 必须显式调用 resumeAllTracking() 才能重新启用
Kotlin Java
// User declined all tracking
Singular.stopAllTracking()

// Example: Handle user opt-out
fun onUserDeclinedTracking() {
    Singular.stopAllTracking()
    Log.d("Privacy", "All tracking stopped")

    // Optionally store preference
    saveUserTrackingPreference(false)
}

resumeAllTracking

在追踪被 stopAllTracking() 停止后,重新启用追踪。

方法签名:

Singular.resumeAllTracking()

使用场景:

  • 同意变更: 用户更改隐私偏好并重新选择加入追踪
  • 隐私设置: 用户通过应用设置菜单更新同意
  • 区域合规: 当用户移动到非监管区域时重新启用追踪
Kotlin Java
// User opted back in to tracking
Singular.resumeAllTracking()

// Example: Handle consent update
fun onUserResumedTracking() {
    Singular.resumeAllTracking()
    Log.d("Privacy", "Tracking resumed")

    // Optionally update stored preference
    saveUserTrackingPreference(true)
}

isAllTrackingStopped

检查当前用户的追踪是否已被禁用。

方法签名:

public static boolean isAllTrackingStopped();

返回值:

  • true: 追踪当前已通过 stopAllTracking() 停止
  • false: 追踪处于活动状态(从未停止或已恢复)

如果 SDK 尚未初始化,无论之前的追踪状态如何,都会返回 false 。在依赖此值之前,请始终确认 Singular.init() 已完成,并将 false 理解为"未停止或尚未初始化",而不是"正在主动追踪"。

Kotlin Java
// Check current tracking status
val isTrackingStopped = Singular.isAllTrackingStopped()

// Example: Display privacy status in settings
fun getPrivacyStatusText(): String {
    return if (Singular.isAllTrackingStopped()) {
        "Tracking: Disabled"
    } else {
        "Tracking: Enabled"
    }
}

// Example: Sync UI with tracking state
fun updatePrivacyToggle() {
    val isStopped = Singular.isAllTrackingStopped()
    privacyToggle.isChecked = !isStopped
    Log.d("Privacy", "Current tracking state: ${if (isStopped) "Stopped" else "Active"}")
}

儿童隐私保护

trackingUnder13

告知 Singular 用户未满 13 岁,以遵守 COPPA(《儿童在线隐私保护法》)和其他儿童隐私法规。

方法签名:

Singular.trackingUnder13()

合规要求:

  • COPPA 合规: 在美国采集 13 岁以下儿童数据的应用必须使用
  • 年龄限制内容: 当用户在注册或年龄验证时被识别为 13 岁以下时使用
  • 受限追踪: 限制数据采集以遵守儿童隐私保护法律
Kotlin Java
// User identified as under 13
Singular.trackingUnder13()

// Example: Call after age verification
fun onAgeVerified(userAge: Int) {
    if (userAge < 13) {
        Singular.trackingUnder13()
        Log.d("Privacy", "COPPA mode enabled for user under 13")
    }
}

重要: 在确定用户未满 13 岁后,请尽快调用此方法,最好在应用初始化时或年龄验证后立即调用。这可确保后续所有追踪均遵守儿童隐私法规。


setLimitAdvertisingIdentifiers

对于混合受众应用,在 SDK 初始化后限制广告标识符(Android 上的 GAID)的采集和使用。

方法签名:

public static void setLimitAdvertisingIdentifiers(boolean enabled);

参数:

  • enabled: 传入 true 以限制广告标识符的采集;传入 false 以恢复正常采集。

请勿将其与配置时变体混淆。 运行时方法 Singular.setLimitAdvertisingIdentifiers(boolean) 需要一个布尔参数,而配置时方法 SingularConfig.withLimitAdvertisingIdentifiers() 不接受参数,并无条件启用该限制。不带参数调用运行时方法将无法编译。

使用场景:

  • 混合受众应用: 在应用启动后确定年龄、面向成人和儿童的应用
  • 动态隐私控制: 根据用户行为或访问的内容调整追踪
  • 运行时限制: 在 SDK 初始设置之后应用广告标识符限制
Kotlin Java
// Limit advertising identifiers after initialization
Singular.setLimitAdvertisingIdentifiers(true)

// Example: Mixed audience app with age gate
fun onKidsModeSwitched(isKidsMode: Boolean) {
    if (isKidsMode) {
        Singular.setLimitAdvertisingIdentifiers(true)
        Log.d("Privacy", "Advertising identifiers limited for kids mode")
    }
}

// Example: Content-based restrictions
fun onViewingChildrensContent() {
    Singular.setLimitAdvertisingIdentifiers(true)
    Log.d("Privacy", "Ad identifiers restricted for children's content")
}

配置替代方案: 如果在 SDK 启动前已知道隐私要求,您也可以在 SDK 初始化时使用 SingularConfig.withLimitAdvertisingIdentifiers() 来限制广告标识符。

配置方法

对于事先了解隐私要求的应用,在 SDK 初始化期间设置广告标识符限制。

Kotlin Java
// Limit advertising identifiers at initialization
val config = SingularConfig("SDK_KEY", "SDK_SECRET")
    .withLimitAdvertisingIdentifiers()

Singular.init(applicationContext, config)

实施最佳实践

完整的隐私管理示例

实施全面的隐私控制,以尊重用户偏好并遵守相关法规。

Kotlin Java
class PrivacyManager(private val context: Context) {

    private val prefs = context.getSharedPreferences("privacy_prefs", Context.MODE_PRIVATE)

    // Initialize privacy settings on app start
    fun initializePrivacy() {
        val hasUserConsent = getUserConsent()
        val allowDataSharing = getDataSharingPreference()
        val userAge = getUserAge()

        // Apply stored preferences
        if (hasUserConsent) {
            Singular.trackingOptIn()
            Singular.resumeAllTracking()
        } else {
            Singular.stopAllTracking()
        }

        // Set data sharing preference
        Singular.limitDataSharing(!allowDataSharing)

        // Handle children's privacy
        if (userAge > 0 && userAge < 13) {
            Singular.trackingUnder13()
            Singular.setLimitAdvertisingIdentifiers(true)
        }

        Log.d("Privacy", "Initialized: consent=$hasUserConsent, sharing=$allowDataSharing, age=$userAge")
    }

    // User accepts tracking via consent dialog
    fun userAcceptedTracking() {
        saveUserConsent(true)
        Singular.trackingOptIn()
        Singular.resumeAllTracking()
        Log.d("Privacy", "User accepted tracking")
    }

    // User declines tracking
    fun userDeclinedTracking() {
        saveUserConsent(false)
        Singular.stopAllTracking()
        Log.d("Privacy", "User declined tracking")
    }

    // User updates data sharing preference
    fun setDataSharingEnabled(enabled: Boolean) {
        saveDataSharingPreference(enabled)
        // Note: limitDataSharing uses inverse logic
        Singular.limitDataSharing(!enabled)
        Log.d("Privacy", "Data sharing: ${if (enabled) "Enabled" else "Limited"}")
    }

    // Handle age verification result
    fun onAgeVerified(age: Int) {
        saveUserAge(age)
        if (age < 13) {
            Singular.trackingUnder13()
            Singular.setLimitAdvertisingIdentifiers(true)
            Log.d("Privacy", "COPPA restrictions applied for user under 13")
        }
    }

    // Get current tracking status
    fun isTrackingEnabled(): Boolean {
        return !Singular.isAllTrackingStopped()
    }

    // Private helper methods
    private fun getUserConsent(): Boolean {
        return prefs.getBoolean("user_consent", false)
    }

    private fun saveUserConsent(consent: Boolean) {
        prefs.edit().putBoolean("user_consent", consent).apply()
    }

    private fun getDataSharingPreference(): Boolean {
        return prefs.getBoolean("data_sharing", false)
    }

    private fun saveDataSharingPreference(enabled: Boolean) {
        prefs.edit().putBoolean("data_sharing", enabled).apply()
    }

    private fun getUserAge(): Int {
        return prefs.getInt("user_age", 0)
    }

    private fun saveUserAge(age: Int) {
        prefs.edit().putInt("user_age", age).apply()
    }
}

最佳实践:

  • 持久化存储: 将用户偏好保存到 SharedPreferences 或安全存储中
  • 提前初始化: 尽可能在 SDK 初始化之前应用隐私设置
  • UI 同步: 使用 isAllTrackingStopped() 保持设置 UI 与实际 SDK 状态同步
  • 清晰沟通: 在应用设置中提供清晰、易于访问的隐私控制
  • 年龄验证: 为面向儿童的应用实施强健的年龄验证
  • 组合控制: 对于 13 岁以下用户,同时应用 trackingUnder13() setLimitAdvertisingIdentifiers(true)