המדריך למפתחים של Attribution Reporting API

בהמשך לקריאה על ארגז החול לפרטיות ב-Android תיעוד, השתמשו בלחצן תצוגה מקדימה למפתחים או בטא כדי לבחור את גרסת התוכנית שאיתה אתם עובדים, כי ההוראות עשויות להשתנות.


Attribution Reporting API נועד לשפר את פרטיות המשתמשים באמצעות להסיר את ההסתמכות על מזהי משתמשים של צדדים שונים ולספק תמיכה בתרחישים חשובים. לשיוך ולמעקב המרות באפליקציות שונות. המדריך הזה למפתחים שמתאר איך להגדיר ולבדוק את Attribution Reporting API כדי לרשום קליקים על מודעות, צפיות והמרות באמצעות שיטות קריאה שרושם את הנתונים הרלוונטיים הטריגרים והמקורות של אירועים כאלה.

במדריך הזה מוסבר איך להגדיר נקודות קצה (endpoints) של שרתים ולבנות אפליקציית לקוח שקוראת לשירותים האלה. מידע נוסף על העיצוב הכולל של Attribution Reporting API בהצעה לתכנון.

מונחי מפתח

  • מקורות השיוך מתייחסים לקליקים או לצפיות.
  • טריגרים הם אירועים שאפשר לשייך להמרות.
  • דוחות מכילים נתונים על טריגר ועל מקור השיוך התואם. הדוחות האלה נשלחים בתגובה להפעלה של אירועים. ב-Attribution Reporting API יש תמיכה בדוחות ברמת האירוע ובדוחות נצברים.

לפני שמתחילים

כדי להשתמש ב-Attribution Reporting API, צריך לבצע את המשימות בצד השרת ובצד הלקוח שמפורטות בקטעים הבאים.

הגדרת נקודות קצה של Attribution Reporting API

כדי להשתמש ב-Attribution Reporting API נדרשת קבוצה של נקודות קצה שאפשר לגשת אליהן ממכשיר בדיקה או מאמולטור. צריך ליצור נקודת קצה אחת לכל אחד מהסוגים הבאים משימות בצד השרת:

יש כמה שיטות להגדרת נקודות הקצה הנדרשות:

  • הדרך המהירה ביותר להתחיל לפעול היא לפרוס את הגדרות השירות OpenAPI v3 מקוד לדוגמה שלנו להדמיה של פלטפורמה או מיקרו-שירותים (microservices). אפשר להשתמש Postman, Prism או כל שרת מדומה אחר שמקבלת את הפורמט הזה. לפרוס כל נקודות קצה ולעקוב אחריהן מזהי ה-URI לשימוש באפליקציה שלכם. כדי לאמת את מסירת הדיווח, יש לעיין בשיחות שפותחו בעבר לפלטפורמה לדוגמה או לפלטפורמה ללא שרת (serverless).
  • להריץ שרת עצמאי משלכם באמצעות הדוגמה ל-Kotlin שמבוססת על Spring Boot. פורסים את השרת הזה אצל ספק הענן או בתשתית הפנימית.
  • אפשר להשתמש בהגדרות השירותים כדוגמאות כדי לשלב את נקודות הקצה במערכת הקיימת.

אישור הרישום של המקור

צריך לתת לנקודת הקצה הזו כתובת מ-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]",
  "event_report_window": "[64-bit signed integer]",
  "aggregatable_report_window": "[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": {
    "[key1 name]": "[key1 value]",
    "[key2 name]": "[key2 value]",
  },

    "debug_key": "[64-bit unsigned integer]",
    "debug_reporting": [boolean]
}
// Specify additional ad tech URLs to register this source with.
Attribution-Reporting-Redirect: <Ad Tech Partner URI 1>
Attribution-Reporting-Redirect: <Ad Tech Partner URI 2>

דוגמה עם ערכים לדוגמה:

Attribution-Reporting-Register-Source: {
  "destination": "android-app://com.example.advertiser",
  "source_event_id": "234",
  "expiry": "259200",
  "event_report_window": "172800",
  "aggregatable_report_window": "172800",
  "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".
  // User saw an ad from campaign 345 (out of 511).
    "campaignCounts": "0x159",

  // Generates a "0x5" key piece (low order bits of the key) for the key named
  // "geoValue".
  // Source-side geo region = 5 (US), out of a possible ~100 regions.
    "geoValue": "0x5",
  },
  // Opts in to receiving verbose debug reports
  "debug_reporting": true
}

Attribution-Reporting-Redirect:
https://adtechpartner1.example?their_ad_click_id=567
Attribution-Reporting-Redirect:
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]",
  "event_report_window": "[64-bit signed integer]",
  "aggregatable_report_window": "[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": {
    "[key1 name]": "[key1 value]",
    "[key2 name]": "[key2 value]",
  }
}
// The Attribution-Reporting-Redirect header is ignored for ad tech 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
    // event trigger data based on source's filter_data. They consist of a
    // filter set, which is a list of filter maps. An event_trigger_data object
    // is ignored if none of the filter maps in the set match the source's
    // filter data.
    // Note: "source_type" can be used as a key in a filter map to filter based
    // on the source's "navigation" or "event" type. The first
    // Event-Trigger that matches (based on the filters/not_filters) will be
    // used for report generation. If none of the event-triggers match, no
    // event report will be generated.
    "filters": [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from filters or source's filter_data, it won't be
      // 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 source's filter_data, it won't
      // be used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }]
  }],
  // Specify a list of dictionaries that generates aggregation keys.
  "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/not_filters are optional fields similar to 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]
    },
    ..
  ],
  aggregatable_deduplication_keys: [{
  deduplication_key": [unsigned 64-bit integer],
    "filters": {
        "category": [filter_1, …, filter_H]
      },
    "not_filters": {
        "category": [filter_1, …, filter_J]
      }
  },
  ...
  {
  "deduplication_key": [unsigned 64-bit integer],
    "filters": {
        "category": [filter_1, …, filter_D]
      },
    "not_filters": {
        "category": [filter_1, …, filter_J]
      }
    }
  ]

  "debug_key": "[64-bit unsigned integer]",
  "debug_reporting": [boolean]

}
// Specify additional ad tech URLs to register this trigger with.
// Repeated Header field "Attribution-Reporting-Redirect"
Attribution-Reporting-Redirect: <Ad Tech Partner URI 1>
Attribution-Reporting-Redirect: <Ad Tech Partner URI 2>

