אחזור פרטים של איש קשר

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

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

אחזור כל הפרטים של איש קשר

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

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

בקשת הרשאות

כדי לקרוא מ-Contacts Provider, לאפליקציה צריכה להיות ההרשאה READ_CONTACTS. כדי לבקש את ההרשאה הזו, מוסיפים לקובץ המניפסט את רכיב הצאצא הבא של <manifest>:

    <uses-permission android:name="android.permission.READ_CONTACTS" />

הגדרת הקרנה

בהתאם לסוג הנתונים ששורה מכילה, יכול להיות שהיא תשתמש רק בכמה עמודות או בהרבה עמודות. בנוסף, הנתונים מופיעים בעמודות שונות בהתאם לסוג הנתונים. כדי להבטיח שתקבלו את כל העמודות האפשריות לכל סוגי הנתונים האפשריים, צריך להוסיף את כל שמות העמודות להקרנה. צריך לאחזר תמיד את Data._ID אם מקשרים את התוצאה Cursor ל-ListView. אחרת, הקישור לא יפעל. אפשר גם לאחזר את השדה Data.MIMETYPE כדי לזהות את סוג הנתונים של כל שורה שאוחזרה. לדוגמה:

Kotlin

private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Data._ID,
        ContactsContract.Data.MIMETYPE,
        ContactsContract.Data.DATA1,
        ContactsContract.Data.DATA2,
        ContactsContract.Data.DATA3,
        ContactsContract.Data.DATA4,
        ContactsContract.Data.DATA5,
        ContactsContract.Data.DATA6,
        ContactsContract.Data.DATA7,
        ContactsContract.Data.DATA8,
        ContactsContract.Data.DATA9,
        ContactsContract.Data.DATA10,
        ContactsContract.Data.DATA11,
        ContactsContract.Data.DATA12,
        ContactsContract.Data.DATA13,
        ContactsContract.Data.DATA14,
        ContactsContract.Data.DATA15
)

Java

    private static final String[] PROJECTION =
            {
                ContactsContract.Data._ID,
                ContactsContract.Data.MIMETYPE,
                ContactsContract.Data.DATA1,
                ContactsContract.Data.DATA2,
                ContactsContract.Data.DATA3,
                ContactsContract.Data.DATA4,
                ContactsContract.Data.DATA5,
                ContactsContract.Data.DATA6,
                ContactsContract.Data.DATA7,
                ContactsContract.Data.DATA8,
                ContactsContract.Data.DATA9,
                ContactsContract.Data.DATA10,
                ContactsContract.Data.DATA11,
                ContactsContract.Data.DATA12,
                ContactsContract.Data.DATA13,
                ContactsContract.Data.DATA14,
                ContactsContract.Data.DATA15
            };

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

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

להגדיר את הקריטריונים לבחירה

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

Kotlin

// Defines the selection clause
private const val SELECTION: String = "${ContactsContract.Data.LOOKUP_KEY} = ?"
...
// Defines the array to hold the search criteria
private val selectionArgs: Array<String> = arrayOf("")
/*
 * Defines a variable to contain the selection value. Once you
 * have the Cursor from the Contacts table, and you've selected
 * the desired row, move the row's LOOKUP_KEY value into this
 * variable.
 */
private var lookupKey: String? = null

Java

    // Defines the selection clause
    private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
    // Defines the array to hold the search criteria
    private String[] selectionArgs = { "" };
    /*
     * Defines a variable to contain the selection value. Once you
     * have the Cursor from the Contacts table, and you've selected
     * the desired row, move the row's LOOKUP_KEY value into this
     * variable.
     */
    private lateinit var lookupKey: String

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

הגדרת סדר המיון

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

Kotlin

/*
 * Defines a string that specifies a sort order of MIME type
 */
private const val SORT_ORDER = ContactsContract.Data.MIMETYPE

Java

    /*
     * Defines a string that specifies a sort order of MIME type
     */
    private static final String SORT_ORDER = ContactsContract.Data.MIMETYPE;

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

איך מאתחלים את ה-Loader

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

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

קטע הקוד הבא מראה איך לאתחל את מסגרת הטעינה:

Kotlin

// Defines a constant that identifies the loader
private const val DETAILS_QUERY_ID: Int = 0

class DetailsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Initializes the loader framework
        loaderManager.initLoader(DETAILS_QUERY_ID, null, this)

