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

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

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

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

המדריך הזה מיועד למי שמכיר את העקרונות הבסיסיים של ספקי תוכן ב-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@gmail.com
  • emilyd@gmail.com
  • חשבון טוויטר belle_of_amherst

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

נניח שאמילי דיקינסון פותחת חלון דפדפן, נכנסת ל-Gmail בתור emily.dickinson@gmail.com, פותחת את אנשי הקשר ומוסיפה את 'תומס היגינסון'. בהמשך, היא מתחברת ל-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. סוג חשבון המשתמש הוא טוויטר.

נתונים

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

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

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

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

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

קטע הקוד הזה נלקח מהפעילות detail:

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

שינוי של כמה תמונות בבת אחת

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

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

נקודות עצירה

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

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

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

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

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

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

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

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

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

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

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());

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

כל אובייקט של operation 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. מחילים את העסקה הקבוצתית.

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

בקטע הקוד הבא אפשר לראות איך ליצור טענת נכוֹנוּת (assertion) 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
    }

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

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

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

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

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

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

טבלה 4. ה-Intents של ניהול אנשי הקשר.

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

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

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

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

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

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

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 מספקת מסגרת סנכרון של תוסף שמבצעת את המשימות הבאות באופן אוטומטי:

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

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

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

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

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

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

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

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

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

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

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

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

נתונים ב-Social stream

הטבלאות 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 column (a 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 לשימוש מאוחר יותר.
  • בנוסף לסנכרון רגיל, אפשר להפעיל את מתאם הסנכרון כדי לאחזר נתונים נוספים כשהמשתמש בוחר איש קשר לצפייה. כך מתאם הסנכרון יכול לאחזר תמונות ברזולוציה גבוהה ופריטים עדכניים מהפיד של איש הקשר.
  • אם רושמים התראה באפליקציה לניהול אנשי הקשר של המכשיר ובניהול אנשי הקשר, אפשר לקבל Intent כשמציגים איש קשר, ובשלב הזה לעדכן את הסטטוס של איש הקשר מהשירות. הגישה הזו עשויה להיות מהירה יותר ולצרוך פחות רוחב פס מאשר סנכרון מלא באמצעות מתאם סנכרון.
  • המשתמשים יכולים להוסיף איש קשר לשירות הרשתות החברתיות שלכם בזמן שהם מסתכלים על איש הקשר באפליקציה לניהול אנשי הקשר של המכשיר. ההפעלה מתבצעת באמצעות התכונה 'הזמנת איש קשר', שמופעלת באמצעות שילוב של פעילות שמוסיפה איש קשר קיים לרשת שלכם, וקובץ XML שמספק לאפליקציה לניהול אנשי הקשר של המכשיר ולניהול אנשי הקשר את הפרטים של האפליקציה שלכם.

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

הרשמה לטיפול בתצוגות של רשתות חברתיות

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

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

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

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

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

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

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

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

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

הערה: 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
השם המלא של המחלקה של הפעילות באפליקציה שרוצים להפעיל כשמשתמש בוחר באפשרות הוספת חיבור מאפליקציה לניהול אנשי הקשר במכשיר.
inviteContactActionLabel
מחרוזת טקסט שמוצגת לפעילות שצוינה ב-inviteContactActivity, בתפריט הוספת חיבור. לדוגמה, אפשר להשתמש במחרוזת 'עקוב אחרי ברשת שלי'. אפשר להשתמש במזהה של משאב מחרוזת בשביל התווית הזו.
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
פריט גרפי שניתן להזזה של Android שהאפליקציה לניהול אנשי הקשר מציגה לצד הנתונים שלכם. הפרמטר הזה משמש כדי לציין למשתמש שהנתונים מגיעים מהשירות שלכם.
android:summaryColumn
שם העמודה של הראשון מבין שני הערכים שאוחזרו משורת הנתונים. הערך מוצג כשורה הראשונה של הרשומה בשורת הנתונים הזו. השורה הראשונה מיועדת לסיכום הנתונים, אבל היא אופציונלית. ראו גם android:detailColumn.
android:detailColumn
שם העמודה של הערך השני מתוך שני ערכים שאוחזרו משורת הנתונים. הערך מוצג כשורה השנייה של הרשומה בשורת הנתונים הזו. מידע נוסף זמין במאמר בנושא android:summaryColumn.

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

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

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

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

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

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

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

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