Privacy Sandbox on Android 开发者预览版现已推出!了解如何开始使用,并继续提供反馈

Attribution Reporting API 开发者指南

Attribution Reporting API 旨在通过消除对跨方用户标识符的依赖来更好地保护用户隐私,并支持跨应用进行归因和转化衡量的关键用例。本开发者指南将介绍如何配置和测试 Attribution Reporting API,通过调用为广告点击、观看和转化事件注册相关触发器和来源的方法来注册广告点击、观看和转化。

本指南将指导您设置服务器端点并构建调用这些服务的客户端应用。如需详细了解 Attribution Reporting API 的整体设计,请参阅设计方案

关键术语

  • “归因来源”是指点击或观看。
  • 触发器是可归因于转化的事件。
  • 报告包含有关触发器和相应归因来源的数据。这些报告是响应触发器事件而发送的。Attribution Reporting API 支持事件级报告可汇总报告

准备工作

为了使用 Attribution Reporting API,请完成以下几部分中列出的服务器端任务和客户端任务。

设置 Attribution Reporting API 端点

Attribution Reporting API 需要一组可通过测试设备或模拟器进行访问的端点。请为以下每个服务器端任务创建一个端点:

您可以通过多种方法来设置所需端点:

  • 最快上手的方法是,将示例代码库中的 OpenAPI v3 服务定义部署到模拟或微服务平台。您可以使用 PostmanPrism 或接受此格式的任何其他模拟服务器平台。部署每个端点,并跟踪在您的应用中使用的 URI。如需验证报告发送,请参阅之前对模拟或无服务器平台发出的调用。
  • 使用基于 Spring BootKotlin 示例运行您自己的独立服务器。在云服务提供商的基础架构或内部基础架构上部署此服务器。
  • 使用服务定义作为示例,将端点集成到您的现有系统中。

接受来源注册

此端点应可通过如下所示的 URI 寻址:

https://adtech.example/attribution_source

当客户端应用注册归因来源时,会提供此服务器端点的 URI。然后,Attribution Reporting API 会发出请求并在其中包含以下标头之一:

  • 对于点击事件:

    Attribution-Reporting-Source-Info: navigation
    
  • 对于观看事件:

    Attribution-Reporting-Source-Info: event
    

配置服务器端点,让其使用以下内容进行响应:

// Metadata associated with attribution source.
Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "web_destination": "[eTLD+1]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "priority": "[64 bit signed integer]",
  "filter_data": {
    "[key name 1]": ["key1 value 1", "key1 value 2"],
    "[key name 2]": ["key2 value 1", "key2 value 2"],
    // Note: "source_type" key will be automatically generated as
    // one of {"navigation", "event"}.
  },
  // Attribution source metadata specifying histogram contributions in aggregate
  // report.
  "aggregation_keys": [{
    "id": "[key name]",
    "key_piece": "[key piece value]",
  },
  ..
  ]
    "debug_key": "[64-bit unsigned integer]"
}
// Specify additional adtech URLs to register this source with.
Attribution-Reporting-Redirects: <Adtech partner URIs; comma-separated>

以下是添加了示例值的示例:

Attribution-Reporting-Register-Source: {
  "destination": "android-app://com.example.advertiser",
  "source_event_id": "234",
  "expiry": "60000",
  "priority": "5",
  "filter_data": {
    "product_id": ["1234"]
  },
  "aggregation_keys": [{
  // Generates a "0x159" key piece named (low order bits of the key) for the key
  // named "campaignCounts".
    "id": "campaignCounts",
  // User saw an ad from campaign 345 (out of 511).
    "key_piece": "0x159",
  },
  {
  // Generates a "0x5" key piece (low order bits of the key) for the key named
  // "geoValue".
    "id": "geoValue",
  // Source-side geo region = 5 (US), out of a possible ~100 regions.
    "key_piece": "0x5",
  }]
}
Attribution-Reporting-Redirects: https://adtechpartner1.example?their_ad_click_id=567, https://adtechpartner2.example?their_ad_click_id=890

