استرداد تفاصيل جهة اتصال

يوضّح هذا الدرس كيفية استرداد بيانات تفصيلية لجهة اتصال، مثل عناوين البريد الإلكتروني وأرقام الهواتف وما إلى ذلك. وهي التفاصيل التي يبحث عنها المستخدمون عند استرداد جهة اتصال. يمكنك منحها جميع التفاصيل الخاصة بجهة اتصال، أو عرض تفاصيل من نوع معيّن فقط، مثل عناوين البريد الإلكتروني.

تفترض الخطوات الواردة في هذا الدرس أنّ لديك صفًا في ContactsContract.Contacts لجهة اتصال اختارها المستخدم. يوضّح درس استرداد أسماء جهات الاتصال كيفية استرداد قائمة بجهات الاتصال.

استرداد جميع تفاصيل جهة اتصال

لاسترداد جميع التفاصيل الخاصة بجهة اتصال، ابحث في جدول ContactsContract.Data عن أي صفوف تحتوي على LOOKUP_KEY لجهة الاتصال. يتوفّر هذا العمود في جدول ContactsContract.Data، لأنّ "موفّر جهات الاتصال" ينشئ ربطًا ضمنيًا بين جدول ContactsContract.Contacts وجدول ContactsContract.Data. يتم وصف عمود LOOKUP_KEY بمزيد من التفاصيل في درس استرداد أسماء جهات الاتصال.

ملاحظة: يؤدي استرداد جميع التفاصيل لجهة اتصال إلى تقليل أداء الجهاز، لأنه يحتاج إلى استرداد جميع الأعمدة في الجدول ContactsContract.Data. ننصحك بالتفكير في تأثير الأداء قبل استخدام هذه التقنية.

طلب الحصول على الأذونات

للقراءة من "مقدِّم جهات الاتصال"، يجب أن يحصل تطبيقك على إذن 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 Injection الضارة.

تحديد نظام الترتيب

حدِّد نظام الترتيب الذي تريده في 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

يجب دائمًا إجراء عمليات الاسترجاع من &quot;مقدِّم جهات الاتصال&quot; (وجميع أدوات توفير المحتوى الأخرى) في سلسلة تعليمات في الخلفية. استخدِم إطار عمل 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 من خلال هذه الطريقة. بما أنّك تبحث في جدول 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 من خلال ضبطها على قيمة فارغة. وإذا لم تفعل ذلك، لن يتلف إطار عمل أداة التحميل 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 بين علامات اقتباس مفردة من خلال دمج حرف "'" (علامة اقتباس مفردة) في بداية الثابت ونهايته، وإلا سيفسّر الموفّر الثابت على أنّه اسم متغيّر وليس قيمة سلسلة. لست بحاجة إلى استخدام عنصر نائب لهذه القيمة، لأنك تستخدم قيمة ثابتة بدلاً من قيمة يوفّرها المستخدم. على سبيل المثال:

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 ";