Administrator
发布于 2026-09-22 / 2,808 阅读
0
0

Android Privacy Sandbox:Topics API、SDK Runtime 与归因重构

前言

如果你做广告变现或者用户分析相关功能,Google 正在用 Privacy Sandbox 把你过去十年的技术方案推翻。

Privacy Sandbox 不是"加个隐私弹窗"这么简单。它做的事情是:在系统层消除跨 App 追踪的技术基础。GAID(Google Advertising ID)会在用户关闭后失效,第三方 SDK 不再能直接跑在你的进程里读取 IMEI/MAC。

这一篇讲三个核心 API:Topics API(兴趣推断)、SDK Runtime(SDK 沙箱)、Attribution Reporting(归因报告),以及怎么适配。

一、Privacy Sandbox 整体架构

+------------------------------------------+
|              你的 App                      |
|  +----------+    +------------------+     |
|  | 业务代码  |    | SDK Runtime Client|    |
|  +----------+    +--------+---------+     |
|                           |               |
+---------------------------+---------------+
                            |  IPC (跨进程)
                +-----------+-----------+
                |   SDK Runtime 进程     |
                |  (沙箱,隔离你的 App)   |
                |  +-----------------+  |
                |  | Third-party SDK  |  |
                |  +-----------------+  |
                +-----------+-----------+
                            |
              +-------------+-------------+
              |     Privacy Sandbox APIs    |
              |  +--------+ +------------+ |
              |  | Topics | | Attribution | |
              |  +--------+ +------------+ |
              |  +-----------+             |
              |  | Protected |             |
              |  | Audience  |             |
              |  +-----------+             |
              +-----------------------------+

核心思路:SDK 不能直接访问用户数据,所有用户数据归系统管。SDK 只能通过系统 API 拿到"聚合后的、不可追踪到个人的"结果。

二、Topics API

Topics API 替代 GAID 做兴趣推断。系统根据用户最近使用的 App 推断出 5 个兴趣话题(Topics),广告 SDK 可以查询这些话题来匹配广告,但拿不到用户身份。

工作机制

1. 系统持续观察用户使用的 App
2. 每个周期(默认 7 天)计算 Top 5 Topics
3. 广告 SDK 调 getTopics() -> 拿到一个 Topic 列表
4. 同一周期内,同一 App 只能拿到一个 Topic(随机选)
5. 3 周后 Topic 过期

集成代码

// 广告 SDK 侧:获取 Topics
class AdManager(context: Context) {
    private val topicsManager = context.getSystemService(TopicsManager::class.java)

    suspend fun getTopics(): List<Topic> = suspendCoroutine { cont ->
        val request = GetTopicsRequest.Builder()
            .setAdsExtName("com.example.adnetwork")  // 你的 SDK 包名
            .build()

        topicsManager.getTopics(
            executor = Executors.newSingleThreadExecutor(),
            callback = object : OutcomeReceiver<GetTopicsResponse, Exception> {
                override fun onResult(result: GetTopicsResponse) {
                    cont.resume(result.topics)
                }
                override fun onError(error: Exception) {
                    cont.resume(emptyList())
                }
            },
            input = request
        )
    }
}

返回的 Topic 对象包含:

  • taxonomyVersions: 分类版本号

  • modelVersion: 模型版本

  • id: 话题 ID(对应 IAB 分类)

  • topicsVersion: 系统版本

你可以把 Topic ID 映射到广告投放策略,但不能拿它追踪用户。

隐私保证

  1. 不可链接:App A 拿到的 Topic 和 App B 拿到的不一样(同一周期内随机分配)

  2. 过期:3 周后旧 Topic 消失

  3. 可控:用户在系统设置里可以关闭 Topics,或查看哪些 App 查询过他的 Topics

三、SDK Runtime

SDK Runtime 把第三方 SDK 跑在一个独立进程里,跟你的 App 进程隔离。SDK 不能直接访问你的 App 内存、SharedPreferences、数据库。

契约式通信

你的 App 和 SDK 通过 AIDL 或 RemoteViews 通信:

// 1. SDK 侧:实现接口
class AdService : SandboxedSdkProvider() {
    override fun onLoadSdk(params: LoadSdkParams): SandboxedSdk {
        return SandboxedSdk(AdServiceImpl())
    }
}