如果 Attribution-Reporting-Redirects 包含广告技术合作伙伴的 URI,Attribution Reporting API 就会向每个 URI 发出类似的请求。每个广告技术合作伙伴都必须配置一台服务器,让其使用以下标头进行响应:

Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "web_destination": "[eTLD+1]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "priority": "[64 bit signed integer]",
  "filter_data": {
    "[key name 1]": ["key1 value 1", "key1 value 2"],
    "[key name 2]": ["key2 value 1", "key2 value 2"],
    // Note: "source_type" key will be automatically generated as
    // one of {"navigation", "event"}.
  },
  "aggregation_keys": [{
    "id": "[key name]",
    "key_piece": "[key piece value]",
   },
   ..
  ]
}
// The Attribution-Reporting-Redirects header is ignored for adtech partners.

接受转化触发器注册

此端点应可通过如下所示的 URI 寻址:

https://adtech.example/attribution_trigger

当客户端应用注册触发器事件时,会提供此服务器端点的 URI。然后,Attribution Reporting API 会发出请求并在其中包含以下标头之一:

配置服务器端点,让其使用以下内容进行响应:

// Metadata associated with trigger.
Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    // "trigger_data returned" in event reports is truncated to
    // the last 1 or 3 bits, based on conversion type.
    "trigger_data": "[unsigned 64-bit integer]",
    "priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]",
    // "filter" and "not_filters" are optional fields which allow configuring
    // different event trigger data based on source's filter_data.
    // Note: "source_type" can be used as a key to filter based on the source's
    // type "navigation" or "event".
    // The first "Event-Trigger" that matches, based on "filters" and
    // "not_filters", is used for report generation. If there are no matches,
    // no report is generated.
    "filters": {
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from "filters" or the attribution source's
      // "filter_data", it isn't used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    },
    "not_filters":  {
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from "not_filters" or the attribution source's
      // "filter_data", it isn't used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }
  }],
  // Specify a list of dictionaries that generates aggregation keys.
  "aggregabtable_trigger_data": [
    // Each dictionary entry independently adds pieces to multiple source keys.
    {
      "key_piece": "[key piece value]",
      "source_keys": ["[key name the key piece value applies to]",
      ["list of IDs in source to match. Non-matching IDs are ignored"]]
      // "filters" and "not_filters" are optional fields, similar to the event
      // trigger data filter fields.
      "filters": {
        "[key name 1]": ["key1 value 1", "key1 value 2"]
      },
      "not_filters":  {
          "[key name 1]": ["key1 value 1", "key1 value 2"],
          "[key name 2]": ["key2 value 1", "key2 value 2"],
      }
    },
    ..
  ],
  // Specify an amount of an abstract value, which can be integers in [1, 2^16],
  // to contribute to each key that is attached to aggregation keys in the order
  // that they're generated.
  "aggregabtable_values": [
     // Each source event can contribute a maximum of L1 = 2^16 to the aggregate
     // histogram.
    {
     "[key_name]": [value]
    },
    ..
  ]
"debug_key": "[64-bit unsigned integer]"
}
// Specify additional Adtech URLs to register this trigger with.
Attribution-Reporting-Redirects: <Adtech partner URIs, comma-separated>

