ניהול אנשי הקשר

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

במדריך הזה נסביר את הנושאים הבאים:

  • המבנה הבסיסי של הספק.
  • איך מאחזרים נתונים מהספק.
  • איך לשנות נתונים אצל הספק.
  • איך לכתוב מתאם לסנכרון לצורך סנכרון נתונים מהשרת שלכם לספק אנשי הקשר.

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

הארגון של ניהול אנשי הקשר

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

איור 1. מבנה הטבלה של ניהול אנשי הקשר.

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

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

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

אנשי קשר גולמיים

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

רוב הנתונים של איש קשר בפורמט גולמי לא מאוחסנים בטבלה ContactsContract.RawContacts. במקום זאת, הוא מאוחסן בשורה אחת או יותר בטבלה ContactsContract.Data. לכל שורת נתונים יש עמודה Data.RAW_CONTACT_ID שמכילה את הערך RawContacts._ID של שורת ההורה ContactsContract.RawContacts.

עמודות חשובות של אנשי קשר בפורמט גולמי

העמודות החשובות בטבלה ContactsContract.RawContacts מפורטות בטבלה 1. יש לקרוא את ההערות שמופיעות אחרי הטבלה:

טבלה 1. עמודות חשובות של אנשי קשר גולמיים.

שם העמודה שימוש הערות
ACCOUNT_NAME שם החשבון של סוג החשבון שממנו הגיע איש הקשר הגולמי הזה. לדוגמה, שם החשבון של חשבון Google הוא אחת מכתובות Gmail של הבעלים של המכשיר. מידע נוסף זמין ברשומה הבאה של ACCOUNT_TYPE. הפורמט של השם הזה ספציפי לסוג החשבון. היא לא חייבת להיות כתובת אימייל.
ACCOUNT_TYPE סוג החשבון שממנו הגיע איש הקשר הגולמי הזה. לדוגמה, סוג החשבון של חשבון Google הוא com.google. תמיד צריך לציין את סוג החשבון עם מזהה דומיין של דומיין שבבעלותכם או בשליטתכם. כך תוכלו לוודא שסוג החשבון יהיה ייחודי. לרוב, לסוג חשבון שמציע נתוני אנשי קשר יש מתאם סנכרון משויך שמסנכרן עם ספק אנשי הקשר.
DELETED הדגל 'נמחק' של איש קשר בפורמט גולמי. הדגל הזה מאפשר לספק אנשי הקשר לשמור את השורה באופן פנימי עד שהמתאמים לסנכרון יוכלו למחוק את השורה מהשרתים שלהם, ואז למחוק את השורה מהמאגר.

הערות

הערות חשובות לגבי הטבלה ContactsContract.RawContacts:

  • השם של איש קשר בפורמט גולמי לא נשמר בשורה שלו ב-ContactsContract.RawContacts. במקום זאת, הוא מאוחסן בטבלה ContactsContract.Data, בשורה ContactsContract.CommonDataKinds.StructuredName. לאיש קשר גולמי יש רק שורה אחת מהסוג הזה בטבלה ContactsContract.Data.
  • זהירות: כדי להשתמש בנתוני החשבון שלכם בשורה של איש קשר גולמי, קודם צריך לרשום אותם ב-AccountManager. כדי לעשות זאת, צריך לבקש מהמשתמשים להוסיף את סוג החשבון ואת שם החשבון שלהם לרשימת החשבונות. אם לא תעשו זאת, ספק אנשי הקשר ימחק באופן אוטומטי את שורת אנשי הקשר הגולמיים.

    לדוגמה, אם אתם רוצים שהאפליקציה תשמור את נתוני אנשי הקשר של השירות מבוסס-האינטרנט שלכם בדומיין com.example.dataservice, והחשבון של המשתמש בשירות הוא becky.sharp@dataservice.example.com, המשתמש צריך להוסיף קודם את 'סוג' החשבון (com.example.dataservice) ואת 'שם' החשבון (becky.smart@dataservice.example.com) כדי שהאפליקציה תוכל להוסיף שורות של אנשי קשר גולמיים. אפשר להסביר את הדרישה הזו למשתמש במסמכי העזרה, או לבקש מהמשתמש להוסיף את הסוג והשם, או את שניהם. סוגי החשבונות ושמות החשבונות מתוארים בהרחבה בקטע הבא.

מקורות של נתונים גולמיים של אנשי קשר

כדי להבין איך פועלים אנשי הקשר הגולמיים, ניקח לדוגמה את המשתמש 'Emily Dickinson', שיש לה שלושה חשבונות משתמשים מוגדרים במכשיר:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • חשבון Twitter‏ "belle_of_amherst"

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

נניח ש-Emily Dickinson פותחת חלון דפדפן, נכנסת ל-Gmail בתור emily.dickinson@gmail.com, פותחת את אנשי הקשר ומוסיפה את Thomas Higginson. בהמשך, היא מתחברת ל-Gmail בתור emilyd@gmail.com ושולחת אימייל אל 'Thomas Higginson', וכך הוא מתווסף באופן אוטומטי כאיש קשר. היא גם עוקבת אחרי 'colonel_tom' (מזהה Twitter של Thomas Higginson) ב-Twitter.

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

  1. איש קשר גולמי של 'Thomas Higginson' שמשויך ל-emily.dickinson@gmail.com. סוג חשבון המשתמש הוא Google.
  2. איש קשר גולמי שני של 'Thomas Higginson' שמשויך ל-emilyd@gmail.com. סוג חשבון המשתמש הוא גם Google. יש איש קשר גולמי שני, למרות שהשם זהה לשם קודם, כי האדם נוסף לחשבון משתמש אחר.
  3. איש קשר גולמי שלישי בשם 'Thomas Higginson' שמשויך לחשבון 'belle_of_amherst'. סוג חשבון המשתמש הוא Twitter.

נתונים

כפי שצוין קודם, הנתונים של איש קשר גולמי מאוחסנים בשורה ContactsContract.Data שמקושרת לערך _ID של איש הקשר הגולמי. כך אפשר ליצור כמה מופעים של אותו סוג נתונים, כמו כתובות אימייל או מספרי טלפון, לאותו איש קשר גולמי. לדוגמה, אם ל-'Thomas Higginson' ב-emilyd@gmail.com (שורת איש הקשר הגולמי של Thomas Higginson שמשויכת לחשבון Google emilyd@gmail.com) יש כתובת אימייל ביתית thigg@gmail.com וכתובת אימייל לעבודה thomas.higginson@gmail.com, ספק אנשי הקשר שומר את שתי השורות של כתובות האימייל ומקשר את שתיהן לאיש הקשר הגולמי.

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

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

דוגמאות לשמות עמודות תיאוריים:

RAW_CONTACT_ID
הערך של העמודה _ID של איש הקשר הגולמי לנתונים האלה.
MIMETYPE
סוג הנתונים שמאוחסנים בשורה הזו, כפי שהוא מופיע כסוג MIME מותאם אישית. ספק אנשי הקשר משתמש בסוגי ה-MIME שמוגדרים בתת-הסוגים של ContactsContract.CommonDataKinds. סוגי ה-MIME האלה הם קוד פתוח, וכל אפליקציה או מתאם סנכרון שפועלים עם ספק אנשי הקשר יכולים להשתמש בהם.
IS_PRIMARY
אם שורת הנתונים מהסוג הזה יכולה להופיע יותר מפעם אחת באיש קשר גולמי, עמודת IS_PRIMARY מסמנת את שורת הנתונים שמכילה את הנתונים הראשיים של הסוג. לדוגמה, אם המשתמש לוחץ לחיצה ארוכה על מספר טלפון של איש קשר ובוחר באפשרות הגדרת ברירת מחדל, בעמודה IS_PRIMARY של השורה ContactsContract.Data שמכילה את המספר הזה יוגדר ערך שאינו אפס.

שמות עמודות כלליים

יש 15 עמודות כלליות בשמות DATA1 עד DATA15 שזמינות לכולם, ועוד ארבע עמודות כלליות בשמות SYNC1 עד SYNC4 שרק מתאמי סנכרון יכולים להשתמש בהן. הקבועים של שמות העמודות הכלליים תמיד פועלים, ללא קשר לסוג הנתונים שהשורה מכילה.

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

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

שמות עמודות ספציפיים לסוג

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

לדוגמה, בכיתה ContactsContract.CommonDataKinds.Email מוגדרות קבועות של שמות עמודות ספציפיות לסוג בשביל שורה ContactsContract.Data עם סוג ה-MIME Email.CONTENT_ITEM_TYPE. הכיתה מכילה את הקבוע ADDRESS לעמודה של כתובת האימייל. הערך בפועל של ADDRESS הוא 'data1', שהוא זהה לשם הגנרי של העמודה.

זהירות: אל תוסיפו נתונים מותאמים אישית משלכם לטבלה ContactsContract.Data באמצעות שורה עם אחד מסוגי ה-MIME המוגדרים מראש של הספק. אם תעשו זאת, יכול להיות שתאבדו את הנתונים או שתגרמו לבעיות תפקוד אצל הספק. לדוגמה, אסור להוסיף שורה עם סוג ה-MIME Email.CONTENT_ITEM_TYPE שמכילה שם משתמש במקום כתובת אימייל בעמודה DATA1. אם משתמשים בסוג MIME בהתאמה אישית בשביל השורה, אפשר להגדיר שמות עמודות ספציפיים לסוג ולהשתמש בעמודות כרצונכם.