class AdServiceImpl : IAdService.Stub() {
    override fun loadAd(adUnitId: String): AdResponse {
        // 在 SDK 进程里执行,拿不到 App 的数据
        return fetchAdFromServer(adUnitId)
    }
}

// 2. App 侧:调用
class MainActivity : ComponentActivity() {
    private lateinit var sdkSandboxManager: SdkSandboxManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sdkSandboxManager = getSystemService(SdkSandboxManager::class.java)

        // 加载 SDK
        val sandboxedSdk = sdkSandboxManager.loadSdk(
            "com.example.adsdk",
            Bundle()
        )
        // 拿到接口
        val adService = IAdService.Stub
            .asInterface(sandboxedSdk.getInterface())
        // 调用
        val ad = adService?.loadAd("banner_home")
    }
}

实际影响

圽景

旧方式

SDK Runtime

SDK 读 IMEI

直接读

不行,跨进程

SDK 读 SharedPreferences

直接读

不行,进程隔离

SDK 崩溃

App 跟着崩

SDK 进程崩,App 可捕获

SDK 内存泄漏

影响 App

独立进程,可重启

SDK 大小

算进 App 体积

可缓存共享

四、Attribution Reporting API

替代传统像素追踪和 MMP(AppsFlyer/Adjust)的设备指纹方案。

工作流程

1. 广告展示:App A 展示广告 -> 注册 Source(source_event_id)
2. 用户转化:用户安装 App B 并触发事件 -> 注册 Trigger(trigger_event_id)
3. 归因报告:系统匹配 Source 和 Trigger -> 生成归因报告
4. 报告发送:延迟发送给广告主服务器(加入噪声保护隐私)

代码示例

// 广告展示侧:注册 Source
class AdDisplayActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val manager = getSystemService(AttributionManager::class.java)

        val sourceParams = SourceRegistrationRequest.Builder(
            AttributionRegistrationRequest.SourceParams(
                destination = "android-app://com.example.shopping",
                sourceEventId = 12345L,
                sourceType = SourceType.EVENT,
                inputEvent = null
            )
        ).build()

        manager.registerWebSource(
            request = sourceParams,
            executor = mainExecutor,
            callback = object : OutcomeReceiver<Unit, Exception> {
                override fun onResult(result: Unit) { /* 注册成功 */ }
                override fun onError(error: Exception) { /* 失败 */ }
            }
        )
    }
}

// 转化侧:注册 Trigger
class ConversionActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val manager = getSystemService(AttributionManager::class.java)

        val triggerParams = AttributionRegistrationRequest.TriggerParams(
            destination = "android-app://com.example.advertiser",
            triggerEventId = 67890L,
            triggerData = "purchase".toByteArray()
        )

        manager.registerTrigger(triggerParams)
    }
}

隐私保护机制

  • 延迟报告:归因报告不是实时发送,有 24-48 小时延迟

  • 噪声注入:报告里加入随机假数据,无法精确追踪单个用户

  • 预算限制:每个 Source 的 Trigger 匹配次数有上限

  • 数据汇总:不支持精确的事件级归因,只支持聚合维度

五、适配建议

  1. 广告变现 App:迁移到 Topics API + Attribution Reporting,保留 GAID 作为 fallback(用户未关闭时)

  2. 分析 SDK:评估是否需要迁移到 SDK Runtime。如果 SDK 需要读取 App 数据,迁移成本很高

  3. 时间线:

    • 2024-2025:Privacy Sandbox API 稳定版可用

    • 2026:Android 15+ 开始限制 GAID 可用性

    • 2027+:可能完全弃用 GAID

  4. 兼容方案:

fun getAdvertisingId(context: Context): String? {
    // 先尝试 GAID
    val adId = AdvertisingIdClient.getAdvertisingIdInfo(context)
    if (adId.id != "00000000-0000-0000-0000-000000000000") {
        return adId.id  // 用户未关闭 GAID
    }
    // fallback: Topics API
    return null  // 用 Topics 代替
}

小结

Privacy Sandbox 重构的是广告变现的技术底座。短期影响小(GAID 还在),但中期(2026-2027)会直接影响收入。做广告变现的团队需要提前评估迁移成本,特别是 SDK Runtime 的跨进程改造。

下篇讲 Android 音频系统,从 AudioTrack 到 Oboe,低延迟音频怎么做。


评论