以下是添加了示例值的示例:

Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    "trigger_data": "1122", // Returns 010 for CTCs and 0 for VTCs in reports.
    "priority": "3",
    "deduplication_key": "3344"
    "filters": {
      "product_id": ["1234"],
      "source_type": ["event"]
    }
  },
  {
    "trigger_data": "4", // Returns 100 for CTCs and 0 for VTCs in reports.
    "priority": "3",
    "deduplication_key": "3344"
    "filters": {
      "product_id": ["1234"],
      "source_type": ["navigation"]
    }
  }],
  "aggregatable_trigger_data": [
    // Each dictionary independently adds pieces to multiple source keys.
    {
      // Conversion type purchase = 2 at a 9-bit offset, i.e. 2 << 9.
      // A 9-bit offset is needed because there are 511 possible campaigns,
      // which takes up 9 bits in the resulting key.
      "key_piece": "0x400",// Conversion type purchase = 2
      // Apply this key piece to:
      "source_keys": ["campaignCounts"]
    },
    {
      // Purchase category shirts = 21 at a 7-bit offset, i.e. 21 << 7.
      // A 7-bit offset is needed because there are ~100 regions for the geo
      // key, which takes up 7 bits of space in the resulting key.
      "key_piece": "0xA80",
      // Apply this key piece to:
      "source_keys": ["geoValue", "nonMatchingIdsListedHereAreIgnored"]
    }
  ],
  "aggregatable_values": [
    {
      // Privacy budget for each key is L1 / 2 = 2^15 (32768).
      // Conversion count was 1.
      // Scale the count to use the full budget allocated: 1 * 32768 = 32768.
      "campaignCounts": 32768,

      // Purchase price was $52.
      // Purchase values for the app range from $1 to $1,024 (integers only).
      // Scaling factor applied is 32768 / 1024 = 32.
      // For $52 purchase, scale the value by 32 ($52 * 32 = $1,664).
      "geoValue": 1664
    }
  ]
}
Attribution-Reporting-Redirects:https://adtechpartner.example?app_install=567

如果 Attribution-Reporting-Redirects 包含广告技术合作伙伴的 URI,Attribution Reporting API 就会向每个 URI 发出类似的请求。每个广告技术合作伙伴都必须配置一台服务器,让其使用以下标头进行响应:

// Metadata associated with trigger.
Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    // "trigger_data" returned in event reports is truncated to
    // the last 1 or 3 bits, based on conversion type.
    "trigger_data": "[unsigned 64-bit integer]",
    "priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]",
    // "filters" and "not_filters" are optional fields which allow configuring
    // for configuring different event trigger data based on the attribution]
    // source's "filter_data". Note that "source_type" can be used as a key to
    // filter based on the source's type "navigation" or "event".
    // The first "Event-Trigger" that matches, based on "filters" and
    // "not_filters", is used for report generation. If there are no matches,
    // no report is generated.
    "filters": {
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from "filters" or the attribution source's
      // "filter_data", it isn't used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    },
    "not_filters":  {
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from "not_filters" or the attribution source's
      // "filter_data", it isn't used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }
  }],
  "aggregatable_trigger_data": [
    // Each dictionary entry independently adds pieces to multiple source keys.
    {
      "key_piece": "[key piece value]",
      "source_keys": ["[key name the key piece value applies to]",
      ["list of IDs in source to match. Non-matching IDs are ignored"]],
      // "filters" and "not_filters" are optional fields, similar to the event
      // trigger data filter fields.
      "filters": {
        "[key name 1]": ["key1 value 1", "key1 value 2"]
      },
      "not_filters":  {
          "[key name 1]": ["key1 value 1", "key1 value 2"],
          "[key name 2]": ["key2 value 1", "key2 value 2"],
      }
    },
    ..
  ],
  // Specify an amount of an abstract value which can be integers in [1, 2^16] to
  // contribute to each key that is attached to aggregation keys in the order they
  // are generated.
  "aggregatable_values": [
    // Each source event can contribute a maximum of L1 = 2^16 to the aggregate
    // histogram.
    {
     "[key_name]": [value]
    }
  ]
}
// The Attribution-Reporting-Redirects header is ignored for adtech partners.

接受事件级报告

此端点应可通过 URI 寻址。如需详细了解如何注册 URI,请参阅注册 Privacy Sandbox 帐号。(系统会根据用于接受来源注册和触发器注册的服务器的 eTLD+1 推断 URI。)使用接受来源注册接受触发器注册的端点的示例 URI,推断出此端点的 URI 为:

https://adtech.example/.well-known/attribution-reporting/report-attribution