באיור 2 מוצג איך עמודות תיאור ועמודות נתונים מופיעות בשורה ContactsContract.Data, ואיך שמות עמודות ספציפיים לסוג 'חופפים' לשמות העמודות הכלליים.

איך שמות עמודות ספציפיים לסוג ממפים לשמות עמודות כלליים

איור 2. שמות עמודות ספציפיים לסוג ושמות עמודות כלליים.

כיתות של שמות עמודות ספציפיים לסוג

בטבלה 2 מפורטות הכיתות הנפוצות ביותר של שמות עמודות ספציפיות לסוג:

טבלה 2. כיתות של שמות עמודות ספציפיות לסוג

מחלקת מיפוי סוג הנתונים הערות
ContactsContract.CommonDataKinds.StructuredName נתוני השם של איש הקשר הגולמי שמשויך לשורת הנתונים הזו. לאיש קשר בפורמט גולמי יש רק אחת מהשורות האלה.
ContactsContract.CommonDataKinds.Photo התמונה הראשית של איש הקשר הגולמי שמשויך לשורת הנתונים הזו. לאיש קשר בפורמט גולמי יש רק אחת מהשורות האלה.
ContactsContract.CommonDataKinds.Email כתובת האימייל של איש הקשר הגולמי שמשויך לשורת הנתונים הזו. לאיש קשר בפורמט גולמי יכולות להיות כמה כתובות אימייל.
ContactsContract.CommonDataKinds.StructuredPostal כתובת למשלוח דואר של איש הקשר הגולמי שמשויך לשורת הנתונים הזו. לאיש קשר בפורמט גולמי יכולות להיות כמה כתובות.
ContactsContract.CommonDataKinds.GroupMembership מזהה שמקשר את איש הקשר הגולמי לאחת מהקבוצות בספק אנשי הקשר. קבוצות הן תכונה אופציונלית של סוג חשבון ושם חשבון. הן מתוארות בהרחבה בקטע קבוצות של אנשי קשר.

אנשי קשר

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

הערה: אם תנסו להוסיף איש קשר לספק אנשי הקשר עם insert(), תקבלו חריגה מסוג UnsupportedOperationException. אם תנסו לעדכן עמודה שמופיעה בתור 'לקריאה בלבד', המערכת תתעלם מהעדכון.

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

ספק אנשי הקשר מקשר שורה של איש קשר לשורות של אנשי הקשר הגולמיים שלו באמצעות עמודת _ID של שורת איש הקשר בטבלה Contacts. העמודה CONTACT_ID בטבלת אנשי הקשר הגולמיים ContactsContract.RawContacts מכילה ערכים של _ID בשביל שורת אנשי הקשר שמשויכת לכל שורה של אנשי קשר גולמיים.

בטבלה ContactsContract.Contacts יש גם את העמודה LOOKUP_KEY, שהיא קישור 'קבוע' לשורת איש הקשר. מכיוון שספק אנשי הקשר שומר את אנשי הקשר באופן אוטומטי, הוא עשוי לשנות את הערך של _ID בשורה של איש הקשר בתגובה לאיסוף או לסנכרון. גם אם זה יקרה, ה-URI של התוכן CONTENT_LOOKUP_URI בשילוב עם LOOKUP_KEY של איש הקשר עדיין יצביע על שורת איש הקשר, כך שתוכלו להשתמש ב-LOOKUP_KEY כדי לשמור על קישורים לאנשי קשר 'מועדפים' וכן הלאה. לעמודה הזו יש פורמט משלה שלא קשור לפורמט של העמודה _ID.

באיור 3 מוצגת היחסות בין שלוש הטבלאות הראשיות.

הטבלאות הראשיות של ניהול אנשי הקשר

איור 3. היחסים בין טבלאות Contacts,‏ Raw Contacts ו-Details.

זהירות: אם אתם מפרסמים את האפליקציה שלכם בחנות Google Play, או אם האפליקציה שלכם פועלת במכשיר עם Android 10 (רמת API 29) ואילך, חשוב לזכור שקבוצה מוגבלת של שדות ושל שיטות לנתוני אנשי קשר לא רלוונטית יותר.

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

גם ממשקי ה-API שמשמשים להגדרת שדות הנתונים שלמעלה לא תקפים יותר:

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

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

כדי לוודא שהפונקציונליות של האפליקציה לא מושפעת מהשינוי הזה, תוכלו לנקות את שדות הנתונים האלה באופן ידני. כדי לעשות זאת, מריצים את הפקודה הבאה של ADB במכשיר עם Android 4.1 (API ברמה 16) ואילך:

adb shell content delete \
--uri content://com.android.contacts/contacts/delete_usage

נתונים ממתאמי סנכרון

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

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

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

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

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

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

זרימת נתונים על אנשים

איור 4. תהליך העברת הנתונים של 'ניהול אנשי הקשר'.

הרשאות נדרשות

אפליקציות שרוצות לגשת לספק אנשי הקשר צריכות לבקש את ההרשאות הבאות:

הרשאת קריאה לטבלה אחת או יותר
READ_CONTACTS, שצוין ב-AndroidManifest.xml עם רכיב <uses-permission> בתור <uses-permission android:name="android.permission.READ_CONTACTS">.
הרשאת כתיבה לטבלה אחת או יותר
WRITE_CONTACTS, שצוין ב-AndroidManifest.xml עם רכיב <uses-permission> בתור <uses-permission android:name="android.permission.WRITE_CONTACTS">.

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

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

פרופיל המשתמש

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

כדי לגשת לפרופיל המשתמש נדרשות הרשאות מיוחדות. בנוסף להרשאות READ_CONTACTS ו-WRITE_CONTACTS שנדרשות לקריאה ולכתיבה, כדי לגשת לפרופיל המשתמש נדרשות ההרשאות android.Manifest.permission#READ_PROFILE ו-android.Manifest.permission#WRITE_PROFILE לקריאה ולכתיבה, בהתאמה.

חשוב לזכור שפרופיל של משתמש נחשב כמידע אישי רגיש. ההרשאה android.Manifest.permission#READ_PROFILE מאפשרת לכם לגשת לנתונים המזהים אישית של משתמש המכשיר. חשוב לציין בתיאור האפליקציה למה דרושות לכם הרשאות גישה לפרופיל המשתמש.

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

Kotlin

// Sets the columns to retrieve for the user profile
projection = arrayOf(
        ContactsContract.Profile._ID,
        ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
        ContactsContract.Profile.LOOKUP_KEY,
        ContactsContract.Profile.PHOTO_THUMBNAIL_URI
)

// Retrieves the profile from the Contacts Provider
profileCursor = contentResolver.query(
        ContactsContract.Profile.CONTENT_URI,
        projection,
        null,
        null,
        null
)

Java

// Sets the columns to retrieve for the user profile
projection = new String[]
    {
        Profile._ID,
        Profile.DISPLAY_NAME_PRIMARY,
        Profile.LOOKUP_KEY,
        Profile.PHOTO_THUMBNAIL_URI
    };

// Retrieves the profile from the Contacts Provider
profileCursor =
        getContentResolver().query(
                Profile.CONTENT_URI,
                projection ,
                null,
                null,
                null);

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

מטא-נתונים של ניהול אנשי הקשר

שירות ניהול אנשי הקשר מנהל נתונים שמתעדים את המצב של נתוני אנשי הקשר במאגר. המטא-נתונים האלה לגבי המאגר מאוחסנים במקומות שונים, כולל השורות בטבלאות Raw Contacts‏, Data ו-Contacts, בטבלה ContactsContract.Settings ובטבלה ContactsContract.SyncState. בטבלה הבאה מפורטת ההשפעה של כל אחד מהמטא-נתונים האלה:

טבלה 3 מטא-נתונים ב'ניהול אנשי הקשר'

טבלה עמודה ערכים משמעות
ContactsContract.RawContacts DIRTY '0' – לא השתנה מאז הסנכרון האחרון. סימון של אנשי קשר גולמיים שהשתנו במכשיר וצריך לסנכרן אותם חזרה עם השרת. הערך מוגדר באופן אוטומטי על ידי ספק אנשי הקשר כשאפליקציות Android מעדכנות שורה.

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

