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

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

تفترض الخطوات الواردة في هذا الدرس أنّه لديك حاليًا صف 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 (لغة الاستعلام البنيوية) بشكل ضار.

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

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