配置此服务器,以接受使用以下格式的 JSON 请求:

{
  "attribution_destination": "android-app://com.advertiser.example",
  "source_event_id": "12345678",
  "trigger_data": "2",
  "report_id": "12324323",
  "source_type": "navigation",
  "randomized_trigger_rate": "0.02"
   [Optional] "source_debug_key": "[64-bit unsigned integer]",
   [Optional] "trigger_debug_key": "[64-bit unsigned integer]",
}

调试键可让您进一步了解归因报告;详细了解配置方式。

接受可汇总报告

此端点应可通过 URI 寻址。如需详细了解如何注册 URI,请参阅注册 Privacy Sandbox 帐号。(系统会根据用于接受来源注册和触发器注册的服务器来源推断 URI。)使用接受来源注册接受触发器注册的端点的示例 URI,推断出此端点的 URI 为:

https://adtech.example/.well-known/attribution-reporting/report-aggregate-attribution

目前,对于可汇总报告,系统会同时填充加密字段和未加密字段。加密报告可以让您开始使用汇总服务进行测试,而未加密的字段则可以让您了解设置的键值对的数据构建方式。

配置此服务器,以接受使用以下格式的 JSON 请求:

{
  // Info that the aggregation services also need encoded in JSON
  // for use with AEAD. Line breaks added for readability.
  "shared_info": "{
     \"api\":\"attribution-reporting\",
     \"attribution_destination\": \"android-app://com.advertiser.example.advertiser\",
     \"scheduled_report_time\":\"[timestamp in seconds]\",
     \"source_registration_time\": \"[timestamp in seconds]\",
     \"version\":\"[api version]\",
     \"report_id\":\"[UUID]\",
     \"reporting_origin\":\"https://reporter.example\" }",

  // In the current Developer Preview release, The "payload" and "key_id" fields
  // are not used because the platform does not yet encrypt aggregate reports.
  // Currently, the "debug_cleartext_payload" field holds unencrypted reports.
  "aggregation_service_payloads": [
    {
      "payload": "[base64 HPKE encrypted data readable only by the aggregation service]",
      "key_id": "[string identifying public key used to encrypt payload]",

      "debug_cleartext_payload": "[unencrypted payload]",
    },
  ],

  "source_debug_key": "[64 bit unsigned integer]",
  "trigger_debug_key": "[64 bit unsigned integer]"
}

调试键可让您进一步了解归因报告;详细了解配置方式。

设置 Android 客户端

客户端应用需要注册归因来源和触发器,并允许生成事件级报告和可汇总报告。如需准备 Android 客户端设备或模拟器以使用 Attribution Reporting API,请执行以下操作:

  1. 为 Privacy Sandbox on Android 设置开发环境
  2. 将系统映像安装到受支持的设备上设置支持 Privacy Sandbox on Android 的模拟器
  3. 通过运行以下 adb 命令启用对 Attribution Reporting API 的访问权限。(此 API 默认处于停用状态)。

    adb shell device_config put adservices ppapi_app_allow_list \"*\"
    
  4. 添加 ACCESS_ADSERVICES_ATTRIBUTION 权限,并为应用创建广告服务配置,以便使用 Attribution Reporting API,如以下代码段所示:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
    
  5. 指定清单中引用的广告服务 XML 资源,例如 res/xml/ad_services_config.xml。详细了解广告服务权限和 SDK 访问权限控制

    <property android:name="android.adservices.AD_SERVICES_CONFIG"
          android:resource="@xml/ad_services_config" />
    
  6. 在清单的 <application> 元素中引用广告服务配置:

    <ad-services-config>
        <attribution allowAllToAccess="true" />
    </ad-services-config>
    

注册广告事件

您的应用需要在来源和转化发生时进行记录,以确保正确报告来源和转化。MeasurementManager 类提供相关方法,可帮助您注册归因来源事件转化触发器

注册归因来源事件