דוגמה עם ערכים לדוגמה שנוספו:

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": [{ // Filter strings can not exceed 25 characters
      "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": [{ // Filter strings can not exceed 25 characters
      "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"]
       // Filter strings can not exceed 25 characters
    },
    {
      // 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", "nonMatchingIdsAreIgnored"]
      // source_key values must not exceed the limit of 25 characters
    }
  ],
  "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
    }
  ,
  // aggregatable_deduplication_keys is an optional field. Up to 50 "keys"
  // can be included in the aggregatable_deduplication_keys list. Filters, not
  // filters, and deduplication_key are optional fields. If deduplication_key
  // is omitted, it will be treated as a null value. See
  // https://wicg.github.io/attribution-reporting-api/#triggering-aggregatable-attribution
  aggregatable_deduplication_keys:
  [
    {
    deduplication_key": 3,
        "filters": {
          "category": [A]
        }
    },
    {
    "deduplication_key": 4,
        "filters": {
          "category": [C, D]
        },
        "not_filters": {
          "category": [F]
        }
    }
  ]
  // Opts into receiving verbose debug reports
  "debug_reporting": true
}
Attribution-Reporting-Redirect:https://adtechpartner.example?app_install=567

המגבלה היא 25 בייטים לכל מזהה מפתח צבירה ומחרוזת מסנן. כלומר, מזהי מפתחות הצבירה ומחרוזות המסננים לא יכולים להכיל יותר מ-25 תווים. בדוגמה הזו, הערך של campaignCounts הוא 14 תווים, כך שהוא חוקי המזהה של מפתח צבירה, ו-1234 הוא 4 תווים, ולכן היא מחרוזת סינון חוקית. אם מזהה מפתח צבירה או מחרוזת מסנן חורגים מ-25 תווים, הטריגר המערכת מתעלמת ממנו.