'1' – השתנה מאז הסנכרון האחרון, צריך לסנכרן אותו חזרה לשרת.
ContactsContract.RawContacts VERSION מספר הגרסה של השורה הזו. ספק אנשי הקשר מגדיל את הערך הזה באופן אוטומטי בכל פעם שהשורה או הנתונים הקשורים שלה משתנים.
ContactsContract.Data DATA_VERSION מספר הגרסה של השורה הזו. ספק אנשי הקשר מגדיל את הערך הזה באופן אוטומטי בכל פעם ששורת הנתונים משתנה.
ContactsContract.RawContacts SOURCE_ID ערך מחרוזת שמזהה באופן ייחודי את איש הקשר הגולמי הזה בחשבון שבו הוא נוצר. כשמתאם סנכרון יוצר איש קשר חדש בפורמט גולמי, צריך להגדיר בעמודה הזו את המזהה הייחודי של איש הקשר בפורמט גולמי בשרת. כשאפליקציה ל-Android יוצרת איש קשר חדש בפורמט גולמי, היא צריכה להשאיר את העמודה הזו ריקה. הפעולה הזו מאותתת למתאם הסנכרון שהוא צריך ליצור איש קשר חדש בפורמט גולמי בשרת ולקבל ערך בשדה SOURCE_ID.

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

  • ייחודי: לכל איש קשר גולמי בחשבון חייב להיות מזהה מקור משלו. אם לא יאכפו את הכלל הזה, יהיו בעיות באפליקציית אנשי הקשר. חשוב לזכור שלשני אנשי קשר גולמיים באותו סוג של חשבון יכול להיות אותו מזהה מקור. לדוגמה, ליצירת קשר 'Thomas Higginson' בחשבון emily.dickinson@gmail.com מותר להשתמש באותו מזהה מקור כמו ליצירת קשר 'Thomas Higginson' בחשבון emilyd@gmail.com.
  • יציב: מזהי המקור הם חלק קבוע מהנתונים של השירות אונליין לגבי איש הקשר הגולמי. לדוגמה, אם המשתמש מנקה את האחסון של אנשי הקשר בהגדרות האפליקציות ומבצע סנכרון מחדש, לאנשי הקשר הגולמיים המשוחזרים אמורים להיות אותם מזהי מקור כמו קודם. אם לא יאכפו את המדיניות הזו, מקשי הקיצור יפסיקו לפעול.
ContactsContract.Groups GROUP_VISIBLE '0' – אנשי הקשר בקבוצה הזו לא אמורים להיות גלויים בממשקי המשתמש של אפליקציות ל-Android. העמודה הזו מיועדת לתאימות עם שרתים שמאפשרים למשתמש להסתיר אנשי קשר בקבוצות מסוימות.
'1' – אנשי הקשר בקבוצה הזו יכולים להופיע בממשקי המשתמש של האפליקציות.
ContactsContract.Settings UNGROUPED_VISIBLE '0' – בחשבון הזה ובסוג החשבון הזה, אנשי קשר שלא שייכים לקבוצה לא גלויים לממשקי המשתמש של אפליקציות ל-Android. כברירת מחדל, אנשי קשר לא גלויים אם אף אחד מאנשי הקשר הגולמיים שלהם לא שייך לקבוצה (השתייכות לקבוצה של איש קשר גולמי מסומן בשורה אחת או יותר של ContactsContract.CommonDataKinds.GroupMembership בטבלה ContactsContract.Data). אם מגדירים את הדגל הזה בשורה ContactsContract.Settings בטבלה של סוג החשבון ושל החשבון, אפשר לאלץ את אנשי הקשר ללא קבוצות להיות גלויים. אחת מהשימושים בדגל הזה היא להציג אנשי קשר משרתי אימייל שלא משתמשים בקבוצות.
'1' – בחשבון הזה ובסוג החשבון הזה, אנשי קשר שלא שייכים לקבוצה גלויים לממשקי המשתמש של האפליקציות.
ContactsContract.SyncState (הכול) משתמשים בטבלה הזו כדי לאחסן מטא-נתונים של מתאם הסנכרון. בעזרת הטבלה הזו אפשר לאחסן במכשיר באופן קבוע את מצב הסנכרון ונתונים אחרים שקשורים לסנכרון.

גישה של ניהול אנשי הקשר

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

  • שאילתות על ישויות.
  • שינוי קבוצתי.
  • אחזור ושינוי באמצעות כוונות.
  • תקינות הנתונים.

אפשר לקרוא מידע נוסף על ביצוע שינויים באמצעות מתאם סנכרון בקטע מתאמי סנכרון של ספקי אנשי קשר.

שליחת שאילתות על ישויות

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

ישות היא כמו טבלה המורכבת מעמודות שנבחרו מטבלת הורה ומהטבלה הצאצאית שלה. כששולחים שאילתה לגבי ישות, צריך לספק הקרנה וקריטריונים לחיפוש על סמך העמודות שזמינות מהישות. התוצאה היא Cursor שמכיל שורה אחת לכל שורה בטבלת הצאצא שאוחזרה. לדוגמה, אם שולחים שאילתה ל-ContactsContract.Contacts.Entity עם שם של איש קשר וכל השורות של ContactsContract.CommonDataKinds.Email עם כל אנשי הקשר הגולמיים של השם הזה, מקבלים חזרה Cursor שמכיל שורה אחת לכל שורה של ContactsContract.CommonDataKinds.Email.

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

הערה: בדרך כלל ישות לא מכילה את כל העמודות של טבלת ההורה וטבלת הצאצא. אם מנסים לעבוד עם שם עמודה שלא מופיע ברשימת הקבועים של שמות העמודות של הישות, מופיעה הערך Exception.

קטע הקוד הבא מראה איך לאחזר את כל שורות איש הקשר הגולמיות של איש קשר. קטע הקוד הוא חלק מאפליקציה גדולה יותר עם שתי פעילויות, 'main' ו-'detail'. בפעילות הראשית מוצגת רשימה של שורות של אנשי קשר. כשהמשתמש בוחר אחת מהן, הפעילות שולחת את המזהה שלה לפעילות המפורטת. בפעילות הפרטים נעשה שימוש ב-ContactsContract.Contacts.Entity כדי להציג את כל שורות הנתונים מכל אנשי הקשר הגולמיים שמשויכים לאיש הקשר שנבחר.

קטע הקוד הזה נלקח מהפעילות 'פרטים':

Kotlin

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY
    )

    // Initializes the loader identified by LOADER_ID.
    loaderManager.initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this        // The context of the activity
    )

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = SimpleCursorAdapter(
            this,                       // the context of the activity
            R.layout.detail_list_item,  // the view item containing the detail widgets
            mCursor,                    // the backing cursor
            fromColumns,               // the columns in the cursor that provide the data
            toViews,                   // the views in the view item that display the data
            0)                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.adapter = cursorAdapter
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    val projection: Array<String> = arrayOf(
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
    )

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC"

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return CursorLoader(
            applicationContext, // The activity's context
            contactUri,        // The entity content URI for a single contact
            projection,         // The columns to retrieve
            null,               // Retrieve all the raw contacts and their data rows.
            null,               //
            sortOrder           // Sort by the raw contact ID.
    )
}

Java

...
    /*
     * Appends the entity path to the URI. In the case of the Contacts Provider, the
     * expected URI is content://com.google.contacts/#/entity (# is the ID value).
     */
    contactUri = Uri.withAppendedPath(
            contactUri,
            ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);

    // Initializes the loader identified by LOADER_ID.
    getLoaderManager().initLoader(
            LOADER_ID,  // The identifier of the loader to initialize
            null,       // Arguments for the loader (in this case, none)
            this);      // The context of the activity

    // Creates a new cursor adapter to attach to the list view
    cursorAdapter = new SimpleCursorAdapter(
            this,                        // the context of the activity
            R.layout.detail_list_item,   // the view item containing the detail widgets
            mCursor,                     // the backing cursor
            fromColumns,                // the columns in the cursor that provide the data
            toViews,                    // the views in the view item that display the data
            0);                          // flags

    // Sets the ListView's backing adapter.
    rawContactList.setAdapter(cursorAdapter);
...
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    /*
     * Sets the columns to retrieve.
     * RAW_CONTACT_ID is included to identify the raw contact associated with the data row.
     * DATA1 contains the first column in the data row (usually the most important one).
     * MIMETYPE indicates the type of data in the data row.
     */
    String[] projection =
        {
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
            ContactsContract.Contacts.Entity.DATA1,
            ContactsContract.Contacts.Entity.MIMETYPE
        };

    /*
     * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw
     * contact collated together.
     */
    String sortOrder =
            ContactsContract.Contacts.Entity.RAW_CONTACT_ID +
            " ASC";

    /*
     * Returns a new CursorLoader. The arguments are similar to
     * ContentResolver.query(), except for the Context argument, which supplies the location of
     * the ContentResolver to use.
     */
    return new CursorLoader(
            getApplicationContext(),  // The activity's context
            contactUri,              // The entity content URI for a single contact
            projection,               // The columns to retrieve
            null,                     // Retrieve all the raw contacts and their data rows.
            null,                     //
            sortOrder);               // Sort by the raw contact ID.
}

בסיום הטעינה, LoaderManager מפעילה קריאה חוזרת (callback) ל-onLoadFinished(). אחד מהארגומנטים הנכנסים לשיטה הזו הוא Cursor עם תוצאות השאילתה. באפליקציה שלכם, תוכלו לקבל את הנתונים מ-Cursor כדי להציג אותם או להמשיך לעבוד איתם.

שינוי של מספר פריטים בו-זמנית

