שילוב אנשי קשר (ContactsProvider) באפליקציית אנדרואיד
לקוח פנה אלינו עם אפליקציה שקרסה בעת טעינת רשימה של 600 אנשי קשר. הניתוח חשף שאילתה מקוננת ל-Contacts + Phone, שגרמה ל-ANR במכשירים ישנים יותר. ייעלנו את הקוד על ידי מעבר לשאילתה אחת דרך Data URI, וקיצרנו את זמן הטעינה מ-3.5 שניות ל-190 אלפיות השנייה. הלקוח חסך 40% מזמן הפיתוח ונמנע מביקורות שליליות. מצב זה אופייני לפרויקטים שמתעלמים מהמבנה התלת-שכבתי של אנשי קשר באנדרואיד: RawContacts (רשומה מחשבון ספציפי—Google, Telegram, טלפון), Contacts (צירוף של מספר RawContacts), ו-Data (שדות ספציפיים: טלפון, אימייל, תמונה). רוב המפתחים עובדים רק עם הטבלאות Contacts ו-Data, מתעלמים מהצירוף—ובסופו של דבר נתקלים בכפילויות. אנו מציעים אינטגרציה סוהר עם הבטחות ביצועים ואבטחה. הניסיון שלנו: 5+ שנים בפיתוח אנדרואיד ולמעלה מ-100 פרויקטים של ניהול אנשי קשר. קבלו ייעוץ—צרו קשר כדי להעריך את הפרויקט שלכם.
אילו הרשאות נדרשות וכיצד לבקש אותן כראוי?
READ_CONTACTS ו-WRITE_CONTACTS הן הרשאות מסוכנות ויש לבקש אותן בזמן ריצה. ב-Android 11+, נוצר ניואנס: אם המשתמש דוחה את ההרשאה פעמיים, קריאה חוזרת ל-ActivityCompat.requestPermissions() לא תפתח דיאלוג—יש להפנות את המשתמש להגדרות האפליקציה דרך ACTION_APPLICATION_DETAILS_SETTINGS. הניסיון שלנו מראה שטיפול נכון בתרחיש זה מגדיל את שיעור ההמרה של מתן הרשאות ב-40%. ב-Android 13+ (API 33), מופיע דגל לגישה סלקטיבית—המשתמש יכול להעניק גישה רק לחלק מאנשי הקשר. האפליקציה חייבת לעבוד כראוי עם מגבלה זו ולא לצפות לרשימה המלאה.
כיצד להימנע מכפילויות בעבודה עם ContactsProvider?
כפילויות נוצרות כאשר לא משתמשים ב-RawContacts. תמיד צרו RawContact תחילה, ולאחר מכן קשרו אליו נתונים באמצעות withValueBackReference. דוגמה:
val ops = ArrayList<ContentProviderOperation>()
ops.add(
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Иван Петров")
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "+7 999 123 45 67")
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.build()
)
context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)val ops = ArrayList<ContentProviderOperation>() ops.add( ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null) .build() ) ops.add( ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Иван Петров") .build() ) ops.add( ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "+7 999 123 45 67") .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE) .build() ) context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) היא פעולה אטומית. אם שלב אחד נכשל, כל העסקה מתבטלת. applyBatch מתייחס ל-withValueBackReference(RAW_CONTACT_ID, 0) מתוך התוצאה של הפעולה הראשונה באצווה.
גישת ביצועים דרך Data URI
במקום שאילתה כפולה, השתמשו בשאילתה אחת ל-_ID עם מסנן MIMETYPE:
val dataCursor = context.contentResolver.query(
ContactsContract.Data.CONTENT_URI,
arrayOf(
ContactsContract.Data.CONTACT_ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
),
"${ContactsContract.Data.MIMETYPE} = ?",
arrayOf(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
"${ContactsContract.Data.DISPLAY_NAME} ASC"
) | שיטה | מספר שאילתות | זמן טעינה עבור 1200 אנשי קשר | סיכון ANR |
|---|---|---|---|
| נאיבית (שתי שאילתות) | 1 + N | 3.2 שניות | גבוה |
| מותאמת (Data URI) | 1 | 180 אלפיות השנייה | נמוך |
בפרויקט אמיתי עם 1200 אנשי קשר, זה קיצר את זמן הטעינה מ-3.2 שניות ל-180 אלפיות השנייה—שיפור של פי 17. אנו מבטיחים גישה זו באפליקציה שלכם.
למה להשתמש ב-Data URI במקום שאילתה כפולה?
כי ברשימה של 500+ אנשי קשר, שאילתה כפולה מקוננת גורמת ל-ANR במכשירים חלשים אם היא מבוצעת על ה-main thread. הפתרון הוא ContactsContract.Data.CONTENT_URI או MIMETYPE עם val dataCursor = context.contentResolver.query( ContactsContract.Data.CONTENT_URI, arrayOf( ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER ), "${ContactsContract.Data.MIMETYPE} = ?", arrayOf(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE), "${ContactsContract.Data.DISPLAY_NAME} ASC" ) . בפרויקטים שלנו, אנו תמיד משתמשים בשאילתה אחת ועיבוד אסינכרוני, מה שמונע הקפאות אפילו עם 2000 אנשי קשר. השוו: הגישה המותאמת יעילה פי 20 בזמן תגובה.
ניואנסים שנשכחים לעיתים קרובות
תמונות אנשי קשר דורשות שאילתה נפרדת דרך CursorLoader. אל תטענו תמונות בשאילתה הראשית—זה משפיע מאוד על הזיכרון ברשימות גדולות. שמרו URI של תמונות במסד הנתונים המקומי שלכם.
תוכנית שלב-אחר-שלב לאינטגרציית ContactsProvider
1. בקשו READ_CONTACTS ו-WRITE_CONTACTS עם טיפול בסירוב. 2. עיצבו את מודל הנתונים: החליטו על סוגי MIME ומבנה RawContacts. 3. הטמיעו קריאה דרך Data URI עם שאילתה אחת. 4. הטמיעו כתיבה דרך applyBatch עם withValueBackReference. 5. הוסיפו שמירת תמונות במטמון וניהול URI. 6. כתבו בדיקות יחידה ובדקו על מכשירים עם גרסאות אנדרואיד שונות.התהליך שלנו: מביקורת ועד פריסה
| שלב | משך | תוצאה |
|---|---|---|
| ניתוח דרישות | 0.5 יום | מפרט טכני, הערכת עלות |
| עיצוב ארכיטקטורה | 0.5 יום | תיעוד, בחירת גישה |
| הטמעת CRUD | יומיים | קוד עם בדיקות יחידה |
| אינטגרציה ובדיקות | יום אחד | דוח בדיקות |
| פריסה ותמיכה | יום אחד | גישה ל-Google Play / הפצה פנימית |
מה כלול בעבודה
- ביקורת על היישום הנוכחי של ContactsProvider שלכם (אם קיים)
- עיצוב גישה מאובטחת בהתחשב בגרסאות אנדרואיד חדשות
- הטמעת קריאה, כתיבה, עדכון ומחיקה של אנשי קשר
- טיפול בהרשאות עם גיבוי להגדרות
- בדיקות יחידה לפעולות ContentResolver
- תיעוד אינטגרציה
- חודש אחד של תמיכה לאחר השחרור
ציר זמן: יומיים עד 5 ימים. העלות מחושבת באופן אישי. הזמינו ביקורת על האפליקציה שלכם—צרו קשר כדי לדון בפרטים. קבלו ייעוץ היום.