אם השדה Attribution-Reporting-Redirect מכיל מזהי 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]",
    // filter and not_filters are optional fields which allow configuring
    // different event trigger data based on source's filter_data. They
    // consist of a filter set, which is a list of filter maps. An
    // event_trigger_data object is ignored if none of the filter maps in the
    // set match the source's filter data. Note: "source_type" can be used as
    // a key in a filter map to filter based on the source's "navigation" or
    // "event" type. The first Event-Trigger that matches (based on the
    // filters/not_filters) will be used for report generation. If none of the
    // event-triggers match, no report will be generated.
    "filters": [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from filters or source's filter_data, it will not be
      // 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 source's filter_data, it will not
      // be 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/not_filters are optional fields similar to 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-Redirect header is ignored for ad tech partners.

אישור דוחות ברמת האירוע

יש לאפשר כתובת לנקודת הקצה הזו מ-URI. מידע נוסף זמין במאמר הרשמה לתוכנית פרטיות חשבון Sandbox לקבלת מידע נוסף על רישום URI. (ה-URI משתמע מהמקור של השרתים המשמשים לקבלת רישום המקור ולרישום הטריגר). לפי דוגמאות ה-URI לנקודות קצה שמקבלות רישום של מקורות ומקבלות רישום של טריגרים, ה-URI של נקודת הקצה הזו הוא:

https://adtech.example/.well-known/attribution-reporting/report-event-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. מידע נוסף זמין במאמר הרשמה לתוכנית פרטיות חשבון Sandbox לקבלת מידע נוסף על רישום URI. (ה-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. להגדיר את סביבת הפיתוח לארגז החול לפרטיות ב- Android.
  2. התקנה של תמונת מערכת במכשיר נתמך או להגדיר אמולטור שכולל תמיכה בארגז החול לפרטיות ב- Android.
  3. להפעיל את הגישה ל-Attribution Reporting API על ידי הרצת באמצעות פקודת ADB. (ה-API מושבת כברירת מחדל).

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
  4. אם אתם בודקים באופן מקומי את Attribution Reporting API (למשל: במכשיר שיש לך גישה אליו פיזית), מריצים את הפקודה הזו כדי להשבית הרשמה:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
  5. יש לכלול את ההרשאה ACCESS_ADSERVICES_ATTRIBUTION ב-Android קובץ מניפסט וליצור הגדרה של שירותי מודעות לאפליקציה כדי משתמשים בממשקי Attribution Reporting API:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
    
  6. (אופציונלי) אם אתם מתכננים לקבל דוחות ניפוי באגים, צריך לכלול את ההרשאה ACCESS_ADSERVICES_AD_ID בקובץ Android Manifest:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
    
  7. להפנות להגדרה של שירותי מודעות ברכיב <application> של במניפסט:

    <property android:name="android.adservices.AD_SERVICES_CONFIG"
              android:resource="@xml/ad_services_config" />
    
  8. מציינים את משאב ה-XML של שירותי הפרסום שאליו קיימת הפניה במניפסט, למשל res/xml/ad_services_config.xml. מידע נוסף על הרשאות של שירותי מודעות ובקרת גישה של SDK

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

רישום אירועי מודעות

האפליקציה צריכה לתעד מקורות והמרות בזמן שהם מתרחשים, כדי לוודא שהם מדווחים כראוי. בכיתה MeasurementManager יש שיטות שיעזרו לכם לרשום אירועי מקור שיוך וטריגרי המרה.

רישום אירוע של מקור שיוך

כשמשתמש צופה במודעה או לוחץ עליה, אפליקציה של בעל תוכן דיגיטלי שולחת קריאה ל-registerSource() כדי לרשום מקור שיוך כפי שמוצג בקטע הקוד.

ב-Attribution Reporting API יש תמיכה בסוגי האירועים הבאים של מקורות שיוך:

  • קליקים, שבדרך כלל רושמים בשיטת קריאה חוזרת (callback) שדומה onClick() אירוע הטריגר התואם מתרחש בדרך כלל זמן קצר אחרי אירוע מסוג קליק. אירוע מהסוג הזה מספק מידע נוסף על המשתמש ולכן הוא סוג טוב של מקור שיוך שמספק בעדיפות גבוהה.
  • צפיות, שרשומות בדרך כלל בשיטת קריאה חוזרת (callback) שדומה 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 שולח בקשת POST של HTTP לנקודת הקצה של השירות בכתובת שצוינה ב-attributionSourceUri. התגובה של נקודת הקצה כוללת ערכים של destination, source_event_id, expiry ו-source_priority.

אם חברת טכנולוגיית הפרסום המקורית רוצה לשתף את רישומי המקור, ה-URI המקורי של מקור השיוך יכול לכלול הפניות אוטומטיות לנקודות קצה אחרות של טכנולוגיית פרסום. המגבלות והכללים שחלים על ההפניות האוטומטיות מפורטים בהצעה הטכנית.

הוספנו תמיכה בהפניות אוטומטיות בשרשור עבור registerSource ו-registerTrigger. בנוסף לכותרת הרישום, צרכן ה-API יכול עכשיו לספק הפניה אוטומטית של HTTP בתור תגובת השרת, שכוללת קוד סטטוס 302 וכותרת Location עם כתובת ה-URL הבאה שצריך לבקר בה כדי לבצע רישום נוסף.

רק השדה 'יעד' שסופק בביקורים הראשונים משמש לאורך שרשרת התגים. מספר הביקורים כולל אותה מגבלה כמו הפניה אוטומטית לדיווח על שיוך (Attribution) כותרות עליונות. התמיכה בהפניה אוטומטית היא בנוסף לתמיכה הקיימת ב-Attribution-Reporting-Redirect. אם שתי התכונות האלה קיימות, המערכת תעדיף את Attribution-Reporting-Redirect.

רישום אירוע של טריגר המרה

כדי לרשום אירוע של טריגר המרה, צריך להתקשר למספר 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 שולח בקשת POST של HTTP לנקודת הקצה של השירות בכתובת שצוינה ב-attributionTriggerUri. התשובה של נקודת הקצה כוללת ערכים בדוחות של אירועים ושל דוחות מצטברים.

אם פלטפורמת ה-AdTech המקורית מאפשרת לשתף את הרישום של הטריגרים, ה-URI יכול לכלול הפניות אוטומטיות ל-URIs ששייכים לפלטפורמות אחרות של AdTech. המגבלות והכללים שחלים על ההפניות האוטומטיות מפורטים בהצעה הטכנית.

רישום למדידה בכמה אפליקציות ובאתרים

במקרה שבו גם האפליקציה וגם הדפדפן ממלאים תפקיד בתהליך שבו המשתמש עובר מהמקור לטריגר, יש הבדלים קלים בהטמעה של רישום אירועי מודעות. אם משתמש רואה מודעה באפליקציה ומופנה לדפדפן כדי להשלים המרה, המקור נרשם על ידי האפליקציה וההמרה על ידי דפדפן האינטרנט. באופן דומה, אם משתמש מתחיל בדפדפן אינטרנט ומופנה לאפליקציה להשלמת המרה, הדפדפן רושם את המקור והאפליקציה רושמת את ההמרה.

יש הבדלים באופן שבו טכנולוגיות הפרסום מאורגנות באינטרנט וב-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 ad tech.
    .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 ad tech.
    .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 ad tech.
    .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 ad tech.
    .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) והטריגר. . הן נשלחות בפורמט המקורי (לא מוצפן) למקור הדיווח. כדי להגן על פרטיות המשתמשים, אפשר להוסיף רעש כדי שיהיה קשה יותר לזהות משתמש ספציפי. מופקים דוחות ממוחשבים ברמת האירוע נשלחים בהתאם למסגרת של פרטיות דיפרנציאלית. אלה ערכי ברירת המחדל של אחוז הרעשי הרקע בתרחישים שונים:

סוג המקור

ערך יעד המקור

הסתברות לדיווח עם רעש לכל רישום מקור

הצגה

באפליקציה או באתר

0.0000025

הצגה

אפליקציה ואתר

0.0000042

קליק

אפליקציה או אינטרנט

0.0024263

קליק

אפליקציה ואתר

0.0170218