כשהדבר אפשרי, מומלץ להוסיף, לעדכן ולמחוק נתונים ב-Contacts Provider ב'מצב באצווה'. לשם כך, יוצרים ArrayList של אובייקטים מסוג ContentProviderOperation ומפעילים את applyBatch(). מכיוון שספק אנשי הקשר מבצע את כל הפעולות ב-applyBatch() בעסקה אחת, השינויים שלכם לעולם לא יגרמו למצב לא עקבי במאגר אנשי הקשר. שינוי בכמות גדולה מאפשר גם להוסיף בו-זמנית איש קשר בסיסי ואת פרטיו המפורטים.

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

נקודות עצירה

שינוי באצווה שמכיל מספר גדול של פעולות עלול לחסום תהליכים אחרים, וכתוצאה מכך חוויית המשתמש הכוללת תהיה גרועה. כדי לארגן את כל השינויים שרוצים לבצע ברשימות נפרדות ספורות ככל האפשר, ובמקביל למנוע מהם לחסום את המערכת, צריך להגדיר נקודות תשואה לפעולה אחת או יותר. נקודת כניעה היא אובייקט ContentProviderOperation שהערך שלו בשדה isYieldAllowed() מוגדר כ-true. כשספק אנשי הקשר נתקל בנקודת העברה, הוא משהה את העבודה כדי לאפשר לתהליכים אחרים לפעול וסוגר את העסקה הנוכחית. כשהספק מתחיל שוב, הוא ממשיך בפעולה הבאה ב-ArrayList ומתחיל עסקה חדשה.

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

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

שינוי של הפניות לאחור

כשאתם מוסיפים שורה חדשה של איש קשר בפורמט גולמי ושורות הנתונים המשויכות אליה כקבוצה של אובייקטים מסוג ContentProviderOperation, אתם צריכים לקשר את שורות הנתונים לשורה של איש הקשר בפורמט גולמי על ידי הוספת הערך _ID של איש הקשר בפורמט גולמי בתור הערך RAW_CONTACT_ID. עם זאת, הערך הזה לא זמין כשיוצרים את ContentProviderOperation בשורה של הנתונים, כי עדיין לא החילו את ContentProviderOperation על שורת איש הקשר הגולמי. כדי לעקוף את הבעיה, לכיתה ContentProviderOperation.Builder יש את השיטה withValueBackReference(). השיטה הזו מאפשרת להוסיף או לשנות עמודה עם התוצאה של פעולה קודמת.

לשיטה withValueBackReference() יש שני ארגומנטים:

key
המפתח של צמד מפתח/ערך. הערך של הארגומנט הזה צריך להיות שם של עמודה בטבלה שאתם משנים.
previousResult
האינדקס שמתחיל בספרה אפס של ערך במערך של אובייקטים מסוג ContentProviderResult מ-applyBatch(). כשמפעילים את פעולות האצווה, התוצאה של כל פעולה מאוחסנת במערך ביניים של תוצאות. הערך previousResult הוא האינדקס של אחת מהתוצאות האלה, שנאחזר ונשמר עם הערך key. כך תוכלו להוסיף רשומת איש קשר חדשה ולקבל בחזרה את הערך של _ID, ואז ליצור "הפניה לאחור" לערך כשמוסיפים שורה ContactsContract.Data.

מערך התוצאות כולו נוצר בקריאה הראשונה ל-applyBatch(), בגודל שווה לגודל של ArrayList של אובייקטי ContentProviderOperation שסיפקתם. עם זאת, כל הרכיבים במערך התוצאות מוגדרים ל-null, ואם מנסים לבצע הפניה לאחור לתוצאה של פעולה שעדיין לא הוחלו, מתקבלת הודעת השגיאה Exception ב-withValueBackReference().

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

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

Kotlin

// Creates a contact entry from the current UI values, using the currently-selected account.
private fun createContactEntry() {
    /*
     * Gets values from the UI
     */
    val name = contactNameEditText.text.toString()
    val phone = contactPhoneEditText.text.toString()
    val email = contactEmailEditText.text.toString()

    val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition]

    val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]

Java

// Creates a contact entry from the current UI values, using the currently-selected account.
protected void createContactEntry() {
    /*
     * Gets values from the UI
     */
    String name = contactNameEditText.getText().toString();
    String phone = contactPhoneEditText.getText().toString();
    String email = contactEmailEditText.getText().toString();

    int phoneType = contactPhoneTypes.get(
            contactPhoneTypeSpinner.getSelectedItemPosition());

    int emailType = contactEmailTypes.get(
            contactEmailTypeSpinner.getSelectedItemPosition());

קטע הקוד הבא יוצר פעולה להוספת שורת איש הקשר הגולמי לטבלה ContactsContract.RawContacts:

Kotlin

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

    // Creates a new array of ContentProviderOperation objects.
    val ops = arrayListOf<ContentProviderOperation>()

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    var op: ContentProviderOperation.Builder =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    /*
     * Prepares the batch operation for inserting a new raw contact and its data. Even if
     * the Contacts Provider does not have any data for this person, you can't add a Contact,
     * only a raw contact. The Contacts Provider will then add a Contact automatically.
     */

     // Creates a new array of ContentProviderOperation objects.
    ArrayList<ContentProviderOperation> ops =
            new ArrayList<ContentProviderOperation>();

    /*
     * Creates a new raw contact with its account type (server type) and account name
     * (user's account). Remember that the display name is not stored in this row, but in a
     * StructuredName data row. No other data is required.
     */
    ContentProviderOperation.Builder op =
            ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType())
            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

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

כל אובייקט של ה-builder של הפעולה משתמש ב-withValueBackReference() כדי לקבל את ה-RAW_CONTACT_ID. נקודות העזרה מפנות לאובייקט ContentProviderResult מהפעולה הראשונה, שמוסיפה את שורת איש הקשר הגולמי ומחזירה את הערך החדש של _ID. כתוצאה מכך, כל שורת נתונים מקושרת באופן אוטומטי לפי הערך שלה בשדה RAW_CONTACT_ID לשורת ContactsContract.RawContacts החדשה שאליה היא שייכת.

האובייקט ContentProviderOperation.Builder שמוסיף את שורת האימייל מסומן ב-withYieldAllowed(), שמגדיר נקודת תשואה:

Kotlin

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified phone number and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

    // Inserts the specified email and type as a Phone data row
    op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType)

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true)

    // Builds the operation and adds it to the array of operations
    ops.add(op.build())

Java

    // Creates the display name for the new raw contact, as a StructuredName data row.
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * withValueBackReference sets the value of the first argument to the value of
             * the ContentProviderResult indexed by the second argument. In this particular
             * call, the raw contact ID column of the StructuredName data row is set to the
             * value of the result returned by the first operation, which is the one that
             * actually adds the raw contact row.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to StructuredName
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)

            // Sets the data row's display name to the name in the UI.
            .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified phone number and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Phone
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

            // Sets the phone number and type
            .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
            .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

    // Inserts the specified email and type as a Phone data row
    op =
            ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
            /*
             * Sets the value of the raw contact id column to the new raw contact ID returned
             * by the first operation in the batch.
             */
            .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)

            // Sets the data row's MIME type to Email
            .withValue(ContactsContract.Data.MIMETYPE,
                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

            // Sets the email address and type
            .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
            .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType);

    /*
     * Demonstrates a yield point. At the end of this insert, the batch operation's thread
     * will yield priority to other threads. Use after every set of operations that affect a
     * single contact, to avoid degrading performance.
     */
    op.withYieldAllowed(true);

    // Builds the operation and adds it to the array of operations
    ops.add(op.build());

בקטע הקוד האחרון מוצגת הקריאה ל-applyBatch() שמוסיפה את שורות הנתונים ואנשי הקשר הגולמיים החדשים.

Kotlin

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})")
    Log.d(TAG, "Creating contact: $name")

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
    } catch (e: Exception) {
        // Display a warning
        val txt: String = getString(R.string.contactCreationFailure)
        Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show()

        // Log exception
        Log.e(TAG, "Exception encountered while inserting contact: $e")
    }
}

Java

    // Ask the Contacts Provider to create a new contact
    Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" +
            selectedAccount.getType() + ")");
    Log.d(TAG,"Creating contact: " + name);

    /*
     * Applies the array of ContentProviderOperation objects in batch. The results are
     * discarded.
     */
    try {

            getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    } catch (Exception e) {

            // Display a warning
            Context ctx = getApplicationContext();

            CharSequence txt = getString(R.string.contactCreationFailure);
            int duration = Toast.LENGTH_SHORT;
            Toast toast = Toast.makeText(ctx, txt, duration);
            toast.show();

            // Log exception
            Log.e(TAG, "Exception encountered while inserting contact: " + e);
    }
}

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

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

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

  1. מאחזרים את העמודה VERSION של איש הקשר הגולמי יחד עם הנתונים האחרים שאתם מאחזרים.
  2. יוצרים אובייקט ContentProviderOperation.Builder שמתאים לאכיפה של אילוץ באמצעות השיטה newAssertQuery(Uri). ב-URI של התוכן, משתמשים ב-RawContacts.CONTENT_URI עם ה-_ID של איש הקשר הגולמי שמצורף אליו.
  3. באובייקט ContentProviderOperation.Builder, צריך להפעיל את הפונקציה withValue() כדי להשוות את העמודה VERSION למספר הגרסה שאתם מקבלים.
  4. לאותו ContentProviderOperation.Builder, צריך להפעיל את withExpectedCount() כדי לוודא שרק שורה אחת נבדקת על ידי ההצהרה הזו.
  5. קוראים ל-build() כדי ליצור את האובייקט ContentProviderOperation, ואז מוסיפים את האובייקט הזה כאובייקט הראשון ב-ArrayList ששולחים ל-applyBatch().
  6. מחילים את עסקת האצווה.

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

