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

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

تفترض الخطوات في هذا الدرس أنّ لديك صفًا 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(). نقْل معرّف عددي إلى الطريقة، ويتمّ نقل هذا المعرّف إلى methods 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 ";