במדידת שיוך (Attribution) של אפליקציה לאתר, שבה מקורות יכולים להגדיל את מספר ההמרות גם יעד אפליקציה וגם יעד אינטרנט, דוחות ברמת האירוע יכולים לציין אם שהתרחש באפליקציה או באתר. כדי לפצות על הפרטים הנוספים האלה, דוחות עם רעשי רקע שמופקים הם פי 7 לפי קליקים ולפי 1.7 כפול צפיות.

בחלק מהטכנולוגיות של מודעות אין צורך בדוחות ברמת האירוע כדי לציין אם הטריגר התרחש ביעד האפליקציה או ביעד האינטרנט. טכנאי הפרסום יכולים להשתמש בשדה coarse_event_report_destinations שמתחת לכותרת Attribution-Reporting-Register-Source כדי לצמצם את הרעש. אם מקור עם השדה coarse_event_report_destinations שצוין זוכה בשיוך, הדוח שנוצר יכלול יעדים לאפליקציות ויעדים לאתרים, ללא הבחנה לגבי המיקום שבו התרחש הטריגר בפועל.

בדוגמאות הבאות, משתמש לוחץ על מודעה והמקור הזה רשום ב-API. לאחר מכן המשתמש משלים המרה גם באפליקציה של המפרסם וגם באתר של המפרסם. שתי ההמרות האלה נרשמות כטריגרים ומשויכות לקליק הראשוני.

כותרת HTTP של רישום מקור המבוסס על קליקים:

Attribution-Reporting-Register-Source: {
    "destination": "android-app://com.advertiser.example",
    "web_destination": "https://advertiser.com",
    "source_event_id": "234",
    "expiry": "60000",
    "priority": "5",
    // Ad tech opts out of receiving app-web destination distinction
    // in event report, avoids additional noise
    "coarse_event_report_destinations": "true"
}

מופעל טריגר מהאפליקציה עם שם החבילה com.advertiser.example:

Attribution-Reporting-Register-Trigger: {
    "event_trigger_data": [{
    "trigger_data": "1",
    "priority": "1"
    }],
}

הטריגר נרשם מדפדפן מהאתר עם הדומיין eTLD+1‏https://advertiser.com:

Attribution-Reporting-Register-Trigger: {
    "event_trigger_data": [{
    "trigger_data": "2",
    "priority": "2"
    }],
}

המערכת יוצרת את הדוחות ברמת האירוע. בהנחה ששני הטריגרים משויכים למקור, נוצרים הדוחות הבאים ברמת האירוע:

  {
    "attribution_destination": ["android-app://com.advertiser.example,https://advertiser.com"],
    "scheduled_report_time": "800176400",
    "source_event_id": "53234",
    "trigger_data": "1",
    // Can be "event" if source were registered by user viewing the ad
    "source_type": "navigation",
    // Would be 0.0170218 without coarse_event_report_destinations as true in the source
    "randomized_trigger_rate": 0.0024263
  }

יצירה ושליחה של דוחות

Attribution Reporting API שולח דוחות לנקודות הקצה (endpoints) בשרת שלכם שמקבלות דוחות ברמת האירוע ודוחות נצברים.

אילוץ הרצה של משימות דיווח

אחרי שמירת אירוע של מקור שיוך או אירוע טריגר, המערכת מתזמנת את הפעלת המשימה של הדיווח. כברירת מחדל, המשימה הזו פועלת כל 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

אילוץ שליחה של דוחות

גם אם משימת הדיווח אולצת לפעול, המערכת עדיין שולחת דוחות בהתאם לזמני האספקה המתוכננים שלהם, שנעים בין כמה שעות עד כמה ימים. למטרות בדיקה, אפשר לקדם את מערכת המכשיר יהיה אחרי העיכובים המתוזמנים לתחילת שליחת הדוח.

אימות הדוחות בשרת

אחרי שליחת הדוחות, מוודאים שהם נשלחו על ידי בדיקת הדוחות שהתקבלו, יומני השרת הרלוונטיים, כמו היסטוריית השרת המדומה או המערכת בהתאמה אישית.

פענוח של הדוח המצטבר

כשמקבלים דוח מצטבר, השדה debug_cleartext_payload מכיל גרסה לא מוצפנת של הדוח המצטבר. הגרסה הזו של הדוח לא מוצפנת, אבל עדיין צריך לפענח אותה.

בהמשך מופיעה דוגמה לפענוח התוכן של השדה debug_cleartext_payload בשני שלבים: הראשון באמצעות פענוח Base 64, והשני באמצעות פענוח CBOR.

String base64DebugPayload  = "omRkYXRhgqJldmFsdWVEAAAGgGZidWNrZXRQAAAAAAAAAAAAAAAAAAAKhaJldmFsdWVEAACAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAFWWlvcGVyYXRpb25paGlzdG9ncmFt";
byte[] cborEncoded = Base64.getDecoder().decode(base64DebugPayload);

// CbodDecoder comes from this library https://github.com/c-rack/cbor-java
final List<DataItem> dataItems = new CborDecoder(new ByteArrayInputStream(cborEncoded)).decode();

// In here you can see the contents, but the value will be something like:
// Data items: [{ data: [{ value: co.nstant.in.cbor.model.ByteString@a8b5c07a,
//   bucket: co.nstant.in.cbor.model.ByteString@f812097d },
//   { value: co.nstant.in.cbor.model.ByteString@a8b5dfc0,
//   bucket: co.nstant.in.cbor.model.ByteString@f8120934 }], operation: histogram }]
Log.d("Data items : " + dataItems);

// In order to see the value for bucket and value, you can traverse the data
// and get their values, something like this:
final Map payload = (Map) dataItems.get(0);
final Array payloadArray = (Array) payload.get(new UnicodeString("data"));