קטע הקוד הבא מדגים איך ליצור טענת נכונות (assert)‏ ContentProviderOperation אחרי ששולחים שאילתה ליצירת איש קשר גולמי יחיד באמצעות CursorLoader:

Kotlin

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID))
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION))
}

...

// Sets up a Uri for the assert operation
val rawContactUri: Uri = ContentUris.withAppendedId(
        ContactsContract.RawContacts.CONTENT_URI,
        rawContactID
)

// Creates a builder for the assert operation
val assertOp: ContentProviderOperation.Builder =
        ContentProviderOperation.newAssertQuery(rawContactUri).apply {
            // Adds the assertions to the assert operation: checks the version
            withValue(SyncColumns.VERSION, mVersion)

            // and count of rows tested
            withExpectedCount(1)
        }

// Creates an ArrayList to hold the ContentProviderOperation objects
val ops = arrayListOf<ContentProviderOperation>()

ops.add(assertOp.build())

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try {
    val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops)
} catch (e: OperationApplicationException) {
    // Actions you want to take if the assert operation fails go here
}

Java

/*
 * The application uses CursorLoader to query the raw contacts table. The system calls this method
 * when the load is finished.
 */
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Gets the raw contact's _ID and VERSION values
    rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
    mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION));
}

...

// Sets up a Uri for the assert operation
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID);

// Creates a builder for the assert operation
ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri);

// Adds the assertions to the assert operation: checks the version and count of rows tested
assertOp.withValue(SyncColumns.VERSION, mVersion);
assertOp.withExpectedCount(1);

// Creates an ArrayList to hold the ContentProviderOperation objects
ArrayList ops = new ArrayList<ContentProviderOperation>;

ops.add(assertOp.build());

// You would add the rest of your batch operations to "ops" here

...

// Applies the batch. If the assert fails, an Exception is thrown
try
    {
        ContentProviderResult[] results =
                getContentResolver().applyBatch(AUTHORITY, ops);

    } catch (OperationApplicationException e) {

        // Actions you want to take if the assert operation fails go here
    }

אחזור ושינוי באמצעות כוונות

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

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

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

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

התהליך הכללי לשליחת כוונה לגשת לספק מתואר בפירוט במדריך בסיסים לספקי תוכן בקטע 'גישה לנתונים באמצעות כוונות'. הפעולה, סוג ה-MIME וערכים של נתונים שבהם משתמשים במשימות הזמינות מפורטים בטבלה 4, ואילו הערכים הנוספים שבהם אפשר להשתמש עם putExtra() מפורטים במסמכי העזרה של ContactsContract.Intents.Insert:

טבלה 4. כוונות של ספקי אנשי קשר.

משימה פעולה נתונים סוג MIME הערות
בחירת איש קשר מרשימה ACTION_PICK אחת מהאפשרויות הבאות:
  • Contacts.CONTENT_URI, שתוצג רשימת אנשי קשר.
  • Phone.CONTENT_URI,‏ שמציגה רשימה של מספרי טלפון של איש קשר בפורמט גולמי.
  • StructuredPostal.CONTENT_URI,‏ שמציגה רשימה של כתובות למשלוח דואר של איש קשר בפורמט גולמי.
  • Email.CONTENT_URI,‏ שמציגה רשימה של כתובות אימייל של איש קשר בפורמט גולמי.
צולם בלי מבזק הצגת רשימה של אנשי קשר גולמיים או רשימה של נתונים מאיש קשר גולמי, בהתאם לסוג ה-URI של התוכן שסיפקתם.

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

הוספת איש קשר חדש בפורמט גולמי Insert.ACTION לא רלוונטי RawContacts.CONTENT_TYPE, סוג MIME של קבוצת אנשי קשר גולמיים. המסך הוספת איש קשר באפליקציית אנשי הקשר של המכשיר מוצג. מוצגים הערכים הנוספים שמוסיפים לכוונה. אם שולחים את הבקשה עם startActivityForResult(), ה-URI של התוכן של איש הקשר הגולמי שנוסף מחדש מועבר בחזרה לשיטת ה-callback‏ onActivityResult() של הפעילות, בארגומנט Intent, בשדה data. כדי לקבל את הערך, קוראים לפונקציה getData().
עריכת איש קשר ACTION_EDIT CONTENT_LOOKUP_URI לאיש הקשר. הפעילות של עורך הרשאות תאפשר למשתמש לערוך את כל הנתונים שמשויכים לאיש הקשר הזה. Contacts.CONTENT_ITEM_TYPE, איש קשר יחיד. הצגת המסך 'עריכת איש קשר' באפליקציית אנשי הקשר. מוצגים הערכים הנוספים שמוסיפים לכוונה. כשהמשתמש לוחץ על סיום כדי לשמור את העריכות, הפעילות חוזרת לחזית.
הצגת בורר שאפשר גם להוסיף נתונים ACTION_INSERT_OR_EDIT לא רלוונטי CONTENT_ITEM_TYPE כוונת החיפוש הזו תמיד מציגה את מסך הבחירה של אפליקציית אנשי הקשר. המשתמש יכול לבחור איש קשר לעריכה או להוסיף איש קשר חדש. מסך העריכה או המסך להוספה יופיעו, בהתאם לבחירה של המשתמש, ונתוני ה-extras שתעבירו בכוונה יוצגו. אם באפליקציה מוצגים פרטים ליצירת קשר כמו כתובת אימייל או מספר טלפון, אפשר להשתמש בכוונה הזו כדי לאפשר למשתמש להוסיף את הפרטים לאיש קשר קיים. איש קשר,

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

אפליקציית אנשי הקשר של המכשיר לא מאפשרת למחוק איש קשר בסיסי או את הנתונים שלו באמצעות כוונה. במקום זאת, כדי למחוק איש קשר בפורמט גולמי, משתמשים ב-ContentResolver.delete() או ב-ContentProviderOperation.newDelete().

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

Kotlin

// Gets values from the UI
val name = contactNameEditText.text.toString()
val phone = contactPhoneEditText.text.toString()
val email = contactEmailEditText.text.toString()

val company = companyName.text.toString()
val jobtitle = jobTitle.text.toString()

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
val contactData = arrayListOf<ContentValues>()

/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
val rawContactRow = ContentValues().apply {
    // Adds the account type and name to the row
    put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type)
    put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name)
}

// Adds the row to the array
contactData.add(rawContactRow)

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
val phoneRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)

    // Adds the phone number and its type to the row
    put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
}

// Adds the row to the array
contactData.add(phoneRow)

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
val emailRow = ContentValues().apply {
    // Specifies the MIME type for this data row (all data rows must be marked by their type)
    put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)

    // Adds the email address and its type to the row
    put(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
}

// Adds the row to the array
contactData.add(emailRow)

// Creates a new intent for sending to the device's contacts application
val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply {
    // Sets the MIME type to the one expected by the insertion activity
    type = ContactsContract.RawContacts.CONTENT_TYPE

    // Sets the new contact name
    putExtra(ContactsContract.Intents.Insert.NAME, name)

    // Sets the new company and job title
    putExtra(ContactsContract.Intents.Insert.COMPANY, company)
    putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle)

    /*
    * Adds the array to the intent's extras. It must be a parcelable object in order to
    * travel between processes. The device's contacts app expects its key to be
    * Intents.Insert.DATA
    */
    putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData)
}

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent)

Java

// Gets values from the UI
String name = contactNameEditText.getText().toString();
String phone = contactPhoneEditText.getText().toString();
String email = contactEmailEditText.getText().toString();

String company = companyName.getText().toString();
String jobtitle = jobTitle.getText().toString();

// Creates a new intent for sending to the device's contacts application
Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION);

// Sets the MIME type to the one expected by the insertion activity
insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE);

// Sets the new contact name
insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name);

// Sets the new company and job title
insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company);
insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle);

/*
 * Demonstrates adding data rows as an array list associated with the DATA key
 */

// Defines an array list to contain the ContentValues objects for each row
ArrayList<ContentValues> contactData = new ArrayList<ContentValues>();


/*
 * Defines the raw contact row
 */

// Sets up the row as a ContentValues object
ContentValues rawContactRow = new ContentValues();

// Adds the account type and name to the row
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType());
rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName());

// Adds the row to the array
contactData.add(rawContactRow);

/*
 * Sets up the phone number data row
 */

// Sets up the row as a ContentValues object
ContentValues phoneRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
phoneRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
);

// Adds the phone number and its type to the row
phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);

// Adds the row to the array
contactData.add(phoneRow);

/*
 * Sets up the email data row
 */

// Sets up the row as a ContentValues object
ContentValues emailRow = new ContentValues();

