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

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


שליחת משוב

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

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

מונחי מפתח

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

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

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

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

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

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

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

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

צריך לאפשר כתובת לנקודת הקצה הזו מ-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]",
}

מפתחות לניפוי באגים מאפשרים לקבל תובנות נוספות לגבי דוחות השיוך (Attribution). מידע נוסף על ההגדרה שלהם.

אישור דוחות נצברים

יש לאפשר כתובת לנקודת הקצה הזו מ-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]"
}

מפתחות לניפוי באגים מאפשרים לקבל תובנות נוספות לגבי דוחות השיוך (Attribution). מידע נוסף על ההגדרה שלהם.

הגדרת לקוח 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:

    <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 שיעזור לכם לרשום אירועים של מקור השיוך (Attribution) טריגרים של המרות.

רישום אירוע של מקור שיוך (Attribution)

כשמשתמש צופה במודעה או לוחץ עליה, אפליקציה של בעל תוכן דיגיטלי שולחת קריאה ל-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 מנפיק בקשת HTTP POST לנקודת הקצה של השירות בכתובת שצוינה על ידי attributionSourceUri. המאפיינים של נקודת הקצה תגובה כוללת ערכים עבור destination, source_event_id, expiry, וגם source_priority

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

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

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

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

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

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

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

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

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

אילוץ הרצה של משימת שיוך (Attribution):

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 שב-2 שלבים: הראשון משתמש בפענוח קוד בסיס 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, שאפשר לפרוס בפלטפורמות נתמכות המדמות או מיקרו-שירותים (microservices).
  • הפרמטר MeasurementAdTechServer כולל קובץ עזר של הטמעה של דוגמה שרת שמבוסס על אפליקציית Springboot עבור Google App Engine.

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

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