有人观看或点击广告时,发布商应用会调用 registerSource() 来注册代码段中所示的归因来源。

Attribution Reporting API 支持以下类型的归因来源事件:

  • 点击:通常在与 onClick() 类似的回调方法中注册。相应的触发器事件通常紧跟在点击事件之后发生。此类事件提供有关用户互动的详细信息,因此适合作为高优先级的归因来源。
  • 观看:通常在与 onAdShown() 类似的回调方法中注册。相应的触发器事件可能会在观看事件发生几小时或几天后发生。

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null

// Use the URI of the server-side endpoint that accepts attribution source
// registration.
val attributionSourceUri: Uri =
  Uri.parse("https://adtech.example/attribution_source?AD_TECH_PROVIDED_METADATA")

val future = CompletableFuture<Void>()

adView.setOnTouchListener(_: View?, event: MotionEvent?)) ->
    exampleClickEvent = event
    true
}

// Register Click Event
measurementManager.registerSource(
        attributionSourceUri,
        exampleClickEvent,
        CALLBACK_EXECUTOR,
        future::complete)

// Register View Event
measurementManager.registerSource(
        attributionSourceUri,
        null,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private InputEvent exampleClickEvent;

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URI of the server-side endpoint that accepts attribution source
// registration.
Uri attributionSourceUri =
Uri.parse("https://adtech.example/attribution_source?AD_TECH_PROVIDED_METADATA");

CompletableFuture<Void> future = new CompletableFuture<>();

adView.setOnTouchListener(v, event)) -> {
    exampleClickEvent = event;
    return true;
}

// Register Click Event
measurementManager.registerSource(attributionSourceUri, exampleClickEvent,
        CALLBACK_EXECUTOR, future::complete);

// Register View Event
measurementManager.registerSource(attributionSourceUri, null,
        CALLBACK_EXECUTOR, future::complete);

注册后,API 会向位于 attributionSourceUri 指定地址的服务端点发出 HTTP POST 请求。端点的响应包含 destination, source_event_id, expirysource_priority 的值。

如果发起调用的广告技术平台希望共享来源注册信息,那么原始归因来源 URI 可以包含指向其他广告技术端点的重定向。如需详细了解适用于重定向的限制和规则,请参阅技术方案

注册转化触发器事件

如需注册转化触发器事件,请在您的应用中调用 registerTrigger()

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)

// Use the URI of the server-side endpoint that accepts trigger registration.
val attributionTriggerUri: Uri =
    Uri.parse("https://adtech.example/trigger?AD_TECH_PROVIDED_METADATA")

val future = CompletableFuture<Void>()