// Specifies the MIME type for this data row (all data rows must be marked by their type)
emailRow.put(
        ContactsContract.Data.MIMETYPE,
        ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
);

// Adds the email address and its type to the row
emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email);

// Adds the row to the array
contactData.add(emailRow);

/*
 * Adds the array to the intent's extras. It must be a parcelable object in order to
 * travel between processes. The device's contacts app expects its key to be
 * Intents.Insert.DATA
 */
insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData);

// Send out the intent to start the device's contacts app in its add contact activity.
startActivity(insertIntent);

תקינות נתונים

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

תמיד מוסיפים שורה של ContactsContract.CommonDataKinds.StructuredName לכל שורה של ContactsContract.RawContacts שמוסיפים.
שורה ContactsContract.RawContacts ללא שורה ContactsContract.CommonDataKinds.StructuredName בטבלה ContactsContract.Data עלולה לגרום לבעיות במהלך צבירה.
תמיד צריך לקשר שורות ContactsContract.Data חדשות לשורת ההורה שלהן, ContactsContract.RawContacts.
שורה של ContactsContract.Data שלא מקושרת ל-ContactsContract.RawContacts לא תהיה גלויה באפליקציית אנשי הקשר של המכשיר, ויכול להיות שהיא תגרום לבעיות במתאמי סנכרון.
לשנות את הנתונים רק לגבי אנשי הקשר הגולמיים שבבעלותכם.
חשוב לזכור שספק אנשי הקשר בדרך כלל מנהל נתונים מכמה סוגים שונים של חשבונות או שירותים אונליין. עליכם לוודא שהאפליקציה משנה או מוחקת רק נתונים של שורות ששייכות לכם, ושהיא מזינה רק נתונים עם סוג חשבון ושם חשבון שנמצאים בשליטתכם.
תמיד צריך להשתמש בערכי הקבועים שמוגדרים ב-ContactsContract ובתת-הסוגים שלו עבור רשויות, מזהי URI של תוכן, נתיבים של URI, שמות של עמודות, סוגי MIME וערכים של TYPE.
השימוש בערכי הקבועים האלה עוזר למנוע שגיאות. תקבלו גם התראות מהמחשב אם אחת מהקבועות הוצאה משימוש.

שורות של נתונים בהתאמה אישית

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

כדי להציג את הנתונים המותאמים אישית, צריך לספק קובץ contacts.xml שמכיל רכיב <ContactsAccountType> ואחד או יותר מרכיבי הצאצא שלו <ContactsDataKind>. הנושא הזה מוסבר בפירוט בקטע <ContactsDataKind> element.

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

מתאמי סנכרון של ספקי אנשי קשר

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

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

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

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

סנכרון של קבצים וכיתות של מתאמים

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

הערה: שימוש בסוג חשבון כחלק מהזיהוי של מתאם הסנכרון מאפשר למערכת לזהות ולקבץ יחד מתאמי סנכרון שיש להם גישה לשירותים שונים מאותו ארגון. לדוגמה, לכל מתאמי הסנכרון של שירותי Google אונליין יש את אותו סוג חשבון com.google. כשמשתמשים מוסיפים חשבון Google למכשירים שלהם, כל מתאמי הסנכרון המותקנים לשירותי Google מופיעים יחד. כל מתאם סנכרון שמופיע מסתנכרן עם ספק תוכן אחר במכשיר.

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

  1. אוספת את השם, הסיסמה או מידע דומה של המשתמש (פרטי הכניסה של המשתמש).
  2. שליחת פרטי הכניסה לשירות
  3. בודק את התשובה של השירות.

אם השירות מקבל את פרטי הכניסה, מאמת החשבונות יכול לשמור אותם לשימוש מאוחר יותר. בגלל מסגרת האימות של הפלאגין, ה-AccountManager יכול לספק גישה לכל אסימון אימות שתומך בו האימות ובוחר לחשוף, כמו אסימוני אימות של OAuth2.

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

הטמעת מתאם סנכרון

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

רכיב Service שמגיב לבקשות מהמערכת לקישור למתאם הסנכרון.
כשהמערכת רוצה להריץ סנכרון, היא קוראת ל-method‏ onBind() של השירות כדי לקבל IBinder למתאם הסנכרון. כך המערכת יכולה לבצע קריאות בין תהליכים ל-methods של המתאם.
מתאם הסנכרון בפועל, שמיושם כסוג משנה קונקרטי של AbstractThreadedSyncAdapter.
הכיתה הזו מבצעת את הורדת הנתונים מהשרת, העלאת הנתונים מהמכשיר ופתרון של קונפליקטים. העבודה העיקרית של המתאם מתבצעת ב-method‏ onPerformSync(). צריך ליצור מופע של המחלקה הזו כ-singleton.
מחלקה משנית של Application.
הכיתה הזו משמשת כמפעל ליחידה היחידה של מתאם הסנכרון. משתמשים בשיטה onCreate() כדי ליצור מופע של מתאם הסנכרון, ומספקים שיטה סטטית מסוג 'getter' כדי להחזיר את ה-Singleton לשיטה onBind() של השירות של מתאם הסנכרון.
אופציונלי: רכיב Service שמגיב לבקשות מהמערכת לאימות משתמשים.
AccountManager מפעיל את השירות הזה כדי להתחיל את תהליך האימות. שיטת onCreate() של השירות יוצרת אובייקט של מאמת. כשהמערכת רוצה לאמת חשבון משתמש למתאמת הסנכרון של האפליקציה, היא קורא לשיטה onBind() של השירות כדי לקבל IBinder לאימות. כך המערכת יכולה לבצע קריאות חוצות-תהליכים לשיטות של מאמת.
אופציונלי: Subclass קונקרטי של AbstractAccountAuthenticator שמטפל בבקשות לאימות.
הכיתה הזו מספקת שיטות ש-AccountManager מפעיל כדי לאמת את פרטי הכניסה של המשתמש מול השרת. הפרטים של תהליך האימות משתנים מאוד בהתאם לטכנולוגיית השרת שבה נעשה שימוש. למידע נוסף על אימות, כדאי לעיין במסמכי העזרה של תוכנת השרת.
קובצי XML שמגדירים את מתאם הסנכרון ואת מאמת החשבון למערכת.
רכיבי השירות של מתאם הסנכרון ומאמת הזהות שתוארו למעלה מוגדרים ברכיבי <service> במניפסט של האפליקציה. הרכיבים האלה מכילים <meta-data> רכיבי צאצא שמספקים למערכת נתונים ספציפיים:
  • הרכיב <meta-data> של שירות מתאם הסנכרון מפנה לקובץ ה-XML res/xml/syncadapter.xml. קובץ זה מציין URI של שירות האינטרנט שיתוזמן עם ספק אנשי הקשר, וגם סוג חשבון לשירות האינטרנט.
  • אופציונלי: הרכיב <meta-data> של המאמת מפנה לקובץ ה-XML res/xml/authenticator.xml. הקובץ הזה מציין את סוג החשבון שתומך בו מאמת החשבונות, וגם משאבי ממשק משתמש שמופיעים במהלך תהליך האימות. סוג החשבון שצוין ברכיב הזה חייב להיות זהה לסוג החשבון שצוין למתאם הסנכרון.

נתונים של מקור נתונים ברשתות חברתיות

הטבלאות android.provider.ContactsContract.StreamItems ו-android.provider.ContactsContract.StreamItemPhotos מנהלות את הנתונים הנכנסים מרשתות חברתיות. אפשר לכתוב מתאם סנכרון שמוסיף לטבלאות האלה נתוני סטרימינג מהרשת שלכם, או לקרוא נתוני סטרימינג מהטבלאות האלה ולהציג אותם באפליקציה שלכם, או גם וגם. בעזרת התכונות האלה, תוכלו לשלב את האפליקציות והשירותים של הרשתות החברתיות בחוויית השימוש של Android ברשתות החברתיות.

טקסט של פיד ברשתות החברתיות

פריטים בסטרימינג תמיד משויכים לאיש קשר גולמי. השדה android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID מקשר לערך _ID של איש הקשר הגולמי. סוג החשבון ושם החשבון של איש הקשר הגולמי נשמרים גם בשורה של פריט הסטרימינג.