payloadArray.getDataItems().forEach(i -> {
    BigInteger value = new BigInteger(((ByteString) ((Map)i).get(new UnicodeString("value"))).getBytes());
    BigInteger bucket = new BigInteger(((ByteString) ((Map)i).get(new UnicodeString("bucket"))).getBytes());
    Log.d("value : " + value + " ;bucket : " + bucket);
});

בדיקה

כדי להתחיל לעבוד עם Attribution Reporting API, אפשר להשתמש פרויקט MeasurementSampleApp ב-GitHub. באפליקציית הדוגמה הזו מוצגים רישום של מקור שיוך ורישום של טריגר.

לגבי נקודות קצה של שרתים, כדאי לעיין במקורות המידע הבאים או למצוא פתרון מותאם אישית:

  • MeasurementAdTechServerSpec כולל הגדרות שירות של OpenAPI, שאפשר לפרוס בפלטפורמות נתמכות של מודלים מדומים או של מיקרו-שירותים.
  • MeasurementAdTechServer כולל הטמעת עזר של שרת מדומה שמבוסס על אפליקציית Spring Boot ל-Google App Engine.

דרישות מוקדמות

פריסת ממשקי API מדומים בנקודות קצה מרוחקות שאפשר לגשת אליהן ממכשיר הבדיקה או מהמכונה הווירטואלית. כדי שיהיה קל לבצע את הבדיקה, אפשר לעיין ב-MeasurementAdTechServerSpec ופרויקטים לדוגמה של MeasurementAdTechServer.

פונקציונליות לבדיקה

  • איך משתמשים באירועי רישום של מקורות שיוך וטריגרים של המרות. צריך לבדוק נקודות הקצה בצד השרת מגיבות בפורמט הנכון.
  • להריץ משימות דיווח.
  • מאמתים את מסירת הדוחות בקצה העורפי או במסוף של שרת הבדיקה.

תכונות שיושקו בקרוב

הגדרות גמישות ברמת האירוע

כדי להתחיל, מומלץ להגדיר כברירת מחדל את הדיווח ברמת האירוע בדיקת יעילות, אבל ייתכן שהיא לא תהיה אידיאלית לכל התרחישים לדוגמה. Attribution Reporting API יתמוך בהגדרות אופציונליות גמישות יותר, כדי לטכנולוגיות הפרסום תהיה שליטה רבה יותר על המבנה של הדוחות ברמת האירוע, וכדי שיוכלו למקסם את התועלת מהנתונים. הגמישות הנוספת הזו תושק ב-Attribution Reporting API בשני שלבים:

  • שלב 1: הגדרה גמישה וקלה ברמת האירוע, קבוצת משנה של שלב 2.
  • שלב 2: גרסה מלאה של הגדרה גמישה ברמת האירוע.

שלב 1: רמת אירוע גמיש קלה

נוסיף את שני הפרמטרים האופציונליים הבאים ל-JSON בקובץ Attribution-Reporting-Register-Source:

  • max_event_level_reports
  • event_report_windows
{
  ...
  // Optional. This is a parameter that acts across all trigger types for the
  // lifetime of this source. It restricts the total number of event-level
  // reports that this source can generate. After this maximum is hit, the
  // source is no longer capable of producing any new data. The use of
  // priority in the trigger attribution algorithm in the case of multiple
  // attributable triggers remains unchanged. Defaults to 3 for navigation
  // sources and 1 for event sources
  "max_event_level_reports": <int>,

  // Optional. Represents a series of time windows, starting at 0. Reports
  // for this source will be delivered an hour after the end of each window.
  // Time is encoded as seconds after source registration. If
  // event_report_windows is omitted, will use the default windows. This
  // field is mutually exclusive with the existing `event_report_window` field.
  // // End time is exclusive.
  "event_report_windows": {
    "start_time": <int>,
    "end_times": [<int>, ...]
  }
}

דוגמה להגדרות מותאמות אישית

ההגדרה לדוגמה הזו תומכת במפתח שרוצה לבצע אופטימיזציה קבלת דוחות בחלונות דיווח מוקדמים יותר.

{
  ...
  "max_event_level_reports": 2,
  "event_report_windows": {
    "end_times": [7200, 43200, 86400] // 2 hours, 12 hours, 1 day in seconds
  }
}

שלב 2: רמת אירוע גמישה מלאה

בנוסף לפרמטרים שנוספו בשלב 1, נוסיף פרמטר אופציונלי נוסף trigger_specs לקובץ ה-JSON בקטע Attribution-Reporting-Register-Source.

