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