שומרים את הנתונים מהסטרימינג בעמודות הבאות:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
חובה. סוג החשבון של המשתמש בפרטי איש הקשר הגולמיים שמשויכים לפריט הזה בסטרימינג. חשוב לזכור להגדיר את הערך הזה כשמוסיפים פריט בסטרימינג.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
חובה. שם החשבון של המשתמש לאיש הקשר הגולמי שמשויך לפריט הסטרימינג הזה. חשוב לזכור להגדיר את הערך הזה כשמוסיפים פריט בסטרימינג.
עמודות מזהה
חובה. כשאתם מוסיפים פריט בסטרימינג, עליכם להוסיף את העמודות הבאות של המזהים:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: הערך של android.provider.BaseColumns#_ID של איש הקשר שאליו משויך פריט הפיד הזה.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: הערך של android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY של איש הקשר שאליו משויך פריט הסטרימינג הזה.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: הערך של android.provider.BaseColumns#_ID של איש הקשר הגולמי שאליו משויך פריט המקור הזה.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
אופציונלי. מאחסן מידע סיכום שאפשר להציג בתחילת פריט בסטרימינג.
android.provider.ContactsContract.StreamItemsColumns#TEXT
הטקסט של פריט הסטרימינג, כלומר התוכן שפורסם על ידי מקור הפריט, או תיאור של פעולה כלשהי שיצרה את פריט הסטרימינג. העמודה הזו יכולה להכיל כל עיצוב ותמונות מוטמעות של משאבים שאפשר להציג באמצעות fromHtml(). יכול להיות שהספק יקצר תוכן ארוך או ידגיש אותו בקו אליפטי, אבל הוא ינסה להימנע משבירת תגים.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
מחרוזת טקסט שמכילה את הזמן שבו פריט הסטרימינג הוכנס או עודכן, בפורמט של מיליאקונדות מאז תחילת הזמן. אפליקציות שמוסיפות או מעדכנות פריטים בסטרימינג אחראיות על ניהול העמודה הזו. היא לא מנוהלת באופן אוטומטי על ידי ספק אנשי הקשר.

כדי להציג מידע מזהה על הפריטים בסטרימינג, אפשר להשתמש ב-android.provider.ContactsContract.StreamItemsColumns#RES_ICON,‏ android.provider.ContactsContract.StreamItemsColumns#RES_LABEL ו-android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE כדי לקשר למשאבים באפליקציה.

הטבלה android.provider.ContactsContract.StreamItems מכילה גם את העמודות android.provider.ContactsContract.StreamItemsColumns#SYNC1 עד android.provider.ContactsContract.StreamItemsColumns#SYNC4 לשימוש בלעדי של מתאמי סנכרון.

תמונות מזרם העדכונים מהרשתות החברתיות

בטבלה android.provider.ContactsContract.StreamItemPhotos מאוחסנות תמונות שמשויכות לפריט במסך הבית. העמודה android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID בטבלה מקשרת לערכים בעמודה _ID בטבלה android.provider.ContactsContract.StreamItems. ההפניות לתמונות מאוחסנות בטבלה בעמודות הבאות:

העמודה android.provider.ContactsContract.StreamItemPhotos#PHOTO (BLOB).
ייצוג בינארי של התמונה, שהספק שינה את הגודל שלה לצורכי אחסון ותצוגה. העמודה הזו זמינה לצורך תאימות לאחור לגרסאות קודמות של ספק אנשי הקשר, שבהן היא שימשה לאחסון תמונות. עם זאת, בגרסה הנוכחית לא מומלץ להשתמש בעמודה הזו לאחסון תמונות. במקום זאת, צריך להשתמש ב-android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID או ב-android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (שניהם מתוארים בהמשך) כדי לאחסן תמונות בקובץ. העמודה הזו מכילה עכשיו תמונה ממוזערת של התמונה, שזמינה לקריאה.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
מזהה מספרי של תמונה של איש קשר בפורמט גולמי. מוסיפים את הערך הזה למשתנה הקבוע DisplayPhoto.CONTENT_URI כדי לקבל URI של תוכן שמצביע לקובץ תמונה יחיד, ואז קוראים ל-openAssetFileDescriptor() כדי לקבל מזהה לקובץ התמונה.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
URI של תוכן שמצביע ישירות לקובץ התמונה של התמונה שמיוצגת בשורה הזו. קוראים ל-openAssetFileDescriptor() עם ה-URI הזה כדי לקבל מזהה לקובץ התמונה.

שימוש בטבלאות של מקור הנתונים 'פעילות ברשתות החברתיות'

הטבלאות האלה פועלות כמו הטבלאות הראשיות האחרות בספק אנשי הקשר, חוץ מהעובדות הבאות:

  • בטבלאות האלה נדרשות הרשאות גישה נוספות. כדי לקרוא מהם, לאפליקציה צריכה להיות ההרשאה android.Manifest.permission#READ_SOCIAL_STREAM. כדי לשנות אותם, לאפליקציה צריכה להיות ההרשאה android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • בטבלה android.provider.ContactsContract.StreamItems, מספר השורות שנשמרות לכל איש קשר גולמי מוגבל. כשמגיעים למגבלה הזו, הספק של אנשי הקשר מפנה מקום לשורות חדשות של פריטים בסטרימינג על ידי מחיקה אוטומטית של השורות עם הערך הישן ביותר של android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP. כדי לקבל את המגבלה, שולחים שאילתה ל-URI של התוכן: android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. אפשר להשאיר את כל הארגומנטים מלבד URI התוכן מוגדרים ל-null. השאילתה מחזירה Cursor שמכיל שורה אחת עם העמודה היחידה android.provider.ContactsContract.StreamItems#MAX_ITEMS.

הכיתה android.provider.ContactsContract.StreamItems.StreamItemPhotos מגדירה טבלת משנה של android.provider.ContactsContract.StreamItemPhotos שמכילה את שורות התמונות של פריט אחד במקור החדשות.

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

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

  • כשמסנכרנים את שירות הרשת החברתית עם ספק אנשי הקשר באמצעות מתאם סנכרון, אפשר לאחזר את הפעילות האחרונה של אנשי הקשר של המשתמש ולאחסן אותה בטבלאות android.provider.ContactsContract.StreamItems ו-android.provider.ContactsContract.StreamItemPhotos לשימוש עתידי.
  • בנוסף לסנכרון הרגיל, אפשר להפעיל את מתאם הסנכרון כדי לאחזר נתונים נוספים כשהמשתמש בוחר איש קשר להצגה. כך מתאם הסנכרון יוכל לאחזר תמונות ברזולוציה גבוהה ואת הפריטים האחרונים שפורסמו בסטורי של איש הקשר.
  • אם תירשמו התראה באפליקציית אנשי הקשר של המכשיר ובספק אנשי הקשר, תוכלו לקבל כוונה כשאיש קשר מוצג, ובשלב הזה לעדכן את הסטטוס של איש הקשר מהשירות שלכם. הגישה הזו עשויה להיות מהירה יותר ולצרוך פחות רוחב פס מאשר סנכרון מלא באמצעות מתאם סנכרון.
  • המשתמשים יכולים להוסיף איש קשר לשירות הרשת החברתית שלכם כשהם צופים באיש הקשר באפליקציית אנשי הקשר של המכשיר. כדי להפעיל את התכונה הזו, צריך להשתמש בתכונה 'הזמנת איש קשר', שמפעילים באמצעות שילוב של פעילות שמוסיפה איש קשר קיים לרשת, וקובץ XML שמספק לאפליקציית אנשי הקשר של המכשיר ולספק אנשי הקשר את פרטי האפליקציה.

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

רישום לטיפול בבקשות לצפיות ברשתות חברתיות

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

  1. יוצרים קובץ בשם contacts.xml בתיקייה res/xml/ של הפרויקט. אם כבר יש לכם את הקובץ הזה, אתם יכולים לדלג על השלב הזה.
  2. בקובץ הזה, מוסיפים את הרכיב <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. אם הרכיב הזה כבר קיים, אפשר לדלג על השלב הזה.
  3. כדי לרשום שירות שיקבל התראה כשהמשתמש יפתח את דף הפרטים של איש קשר באפליקציית אנשי הקשר של המכשיר, מוסיפים את המאפיין viewContactNotifyService="serviceclass" לאלמנט, כאשר serviceclass הוא שם הכיתה המלא של השירות שצריך לקבל את הכוונה מאפליקציית אנשי הקשר של המכשיר. בשירות ההתראות, משתמשים בכיתה שמרחיבה את IntentService כדי לאפשר לשירות לקבל כוונות. הנתונים בכוונה הנכנסת מכילים את ה-URI של התוכן של פרטי הקשר הגולמיים שהמשתמש לחץ עליהם. משירות ההתראות, אפשר לקשר את מתאם הסנכרון ולאחר מכן להפעיל אותו כדי לעדכן את הנתונים של איש הקשר הגולמי.

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

  1. יוצרים קובץ בשם contacts.xml בתיקייה res/xml/ של הפרויקט. אם כבר יש לכם את הקובץ הזה, אתם יכולים לדלג על השלב הזה.
  2. בקובץ הזה, מוסיפים את הרכיב <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. אם הרכיב הזה כבר קיים, אפשר לדלג על השלב הזה.
  3. כדי לרשום אחת מהפעילויות שלכם לטפל במקרה שבו המשתמש לוחץ על פריט בסטרימינג באפליקציית אנשי הקשר של המכשיר, מוסיפים את המאפיין viewStreamItemActivity="activityclass" לאלמנט, כאשר activityclass הוא שם הכיתה המלא של הפעילות שאמורה לקבל את הכוונה מאפליקציית אנשי הקשר של המכשיר.
  4. כדי לרשום אחת מהפעילויות שלכם לטפל במקרה שבו המשתמש לוחץ על תמונה בסטרים באפליקציית אנשי הקשר של המכשיר, מוסיפים את המאפיין viewStreamItemPhotoActivity="activityclass" לאלמנט, כאשר activityclass הוא שם הכיתה המלא של הפעילות שאמורה לקבל את ה-Intent מאפליקציית אנשי הקשר של המכשיר.

