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

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

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

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

لاسترداد جميع تفاصيل جهة اتصال، ابحث في جدول ContactsContract.Data عن أي صفوف تحتوي على LOOKUP_KEY جهة الاتصال. يتوفّر هذا العمود في جدول ContactsContract.Data، لأنّ موفِّر 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 ضار.

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

حدِّد ترتيب الترتيب الذي تريده في 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 الذي تحدّده فئة 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 من خلال ضبطها على القيمة null. وفي حال عدم تنفيذ ذلك، لن يُزيل إطار عمل أداة التحميل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 ";