Java

public class DetailsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Defines a constant that identifies the loader
    static int DETAILS_QUERY_ID = 0;
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        // Initializes the loader framework
        getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);

הטמעת onCreateLoader()‎

מטמיעים את השיטה onCreateLoader(), שנקראת על ידי מסגרת הטעינה מיד אחרי שמפעילים את initLoader(). מחזירים CursorLoader מה-method הזה. מכיוון שאתם מחפשים את הטבלה ContactsContract.Data, כדאי להשתמש בקבוע Data.CONTENT_URI בתור URI של התוכן. לדוגמה:

Kotlin

override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
    // Choose the proper action
    mLoader = when(loaderId) {
        DETAILS_QUERY_ID -> {
            // Assigns the selection parameter
            selectionArgs[0] = lookupKey
            // Starts the query
            activity?.let {
                CursorLoader(
                        it,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        SORT_ORDER
                )
            }
        }
        ...
    }
    return mLoader
}

Java

@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // Choose the proper action
        switch (loaderId) {
            case DETAILS_QUERY_ID:
            // Assigns the selection parameter
            selectionArgs[0] = lookupKey;
            // Starts the query
            CursorLoader mLoader =
                    new CursorLoader(
                            getActivity(),
                            ContactsContract.Data.CONTENT_URI,
                            PROJECTION,
                            SELECTION,
                            selectionArgs,
                            SORT_ORDER
                    );
    }

הטמעת onLoadFinished() ו-onLoaderReset()

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

Kotlin

    override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
        when(loader.id) {
            DETAILS_QUERY_ID -> {
                /*
                 * Process the resulting Cursor here.
                 */
            }
            ...
        }
    }

Java

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                    /*
                     * Process the resulting Cursor here.
                     */
                }
                break;
            ...
        }
    }

השיטה onLoaderReset() מופעלת כשמסגרת הטעינה מזהה שהנתונים שמשמשים כבסיס לתוצאה Cursor השתנו. בשלב הזה, מסירים את כל ההפניות הקיימות ל-Cursor על ידי הגדרתן כ-null. אם לא תעשו זאת, פלטפורמת ה-framework לא תשמיד את Cursor הישן, ותקבלו דליפת זיכרון. לדוגמה:

Kotlin

    override fun onLoaderReset(loader: Loader<Cursor>) {
        when (loader.id) {
            DETAILS_QUERY_ID -> {
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
            }
            ...
        }
    }

Java

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
                }
                break;
    }

אחזור פרטים ספציפיים של איש קשר

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

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

השינויים האלה מתוארים בסעיפים הבאים.

הגדרת הקרנה

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

Kotlin

private val PROJECTION: Array<String> = arrayOf(
        ContactsContract.CommonDataKinds.Email._ID,
        ContactsContract.CommonDataKinds.Email.ADDRESS,
        ContactsContract.CommonDataKinds.Email.TYPE,
        ContactsContract.CommonDataKinds.Email.LABEL
)

Java

    private static final String[] PROJECTION =
            {
                ContactsContract.CommonDataKinds.Email._ID,
                ContactsContract.CommonDataKinds.Email.ADDRESS,
                ContactsContract.CommonDataKinds.Email.TYPE,
                ContactsContract.CommonDataKinds.Email.LABEL
            };

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

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

הגדרת קריטריונים לבחירה

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

Kotlin

/*
 * Defines the selection clause. Search for a lookup key
 * and the Email MIME type
 */
private const val SELECTION =
        "${ContactsContract.Data.LOOKUP_KEY} = ? AND " +
        "${ContactsContract.Data.MIMETYPE} = '${Email.CONTENT_ITEM_TYPE}'"
...
// Defines the array to hold the search criteria
private val selectionArgs: Array<String> = arrayOf("")

Java

    /*
     * Defines the selection clause. Search for a lookup key
     * and the Email MIME type
     */
    private static final String SELECTION =
            Data.LOOKUP_KEY + " = ?" +
            " AND " +
            Data.MIMETYPE + " = " +
            "'" + Email.CONTENT_ITEM_TYPE + "'";
    // Defines the array to hold the search criteria
    private String[] selectionArgs = { "" };

הגדרת סדר מיון

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

Kotlin

private const val SORT_ORDER: String = "${Email.TYPE} ASC"

Java

    private static final String SORT_ORDER = Email.TYPE + " ASC ";