במהלך הקריאה של המסמכים בנושא ארגז החול לפרטיות ב-Android, כדאי להשתמש בלחצן Developer Preview או Beta כדי לבחור את גרסת התוכנה שאתם עובדים איתה, כי ההוראות עשויות להיות שונות.
המטרה של Attribution Reporting API היא לבטל את ההסתמכות על מזהי משתמשים של צדדים שונים כדי לשפר את פרטיות המשתמשים, ולספק תמיכה בתרחישי שימוש עיקריים בשיוך (Attribution) ובמעקב המרות באפליקציות. במדריך למפתחים הזה מוסבר איך להגדיר ולבדוק את ממשקי ה-API של דוחות השיוך כדי לרשום קליקים על מודעות, צפיות והמרות על ידי קריאה לשיטות שרושמות את הטריגרים והמקורות הרלוונטיים לאירועים כאלה.
במדריך הזה נסביר איך להגדיר נקודות קצה של שרתים וליצור אפליקציית לקוח שקוראת לשירותים האלה. במסמך הצעת העיצוב מפורט מידע נוסף על העיצוב הכולל של Attribution Reporting API.
מונחי מפתח
- מקורות השיוך מתייחסים לקליקים או לצפיות.
- טריגרים הם אירועים שאפשר לשייך להמרות.
- דוחות מכילים נתונים על טריגר ומקור השיוך התואם. הדוחות האלה נשלחים בתגובה לאירועי טריגר. Attribution Reporting API תומך בדוחות ברמת האירוע ובדוחות שניתן לצבור.
לפני שמתחילים
כדי להשתמש ב-Attribution Reporting API, צריך לבצע את המשימות בצד השרת ובצד הלקוח שמפורטות בקטעים הבאים.
הגדרת נקודות קצה של Attribution Reporting API
Attribution Reporting API דורש קבוצה של נקודות קצה שאפשר לגשת אליהן ממכשיר בדיקה או מאמולטור. יוצרים נקודת קצה אחת לכל אחת מהמשימות הבאות בצד השרת:
- רישום מקור שיוך (צפייה או קליק)
- רישום של טריגר (המרה)
- אישור דוחות ברמת האירוע
- אישור דוחות שניתן לצבור
יש כמה שיטות להגדרת נקודות הקצה הנדרשות:
- הדרך הכי מהירה להתחיל היא לפרוס את הגדרות השירות של OpenAPI v3 ממאגר קוד לדוגמה שלנו בפלטפורמה של מיקרו-שירותים או בפלטפורמה מדומה. אפשר להשתמש ב-Postman, ב-Prism או בכל פלטפורמה אחרת של שרת מדומה שתומכת בפורמט הזה. מפעילים כל נקודת קצה ועוקבים אחרי מזהי ה-URI לשימוש באפליקציה. כדי לאמת את מסירת הדוחות, אפשר לעיין בקריאות שבוצעו קודם לפלטפורמה המדומה או לפלטפורמה ללא שרת.
- להריץ שרת עצמאי משלכם באמצעות דוגמת 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't 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't 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't 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 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"],
}]
}],
"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. מידע נוסף על רישום של 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. מידע נוסף על רישום של 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 doesn't yet encrypt aggregate reports.
// 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, מבצעים את הפעולות הבאות:
- מגדירים את סביבת הפיתוח לארגז החול לפרטיות ב-Android.
- מתקינים תמונת מערכת במכשיר נתמך או מגדירים אמולטור שכולל תמיכה בארגז החול לפרטיות ב-Android.
מפעילים גישה ל-Attribution Reporting API על ידי הפעלת פקודת ה-ADB הבאה. (ממשק ה-API מושבת כברירת מחדל).
adb shell device_config put adservices ppapi_app_allow_list \"\*\"אם אתם בודקים באופן מקומי את Attribution Reporting API (למשל, בודקים במכשיר שיש לכם גישה פיזית אליו), מריצים את הפקודה הזו כדי להשבית את ההרשמה:
adb shell device_config put adservices disable_measurement_enrollment_check "true"כדי להשתמש בממשקי Attribution Reporting API, צריך לכלול את ההרשאה
ACCESS_ADSERVICES_ATTRIBUTIONבקובץ Android Manifest וליצור הגדרה של שירותי פרסום לאפליקציה:<uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />(אופציונלי) אם אתם מתכננים לקבל דוחות ניפוי באגים, צריך לכלול את ההרשאה
ACCESS_ADSERVICES_AD_IDבקובץ Android Manifest:<uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />מפנים להגדרת שירותי מודעות ברכיב
<application>של קובץ המניפסט:<property android:name="android.adservices.AD_SERVICES_CONFIG" android:resource="@xml/ad_services_config" />מציינים את משאב ה-XML של שירותי הפרסום שאליו יש הפניה במניפסט, כמו
res/xml/ad_services_config.xml. מידע נוסף על הרשאות של שירותי פרסום ובקרת גישה ל-SDK<ad-services-config> <attribution allowAllToAccess="true" /> </ad-services-config>
רישום אירועים שקשורים למודעות
האפליקציה צריכה לרשום מקורות והמרות כשהם מתרחשים כדי לוודא שהם מדווחים בצורה תקינה. במחלקת MeasurementManager יש שיטות שיעזרו לכם לרשום אירועים של מקורות שיוך (Attribution) וטריגרים של המרות.
רישום של אירוע מקור שיוך
כשמשתמש צופה במודעה או לוחץ עליה, אפליקציית בעל התוכן הדיגיטלי קוראת ל-registerSource() כדי לרשום מקור שיוך, כפי שמוצג בקטע הקוד.
Attribution Reporting API תומך בסוגים הבאים של אירועים של מקורות שיוך:
- קליקים, שבדרך כלל נרשמים בשיטת קריאה חוזרת שדומה ל-
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 המקורי של מקור השיוך יכול לכלול הפניות אוטומטיות לנקודות קצה אחרות של טכנולוגיות פרסומיות. בהצעה הטכנית מפורטות המגבלות והכללים שחלים על ההפניות האוטומטיות.
הוספנו תמיכה בהפניות בשרשרת ל-registerSource ול-registerTrigger. בנוסף לכותרת הרישום, צרכן ה-API יכול עכשיו לספק הפניה אוטומטית של HTTP כתגובת השרת, שכוללת קוד סטטוס 302 וכותרת Location עם כתובת ה-URL הבאה שאליה צריך לעבור לרישום נוסף.
רק השדה 'יעד' שמופיע בביקור הראשון משמש בכל שרשרת ההפניות. מספר הביקורים מוגבל כמו מספר הכותרות של 'הפניה לדוח שיוך'. התמיכה בהפניות האלה היא בנוסף לתמיכה הקיימת ב-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 שולח בקשת HTTP POST לנקודת הקצה של השירות בכתובת שצוינה על ידי attributionTriggerUri. התגובה של נקודת הקצה כוללת ערכים של אירועים ודוחות מצטברים.
אם פלטפורמת הטכנולוגיה הפרסומית המקורית מאפשרת שיתוף של רישום טריגרים, ה-URI יכול לכלול הפניות אוטומטיות לכתובות URI ששייכות לפלטפורמות אחרות של טכנולוגיה פרסומית. המגבלות והכללים שחלים על ההפניות האוטומטיות מפורטים בהצעה הטכנית.
רישום של מדידה באפליקציות ובאתרים שונים
אם גם אפליקציה וגם דפדפן ממלאים תפקיד בתהליך שעובר המשתמש מהמקור להפעלת האירוע, יש הבדלים קלים בהטמעה של רישום אירועים שקשורים למודעות. אם משתמש רואה מודעה באפליקציה ומופנה לדפדפן כדי להשלים המרה, המקור נרשם על ידי האפליקציה וההמרה נרשמת על ידי דפדפן האינטרנט. באופן דומה, אם משתמש מתחיל בדפדפן אינטרנט ומופנה לאפליקציה להשלמת ההמרה, הדפדפן רושם את המקור והאפליקציה רושמת את ההמרה.
יש הבדלים באופן שבו כלי פרסום דיגיטלי מאורגנים באינטרנט וב-Android, ולכן הוספנו ממשקי API חדשים לרישום מקורות וטריגרים כשהם מתרחשים בדפדפנים. ההבדל העיקרי בין ממשקי ה-API האלה לבין ממשקי ה-API המקבילים שמבוססים על אפליקציות הוא שאנחנו מצפים מהדפדפן לעקוב אחרי ההפניות האוטומטיות, להחיל מסננים ספציפיים לדפדפן ולהעביר את הרישומים התקינים לפלטפורמה באמצעות קריאה ל-registerWebSource() או ל-registerWebTrigger().
בקטע הקוד הבא מוצגת דוגמה לקריאה ל-API שהדפדפן מבצע כדי לרשום מקור שיוך לפני שהוא מפנה את המשתמש לאפליקציה:
Kotlin
companion object {
private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}
val measurementManager =
context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null
// Use the URIs of the server-side endpoints that accept attribution source
// registration.
val sourceParam1 = WebSourceParams.Builder(Uri.parse(
"https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
// True, if debugging is allowed for the 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);
הוספת רעש לנתונים כדי להגן על הפרטיות
דוחות ברמת האירוע מכילים נתוני יעד, מזהה מקור שיוך וטריגר. הן נשלחות בפורמט המקורי (לא מוצפן) למקור הדיווח. כדי להגן על פרטיות המשתמשים, אפשר להוסיף רעש כדי להקשות על זיהוי של משתמש ספציפי. דוחות ברמת האירוע עם רעשי רקע נוצרים ונשלחים בהתאם למסגרת הפרטיות הדיפרנציאלית. אלה ערכי ברירת המחדל של אחוז הרעש בתרחישים שונים:
סוג המקור |
ערך יעד המקור |
הסתברות משוערת של דוח לכל רישום מקור |
הצגה |
באפליקציה או באינטרנט |
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 שולח דוחות לנקודות הקצה בשרת שלכם שמקבלות דוחות ברמת האירוע ודוחות נצברים.
אילוץ הרצה של משימות דיווח
אחרי שרושמים אירוע של מקור שיוך או אירוע של טריגר, המערכת מתזמנת את הרצת משימת הדיווח. כברירת מחדל, העבודה הזו מופעלת כל 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 יתמוך בהגדרות אופציונליות וגמישות יותר, כדי שחברות AdTech יוכלו לשלוט יותר במבנה של הדוחות ברמת האירוע ולמקסם את התועלת של הנתונים. הגמישות הנוספת הזו תוטמע ב-Attribution Reporting API בשני שלבים:
- שלב 1: הגדרה גמישה של אירועים ברמת Lite; קבוצת משנה של שלב 2.
- שלב 2: גרסה מלאה של הגדרה גמישה ברמת האירוע.
שלב 1: גמישות מוגבלת ברמת האירוע
נוסיף את שני הפרמטרים האופציונליים הבאים ל-JSON ב-Attribution-Reporting-Register-Source:
max_event_level_reportsevent_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 makes sure 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). שימו לב: יכול להיות שגורם מפעיל עדיין יתאים למקור של דוחות מצטברים, גם אם הוא לא עומד בשני קריטריוני ההתאמה שצוינו קודם.
- אילו נתונים ספציפיים של טריגר המפרט הזה חל עליהם. המקור הזה עומד בדרישות להתאמה רק להפעלות שיש להן אחד מהערכים שצוינו
- אלגוריתם ספציפי לסיכום ולסיווג של כל הטריגרים בחלון השיוך. כך אפשר להגדיר טריגרים שיציינו פרמטר
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>,
}
דוגמאות להגדרות בהתאמה אישית
בהמשך מפורטות כמה הגדרות נוספות שחורגות מהגדרות ברירת המחדל. בכל הדוגמאות האלה, הפשרות שהמפתחים נאלצים לעשות כוללות:
- צמצום של מאפיין מסוים בהגדרת ברירת המחדל (מספר הטריגרים, עוצמת הנתונים של הטריגר, מספר החלונות) כדי להגדיל מאפיין אחר ולשמור על רמת הרעש
- הפחתת חלק מהמאפיינים של הגדרת ברירת המחדל (מספר הטריגרים, הקרדינליות של נתוני הטריגר, מספר החלונות) כדי להפחית את רמת הרעש
דיווח על קבוצות של ערכי טריגר
הגדרת הדוגמה הזו תומכת במפתח שרוצה לבצע אופטימיזציה של נתוני ערך רק עבור חלון דיווח אחד (לדוגמה, 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]
}
Binary עם דיווח תכוף יותר
הגדרת הדוגמה הזו תומכת במפתח שרוצה לדעת אם התרחשה לפחות המרה אחת ב-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) חוצה-פלטפורמות ללא הפניות אוטומטיות
טכנולוגיות פרסום צריכות להשתמש בהפניות אוטומטיות כדי לרשום כמה טריגרים של מקורות שיוך ולבצע שיוך חוצה-רשתות. התכונה הזו עוזרת לתמוך בשיוך (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 ad tech identifier
// bits. eg. If Ad Tech-A's sources wins the attribution then 0x1 here will be
// OR'd with the trigger key pieces to generate the final key piece.
"Ad-Tech-A-enrollment_id": "0x1", // Identifier bits in hex for A
"Ad-Tech-B-enrollment_id": "0x2" // Identifier bits in hex for B
}
…
}
חישוב המפתח של הטריגר שמתקבל כשיוצרים דוח עבור המקור של AdTechB:
key_piece:0x400 (010000000000)key_offset:12- הערך של AdtechB:
enrollment_id(מ-x_network_bit_mapping)2 (010) - החלק של מפתח הטריגר שנוצר:
0x400 | 0x2 << 12 = 0x2400
מגבלות
רשימה של היכולות שנמצאות בתהליך פיתוח ב-SDK Runtime זמינה בנתוני הגרסה.
דיווח על באגים ובעיות
המשוב שלכם הוא חלק חשוב מאוד בארגז החול לפרטיות ב-Android. נשמח לקבל מכם דיווח על בעיות שנתקלתם בהן או רעיונות לשיפור ארגז החול לפרטיות ב-Android.