{
  // A trigger spec is a set of matching criteria, along with a scheme to
  // generate bucketized output based on accumulated values across multiple
  // triggers within the specified event_report_window. There will be a limit on
  // the number of specs possible to define for a source.
  "trigger_specs": [{
    // This spec will only apply to registrations that set one of the given
    // trigger data values (non-negative integers) in the list.
    // trigger_data will still appear in the event-level report.
    "trigger_data": [<int>, ...]

    // Represents a series of time windows, starting at the source registration
    // time. Reports for this spec will be delivered an hour after the end of
    // each window. Time is encoded as seconds after source registration.
    // end_times must consist of strictly increasing positive integers.
    //
    // Note: specs with identical trigger_data cannot have overlapping windows;
    // this ensures that triggers match at most one spec. If
    // event_report_windows is omitted, will use the "event_report_window" or
    // "event_report_windows" field specified at the global level for the source
    // (or the default windows if none are specified). End time is exclusive.
    "event_report_windows": {
      "start_time": <int>,
      "end_times": [<int>, ...],
    }

    // Represents an operator that summarizes the triggers within a window
    // count: number of triggers attributed within a window
    // value_sum: sum of the value of triggers within a window
    // The summary is reported as an index into a bucketization scheme. Defaults
    // to "count"
    "summary_window_operator": <one of "count" or "value_sum">,

    // Represents a bucketization of the integers from [0, MAX_INT], encoded as
    // a list of integers where new buckets begin (excluding 0 which is
    // implicitly included).
    // It must consist of strictly increasing positive integers.
    //
    // e.g. [5, 10, 100] encodes the following ranges:
    // [[0, 4], [5, 9], [10, 99], [100, MAX_INT]]
    //
    // At the end of each reporting window, triggers will be summarized into an
    // integer which slots into one of these ranges. Reports will be sent for
    // every new range boundary that is crossed. Reports will never be sent for
    // the range that includes 0, as every source is initialized in this range.
    //
    // If omitted, then represents a trivial mapping
    // [1, 2, ... , MAX_INT]
    // With MAX_INT being the maximum int value defined by the browser.
    "summary_buckets": [<bucket start>, ...]
  }, {
    // Next trigger_spec
  } ...],

  // See description in phase 1.
  "max_event_level_reports": <int>
  // See description in phase 1.
  "event_report_windows": {
    "start_time": <int>,
    "end_times": [<int>, ...]
  }
}

ההגדרה הזו מציינת באופן מלא את נפח הפלט של הדוחות ברמת האירוע, לכל רישום מקור. עבור כל מפרט טריגר אנחנו מציינים באופן מלא:

  • קבוצה של קריטריונים להתאמה:
    • הנתונים הספציפיים של הטריגר שהמפרט הזה חל עליהם. המקור הזה יכול להתאים רק לטריגרים שיש להם אחד מהערכים של trigger_data שצוינו ב-trigger_specs. במילים אחרות, אם הטריגר היה תואם למקור הזה אבל הערך שלו בשדה trigger_data לא היה אחד מהערכים בהגדרות של המקור, הטריגר יתעלם.
    • כשטריגר ספציפי תואם למפרט הזה (באמצעות event_report_windows). שימו לב שעדיין יכול להיות שהטריגר יתאים למקור של דוחות שניתן לצבור, למרות שהוא לא עומד בשני קריטריונים ההתאמה שצוינו למעלה.
  • אלגוריתם ספציפי לסיכום ולארגון של כל הטריגרים בתוך חלון שיוך (Attribution). כך אפשר לציין בטריגרים פרמטר value שמצטבר לפי מפרט ספציפי, אבל מדווח כערך בקטגוריה.

הטריגרים יתמכו גם בהוספת פרמטר ערך אופציונלי מילונים בתוך event_trigger_data.

{
  "event_trigger_data": [
    {
      "trigger_data": "2",
      "value": 100,  // Defaults to 1
      "filters": ...
    },
    ...
  ]
}

כל רישום של טריגר יתאים לכל היותר למפרט טריגר אחד, ויעדכן את ערך הסיכום המשויך. ברמת העל, בזמן ההפעלה נעשה את הפעולות הבאות:

  • להחיל מסנני שיוך גלובליים.
  • עבור כל מפרט טריגר, עליך להעריך את event_trigger_data במפרט כדי למצוא באמצעות event_reporting_window של המפרט. השדה ברמה העליונה event_reporting_windows משמש כערך ברירת מחדל במקרה שספציפיקת טריגר כלשהי היא שדה המשנה event_report_windows החסר.
  • המערכת בוחרת את המפרט הראשון התואם לצורך שיוך, וערך הסיכום עולה ב-value.

כשה-event_report_window של המפרט יושלם, נמפה את ערך הסיכום שלו לקטגוריה ונשלח דוח ברמת האירוע לכל עלייה בקטגוריית הסיכום שנגרמה על ידי ערכי טריגר שמשויכים. לדוחות יהיו מכלי אחד שדה נוסף, trigger_summary_bucket.

{
  ...
  "trigger_summary_bucket": [<bucket start>, <bucket end>],
}

הגדרות שדומות לגרסה הנוכחית

בהמשך מפורטות הגדרות מקבילות למקורות הנוכחיים של האירועים והניווט ב-API, בהתאמה. במיוחד במקורות ניווט, זה ממחיש למה רמות הרעש גבוהות כל כך ביחס למקורות אירועים, כדי לשמור על אותם ערכי אפסילון: למקורות ניווט יש מרחב פלט גדול בהרבה.

יכול להיות שיש כמה מערכים של הגדרות אישיות מקבילים, שחלק מהפרמטרים יכולים להיות מוגדרים כברירת מחדל או לקזז.

מקורות אירועים מקבילים
// Note: most of the fields here are not required to be explicitly listed.
// Here we list them explicitly just for clarity.
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1],
    "event_report_windows": {
      "end_times": [<30 days>]
    },
    "summary_window_operator": "count",
    "summary_buckets": [1],
  }],
  "max_event_level_reports": 1,
  ...
  // expiry must be greater than or equal to the last element of the end_times
  "expiry": <30 days>,
}
מקורות ניווט מקבילים
// Note: most of the fields here are not required to be explicitly listed.
// Here we list them explicitly just for clarity.
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1, 2, 3, 4, 5, 6, 7],
    "event_report_windows": {
      "end_times": [<2 days>, <7 days>, <30 days>]
    },
    "summary_window_operator": "count",
    "summary_buckets": [1, 2, 3],
  }],
  "max_event_level_reports": 3,
  ...
  // expiry must be greater than or equal to the last element of the end_times
  "expiry": <30 days>,
}