רכיב <ContactsAccountType> מתואר בפירוט רב יותר בקטע הרכיב<ContactsAccountType>.

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

אינטראקציה עם שירות הרשת החברתית

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

  1. יוצרים קובץ בשם contacts.xml בתיקייה res/xml/ של הפרויקט. אם כבר יש לכם את הקובץ הזה, אתם יכולים לדלג על השלב הזה.
  2. בקובץ הזה, מוסיפים את הרכיב <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. אם הרכיב הזה כבר קיים, אפשר לדלג על השלב הזה.
  3. מוסיפים את המאפיינים הבאים:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    הערך של activityclass הוא שם המחלקה המלא של הפעילות שאמורה לקבל את הכוונה. הערך של invite_action_label הוא מחרוזת טקסט שמוצגת בתפריט Add Connection באפליקציית אנשי הקשר של המכשיר.

הערה: ContactsSource הוא שם תג שהוצא משימוש עבור ContactsAccountType.

מסמך עזר בנושא contacts.xml

הקובץ contacts.xml מכיל רכיבי XML ששולטים באינטראקציה של מתאם הסנכרון והאפליקציה עם אפליקציית אנשי הקשר ועם ספק אנשי הקשר. הרכיבים האלה מתוארים בקטעים הבאים.

אלמנט <ContactsAccountType>

האלמנט <ContactsAccountType> קובע את האינטראקציה של האפליקציה שלכם עם אפליקציית אנשי הקשר. התחביר שלו הוא:

<ContactsAccountType
        xmlns:android="http://schemas.android.com/apk/res/android"
        inviteContactActivity="activity_name"
        inviteContactActionLabel="invite_command_text"
        viewContactNotifyService="view_notify_service"
        viewGroupActivity="group_view_activity"
        viewGroupActionLabel="group_action_text"
        viewStreamItemActivity="viewstream_activity_name"
        viewStreamItemPhotoActivity="viewphotostream_activity_name">

נכלל ב:

res/xml/contacts.xml

יכולים להכיל:

<ContactsDataKind>

תיאור:

הקוד מכריז על רכיבי Android ותוויות של ממשק משתמש שמאפשרים למשתמשים להזמין אחד מאנשי הקשר שלהם לרשת חברתית, להודיע למשתמשים כשאחד מהפידים שלהם ברשת החברתית מתעדכן וכו'.

שימו לב שקידומת המאפיין android: לא נדרשת למאפיינים של <ContactsAccountType>.

מאפיינים:

inviteContactActivity
שם הכיתה המלא של הפעילות באפליקציה שרוצים להפעיל כשהמשתמש בוחר באפשרות Add connection באפליקציית אנשי הקשר של המכשיר.
inviteContactActionLabel
מחרוזת טקסט שמוצגת לפעילות שצוינה ב-inviteContactActivity, בתפריט Add connection. לדוגמה, אפשר להשתמש במחרוזת 'לעקוב ברשת שלי'. אפשר להשתמש במזהה של משאב מחרוזת בתווית הזו.
viewContactNotifyService
שם הכיתה המלא של שירות באפליקציה שאמור לקבל התראות כשהמשתמש מציג איש קשר. ההתראה הזו נשלחת על ידי אפליקציית אנשי הקשר במכשיר, והיא מאפשרת לאפליקציה לדחות פעולות שמתבצעות באמצעות שימוש רב בנתונים עד שהן נדרשות. לדוגמה, האפליקציה יכולה להגיב להתראה הזו על ידי קריאה והצגה של התמונה באיכות גבוהה של איש הקשר ושל הפריטים האחרונים בסטטוס שלו ברשתות החברתיות. תיאור מפורט יותר של התכונה הזו זמין בקטע אינטראקציות בסטרים של רשתות חברתיות.
viewGroupActivity
שם הכיתה המלא של פעילות באפליקציה שיכולה להציג מידע על קבוצות. כשהמשתמש לוחץ על תווית הקבוצה באפליקציית אנשי הקשר במכשיר, מוצג ממשק המשתמש של הפעילות הזו.
viewGroupActionLabel
התווית שמוצגת באפליקציית אנשי הקשר עבור אמצעי בקרה בממשק המשתמש שמאפשר למשתמש להציג קבוצות באפליקציה.

אפשר להשתמש במזהה משאב של מחרוזת במאפיין הזה.

viewStreamItemActivity
שם הכיתה המלא של פעילות באפליקציה, שאפליקציית אנשי הקשר במכשיר מפעילה כשהמשתמש לוחץ על פריט בסטרימינג של איש קשר גולמי.
viewStreamItemPhotoActivity
שם הכיתה המלא של פעילות באפליקציה שלכם, שאפליקציית אנשי הקשר במכשיר מפעילה כשהמשתמש לוחץ על תמונה בפריט בסטרימינג של איש קשר גולמי.

האלמנט <ContactsDataKind>

הרכיב <ContactsDataKind> קובע את הצגת שורות הנתונים בהתאמה אישית של האפליקציה בממשק המשתמש של אפליקציית אנשי הקשר. התחביר שלו הוא:

<ContactsDataKind
        android:mimeType="MIMEtype"
        android:icon="icon_resources"
        android:summaryColumn="column_name"
        android:detailColumn="column_name">

נכלל ב:

<ContactsAccountType>

תיאור:

אפשר להשתמש ברכיב הזה כדי להציג באפליקציית אנשי הקשר את התוכן של שורת נתונים בהתאמה אישית כחלק מהפרטים של איש קשר גולמי. כל רכיב צאצא של <ContactsDataKind> ב-<ContactsAccountType> מייצג סוג של שורת נתונים בהתאמה אישית שהמתאם לסנכרון מוסיף לטבלה ContactsContract.Data. מוסיפים רכיב <ContactsDataKind> אחד לכל סוג MIME מותאם אישית שבו אתם משתמשים. אין צורך להוסיף את הרכיב אם יש לכם שורת נתונים מותאמת אישית שאתם לא רוצים להציג בה נתונים.

מאפיינים:

android:mimeType
סוג ה-MIME המותאם אישית שהגדרתם לאחד מסוגי השורות של הנתונים בהתאמה אישית בטבלה ContactsContract.Data. לדוגמה, הערך vnd.android.cursor.item/vnd.example.locationstatus יכול להיות סוג MIME בהתאמה אישית של שורת נתונים שמתעדת את המיקום האחרון הידוע של איש הקשר.
android:icon
משאב drawable ב-Android שמוצג באפליקציית אנשי הקשר לצד הנתונים שלכם. אפשר להשתמש בו כדי להצביע למשתמש שהנתונים מגיעים מהשירות שלכם.
android:summaryColumn
שם העמודה של הערך הראשון מבין שני הערכים שאוחזרו משורת הנתונים. הערך מוצג בשורה הראשונה של הרשומה בשורת הנתונים הזו. השורה הראשונה מיועדת לסיכום הנתונים, אבל היא אופציונלית. אפשר לעיין גם במאמר android:detailColumn.
android:detailColumn
שם העמודה של הערך השני מתוך שני ערכים שאוחזרו משורת הנתונים. הערך מוצג בשורה השנייה של הרשומה בשורת הנתונים הזו. למידע נוסף, ראו גם android:summaryColumn.

תכונות נוספות של 'ניהול אנשי הקשר'

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

  • קבוצות של אנשי קשר
  • תכונות שקשורות לתמונות

קבוצות של אנשי קשר

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

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

התמונות של אנשי הקשר

בטבלה ContactsContract.Data תמונות מאוחסנות בשורות עם סוג MIME‏ Photo.CONTENT_ITEM_TYPE. העמודה CONTACT_ID של השורה מקושרת לעמודה _ID של איש הקשר הגולמי שאליו היא שייכת. הכיתה ContactsContract.Contacts.Photo מגדירה טבלת משנה של ContactsContract.Contacts שמכילה את פרטי התמונה של תמונת הפרופיל הראשית של איש הקשר, שהיא תמונת הפרופיל הראשית של איש הקשר הגולמי הראשי. באופן דומה, הכיתה ContactsContract.RawContacts.DisplayPhoto מגדירה טבלת משנה של ContactsContract.RawContacts שמכילה את פרטי התמונה של התמונה הראשית של איש הקשר הגולמי.

במסמכי העזרה של ContactsContract.Contacts.Photo ו-ContactsContract.RawContacts.DisplayPhoto יש דוגמאות לאחזור פרטי תמונות. אין סיווג נוח לאחזור התמונה הממוזערת הראשית של איש קשר בפורמט גולמי, אבל אפשר לשלוח שאילתה לטבלה ContactsContract.Data, ולבחור את העמודות _ID, Photo.CONTENT_ITEM_TYPE ו-IS_PRIMARY של איש הקשר בפורמט גולמי כדי למצוא את השורה של התמונה הראשית של איש הקשר בפורמט גולמי.

נתוני מקור הנתונים של אדם מסוים עשויים לכלול גם תמונות. התמונות האלה מאוחסנות בטבלה android.provider.ContactsContract.StreamItemPhotos, שמתוארת בפירוט בקטע תמונות מהעדכונים של החברים ברשתות החברתיות.