// Register trigger (conversion)
measurementManager.registerTrigger(
        attributionTriggerUri,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URI of the server-side endpoint that accepts trigger registration.
Uri attributionTriggerUri =
        Uri.parse("https://adtech.example/trigger?AD_TECH_PROVIDED_METADATA");

CompletableFuture<Void> future = new CompletableFuture<>();

// Register trigger (conversion)
measurementManager.registerTrigger(
        attributionTriggerUri,
        CALLBACK_EXECUTOR,
        future::complete)

注册后,API 会向位于 attributionTriggerUri 指定地址的服务端点发出 HTTP POST 请求。端点的响应包含事件和汇总报告的值。

如果发起调用的广告技术平台允许共享触发器注册信息,那么该 URI 可以包含指向属于其他广告技术平台的 URI 的重定向。如需详细了解适用于重定向的限制和规则,请参阅技术方案

注册跨应用和跨网站衡量

如果在从来源到触发器的用户历程中应用和浏览器二者都发挥了作用,那么注册广告事件的实现就略有不同。如果用户在应用中看到广告并被重定向到浏览器中完成转化,那么来源由应用注册,转化则由网络浏览器注册。同理,如果用户先在网络浏览器中看到广告,然后被定向到应用完成转化,那么就由浏览器注册来源,由应用注册转化。

由于广告技术平台在网站和 Android 中的组织方式有所不同,因此我们添加了新的 API,用于注册浏览器中发生的来源和触发器。这些 API 与基于应用的相应 API 之间的主要区别在于,我们希望浏览器跟踪重定向,应用任何浏览器专用过滤器,并通过调用 registerWebSource()registerWebTrigger() 将有效注册传递给平台。

以下代码段展示了将用户定向到应用之前,浏览器为注册归因来源而发出的 API 调用示例:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager =
        context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null

// Use the URIs of the server-side endpoints that accept attribution source
// registration.
val sourceParam1 = WebSourceParams.Builder(Uri.parse(
        "https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
// True, if debugging is allowed for the adtech.
    .setDebugKeyAllowed(true)
    .build()

val sourceParam2 = WebSourceParams.Builder(Uri.parse(
        "https://adtech2.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build()

val sourceParam3 = WebSourceParams.Builder(Uri.parse(
        "https://adtech3.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .build()

val sourceParams = Arrays.asList(sourceParam1, sourceParam2, sourceParam3)
val publisherOrigin = Uri.parse("https://publisher.example")
val appDestination = Uri.parse("android-app://com.example.store")
val webDestination = Uri.parse("https://example.com")

val future = CompletableFuture<Void>()

adView.setOnTouchListener {_: View?, event: MotionEvent? ->
    exampleClickEvent = event
    true
}
val clickRegistrationRequest = WebSourceRegistrationRequest.Builder(
          sourceParams,
          publisherOrigin)
      .setAppDestination(appDestination)
      .setWebDestination(webDestination)
      .setInputEvent(event)
      .build()
val viewRegistrationRequest = WebSourceRegistrationRequest.Builder(
          sourceParams,
          publisherOrigin)
      .setAppDestination(appDestination)
      .setWebDestination(webDestination)
      .setInputEvent(null)
      .build()

// Register a web source for a click event.
measurementManager.registerWebSource(
        clickRegistrationRequest,
        CALLBACK_EXECUTOR,
        future::complete)

// Register a web source for a view event.
measurementManager.registerWebSource(
        viewRegistrationRequest,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR =
        Executors.newCachedThreadPool();
private InputEvent exampleClickEvent;

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URIs of the server-side endpoints that accept attribution source
// registration.
WebSourceParams sourceParam1 = WebSourceParams.Builder(Uri.parse(
        "https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the adtech.
    .setDebugKeyAllowed(true)
    .build();

WebSourceParams sourceParam2 = WebSourceParams.Builder(Uri.parse(
        "https://adtech2.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build();

WebSourceParams sourceParam3 = WebSourceParams.Builder(Uri.parse(
        "https://adtech3.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .build();

List<WebSourceParams> sourceParams =
        Arrays.asList(sourceParam1, sourceParam2, sourceParam3);
Uri publisherOrigin = Uri.parse("https://publisher.example");
Uri appDestination = Uri.parse("android-app://com.example.store");
Uri webDestination = Uri.parse("https://example.com");

CompletableFuture<Void> future = new CompletableFuture<>();

adView.setOnTouchListener(v, event) -> {
    exampleClickEvent = event;
    return true;
}

WebSourceRegistrationRequest clickRegistrationRequest =
        new WebSourceRegistrationRequest.Builder(sourceParams, publisherOrigin)
    .setAppDestination(appDestination)
    .setWebDestination(webDestination)
    .setInputEvent(event)
    .build();
WebSourceRegistrationRequest viewRegistrationRequest =
        new WebSourceRegistrationRequest.Builder(sourceParams, publisherOrigin)
    .setAppDestination(appDestination)
    .setWebDestination(webDestination)
    .setInputEvent(null)
    .build();

// Register a web source for a click event.
measurementManager.registerWebSource(clickRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

// Register a web source for a view event.
measurementManager.registerWebSource(viewRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

以下代码段展示了用户从应用被定向到浏览器之后,浏览器为注册转化而发出的 API 调用示例:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)

// Use the URIs of the server-side endpoints that accept trigger registration.
val triggerParam1 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech1.example/trigger?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the adtech.
    .setDebugKeyAllowed(true)
    .build()

val triggerParam2 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech2.example/trigger?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build()

val triggerParams = Arrays.asList(triggerParam1, triggerParam2)
val advertiserOrigin = Uri.parse("https://advertiser.example")

val future = CompletableFuture<Void>()

val triggerRegistrationRequest = WebTriggerRegistrationRequest.Builder(
        triggerParams,
        advertiserOrigin)
    .build()

// Register the web trigger (conversion).
measurementManager.registerWebTrigger(
    triggerRegistrationRequest,
    CALLBACK_EXECUTOR,
    future::complete)

Java

private static final Executor CALLBACK_EXECUTOR =
        Executors.newCachedThreadPool();

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URIs of the server-side endpoints that accept trigger registration.
WebTriggerParams triggerParam1 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech1.example/trigger?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the adtech.
    .setDebugKeyAllowed(true)
    .build();

WebTriggerParams triggerParam2 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech2.example/trigger?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build();

List<WebTriggerParams> triggerParams =
        Arrays.asList(triggerParam1, triggerParam2);
Uri advertiserOrigin = Uri.parse("https://advertiser.example");

CompletableFuture<Void> future = new CompletableFuture<>();

WebTriggerRegistrationRequest triggerRegistrationRequest =
        new WebTriggerRegistrationRequest.Builder(
            triggerParams, advertiserOrigin)
    .build();

// Register the web trigger (conversion).
measurementManager.registerWebTrigger( triggerRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

生成并发送报告

Attribution Reporting API 会将报告发送到您的服务器上接受事件级报告可汇总报告的端点。

强制运行报告作业

注册归因来源事件或注册触发器事件后,系统会安排运行报告作业。默认情况下,此作业每 4 小时运行一次。出于测试目的,您可以强制运行报告作业或缩短两次作业之间的间隔时间。

强制运行归因作业:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 5

强制运行事件级报告作业:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 3

强制运行可汇总报告作业:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 7

查看 logcat 中的输出,了解作业的运行时间。输出应如下所示:

JobScheduler: executeRunCommand(): com.google.android.adservices.api/0 5 s=false f=true

强制发送报告

即使强制运行报告作业,系统仍会按照安排的发送时间(从几小时到几天不等)发送报告。出于测试目的,您可以将设备的系统时间向前调到安排的延迟时间之后,以便立即开始发送报告。

在服务器上验证报告

报告发送后,通过查看收到的报告、适用的服务器日志(如模拟服务器历史记录)或您的自定义系统来验证报告发送。

测试

为帮助您开始使用 Attribution Reporting API,您可以使用 GitHub 上的 MeasurementSampleApp 项目。该示例应用演示了归因来源注册和触发器注册。

对于服务器端点,请考虑使用以下参考资源或您的自定义解决方案:

  • MeasurementAdTechServerSpec 包含 OpenAPI 服务定义,您可将这些定义部署到受支持的模拟或微服务平台。
  • MeasurementAdTechServer 包含一个模拟服务器的参考实现,以用于 Google App Engine 的 Spring Boot 应用为基础。

前提条件

在可通过测试设备或模拟器进行访问的远程端点上部署模拟 API。为便于测试,请参阅 MeasurementAdTechServerSpecMeasurementAdTechServer 示例项目。

需要测试的功能

  • 进行归因来源和转化触发器注册。检查服务器端端点是否以正确格式进行响应。
  • 执行报告作业
  • 在测试服务器的后端或控制台上验证报告发送

限制

有关 SDK 运行时的正在开发中的功能列表,请查看版本说明

报告 bug 和问题

您的反馈对 Privacy Sandbox on Android 至关重要!如果发现任何问题或有任何关于改进 Privacy Sandbox on Android 的想法,欢迎告诉我们。