דוגמאות להגדרות מותאמות אישית

בהמשך מפורטות כמה הגדרות נוספות מעבר להגדרות ברירת המחדל. בכל אלה ההשפעות של המפתחים כוללות, בין היתר:

  • צמצום מאפיין מסוים בהגדרת ברירת המחדל (מספר הטריגרים, עוצמת הנתונים של הטריגר, מספר החלונות) כדי להגדיל מאפיין אחר כדי לשמור על רמת הרעש
  • צמצום חלק מהממדים של הגדרת ברירת המחדל (#טריגרים, הפעלת נתונים) עוצמה (cardinality), #windows) לרמת רעש מופחתת

דיווח על קטגוריות של ערכי טריגר

ההגדרה לדוגמה הזו תומכת במפתח שרוצה לבצע אופטימיזציה להגדלת ערך ההמרות נתונים עבור חלון דיווח אחד בלבד (למשל, 7 ימים), מסחר בהיקף קטן יותר של דוחות כדי להפחית את הרעש. בדוגמה הזו כל טריגר שמגדיר את trigger_data ערך שאינו 0 לא מתאים לשיוך.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      "end_times": [604800, 1209600] // 7 days, 14 days represented in seconds
    },
    "summary_window_operator": "value_sum",
    "summary_buckets": [5, 10, 100]
  }],
}

אפשר לרשום טריגרים באמצעות קבוצת השדות value, לחישוב סיכום ולהעלאה מסווג. לדוגמה, אם יש שלושה טריגרים במהלך 7 ימים ממועד המקור רישומים עם הערכים 1, 3 ו-4.

{ "event_trigger_data": [{"trigger_data": "0", "value": 1}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 3}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 4}] }

הערכים מסתכמים ל-8 ומדווחים בדוחות הבאים אחרי 7 ימים + שעה אחת:

// Report 1
{
  ...
  "trigger_summary_bucket": [5, 9]
}

ב-7 הימים הבאים נרשמים הטריגרים הבאים:

{ "event_trigger_data": [{"trigger_data": "0", "value": 50}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 45}] }

סיכום הערכים הוא 8 + 50 + 45 = 103. כך מתקבלים הדוחות הבאים אחרי 14 יום ועוד שעה:

// Report 2
{
  ...
  "trigger_summary_bucket": [10, 99]
},

// Report 3
{
  ...
  "trigger_summary_bucket": [100, MAX_INT]
}
ספירת טריגרים בדוח

הדוגמה הזו מראה איך מפתח יכול להגדיר מקור כדי לקבל ספירה של מפעילים עד 10.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      "end_times": [604800] // 7 days represented in seconds
    },
    // This field could be omitted to save bandwidth since the default is "count"
    "summary_window_operator": "count",
    "summary_buckets": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  }],
}

טריגרים משויכים שבהם הערך של trigger_data מוגדר ל-0 נספרים ומוגבלים ל-10. המערכת מתעלמת מערך הטריגר כי summary_window_operator מוגדר לספירה. אם 4 טריגרים רשומים ומשויכים למקור, הדוח ייראה כך:

// Report 1
{
  ...
  "trigger_summary_bucket": [1, 1]
}
// Report 2
{
  ...
  "trigger_summary_bucket": [2, 2]
}
// Report 3
{
  ...
  "trigger_summary_bucket": [3, 3]
}
// Report 4
{
  ...
  "trigger_summary_bucket": [4, 4]
}
בינארי עם דיווח תדיר יותר

התצורה לדוגמה הזו תומכת במפתח שרוצה לדעת אם בכתובת התרחשה לפחות המרה אחת ב-10 הימים הראשונים (ללא קשר לערך), אבל רוצה לקבל דוחות בתדירות גבוהה יותר מברירת המחדל. שוב, בדוגמה הזו, כל טריגר שמגדיר את trigger_data לערך שאינו 0 לא עומד בדרישות לצורך שיוך. לכן התרחיש הזה נקרא בינארי.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      // 1 day, 2 days, 3 days, 5 days, 7 days, 10 days represented in seconds
      "end_times": [86400, 172800, 259200, 432000, 604800, 864000]
    },
    // This field could be omitted to save bandwidth since the default is "count"
    "summary_window_operator": "count",
    "summary_buckets": [1]
  }],
}
שינוי המפרטים של הטריגרים ממקור למקור
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1, 2, 3],
    "event_report_windows": {
      "end_times": [172800, 604800, 2592000] // 2 days, 7 days, 30 days represented in seconds
    }
  }],
  "max_event_level_reports": 3
}
{
  "trigger_specs": [
  {
    "trigger_data": [4, 5, 6, 7],
    "event_report_windows": {
      "end_times": [172800, 604800, 2592000] // 2 days, 7 days, 30 days represented in seconds
    }
  }],
  "max_event_level_reports": 3
}

אנחנו ממליצים למפתחים להציע תרחישים שונים לדוגמה שבהם הם יכולים לעשות את זה של תוסף ה-API, ונעדכן את ההסבר הזה בהסברים על הגדרות לדוגמה במקרים כאלה.

שיוך (Attribution) חוצה-פלטפורמות ללא הפניות לכתובת אחרת

מומחי טכנולוגיית הפרסום צריכים להשתמש בהפניות אוטומטיות כדי לרשום כמה טריגרים של מקורות שיוך ולבצע שיוך חוצה-רשתות. התכונה הזו עוזרת לתמוך בשיוך ברשתות שונות במקרים שבהם לא ניתן להפנות משתמשים אוטומטית בין הרשתות. מידע נוסף

טכנולוגיות הפרסום יכולות לשלוח הגדרות בתגובה לרישום הטריגר על סמך אילו מקורות רשומים על ידי טכנולוגיות פרסום אחרות נבחרים כדי ליצור מקורות; מקורות נגזרים אלה משמשים לשיוך. דוחות מצטברים נוצרות אם הטריגר משויך למקור נגזר. דוח אירועים אין תמיכה ביצירת מקורות נגזרים.

טכנולוגיות הפרסום יכולות לבחור מתוך aggregation_keys במקורות הרשומים שלהן ש שהם מתכוונים לשתף עם טכנולוגיות פרסום של שותפים. אפשר להצהיר על המפתחות האלה השדה shared_aggregation_keys האופציונלי, שנמצא ברישום המקור כותרת Attribution-Reporting-Register-Source:

"shared_aggregation_keys": ["[key name1]", "[key name2]"]

מקורות נגזרים נוצרים על סמך ההגדרה שמופיעה בכותרת ההרשמה של הטריגר Attribution-Reporting-Register-Trigger:

  // Specifies the configuration based on which derived sources should be
  // generated. Those derived sources will be included for source matching at the
  // time of attribution. For example, if adtech2 is registering a trigger with an
  // attribution_config with source_network as adtech1, available sources
  // registered by adtech1 will be considered with additional filtering criteria
  // applied to that set as mentioned in the attribution_config. Derived
  // sources can have different values to priority, post_install_exclusivity_window
  // etc.

  "attribution_config": [
    {
      // Derived sources are created from this adtech's registered sources
      "source_network": "[original source's adtech enrollment ID]",
      //(optional) Filter sources whose priority falls in this range
      "source_priority_range": {
        "start": [priority filter lower bound],
        "end": [priority filter upper bound]
      },
      // (optional) Filter sources whose at least one of filter maps matches these
      // filters
      "source_filters": {
        "key name 1": ["key1 value 1"]
      },
      // (optional) Filter sources whose none of filter map matches these
      // filters
        "source_not_filters": {
          "key name 1": ["key1 value 1"]
        },
      // (optional) Apply this priority to the generated derived sources
      "priority": "[64 bit signed integer]",
      // (optional) The derived source will have expiry set as this or parent
      // source's, whichever is earlier
      "expiry": "[64 bit signed integer]",
      // (optional) set on the derived source
      "filter_data": {
        "key name 1": ["key1 value 1"]
      },
      // (optional) set on the derived source
      "post_install_exclusivity_window": "[64-bit unsigned integer]"
    }
  ]

זוהי גרסה עם ערכים לדוגמה שנוספו:

  "attribution_config": [
    {
      "source_network": "adtech1-enrollment-id",
      "source_priority_range": {
        "start": 50,
        "end": 100
      },
      "source_filters": {
        "source_type": ["NAVIGATION"]
      },
      "source_not_filters": {
        "product_id": ["789"]
      },
      "priority": "30",
      "expiry": "78901",
      // (optional) set on the derived source
      "filter_data": {
        "product_id": ["1234"]
        },
      // (optional) set on the derived source
      "post_install_exclusivity_window": "7890"
    }
  ]

נוספו שני שדות אופציונליים חדשים לכותרת של הטריגר לרישום. השדות האלה מאפשרים להציג את המזהה של פלטפורמת הפרסום הזוכה במפתחות דוחות שאפשר לצבור:

  • x_network_bit_mapping: מזהה הרישום של מזהה טכנולוגיית הפרסום במיפוי סיביות
  • x_network_data: שינוי אופקי (הזזה ימינה) של הפעולה x_network_bit_mapping OR של טכנולוגיית הפרסום המנצחת עם החלק של מקש ההפעלה
דוגמה:
"Attribution-Reporting-Register-Trigger": {
  "attribution_config": [...],
  "aggregatable_trigger_data": [
    {
     "key_piece": "0x400",
     "source_keys": ["campaignCounts"]
      "x_network_data" : {
        "key_offset" : 12 // [64 bit unsigned integer]
      }
    }
    
  ]
  
  "x_network_bit_mapping": {
   // This mapping is used to generate trigger key pieces with AdTech identifier
   // bits. eg. If AdTechA's sources wins the attribution then 0x1 here will be
   // OR'd with the trigger key pieces to generate the final key piece.
    "AdTechA-enrollment_id": "0x1", // Identifier bits in hex for A
    "AdTechB-enrollment_id": "0x2"  // Identifier bits in hex for B
  }
  
}

זהו החישוב של חלק המפתח 'טריגר' בזמן הפקת דוח לגבי המקור של AdTechB:

  • key_piece: 0x400 (010000000000)
  • key_offset: 12
  • הערך של enrollment_id ב-AdtechB: 2 (010) (מ-x_network_bit_mapping)
  • חלק מפתח הטריגר שהתקבל: 0x400 | 0x2 << 12 = 0x2400

מגבלות

בנתוני הגרסה תוכלו למצוא רשימה של יכולות שאנחנו עובדים עליהן כרגע עבור זמן הריצה ל-SDK.

דיווח על באגים ובעיות

המשוב שלכם הוא חלק חיוני בארגז החול לפרטיות ב-Android. נשמח לשמוע ממך בכל בעיות שמצאתם או רעיונות לשיפור ארגז החול לפרטיות ב-Android.