บทเรียนนี้จะแสดงวิธีเรียกข้อมูลรายละเอียดของผู้ติดต่อ เช่น อีเมล หมายเลขโทรศัพท์ และอื่นๆ ซึ่งก็คือรายละเอียดที่ผู้ใช้ต้องการเมื่อเรียกข้อมูลรายชื่อติดต่อ คุณสามารถระบุรายละเอียดทั้งหมดของผู้ติดต่อ หรือแสดงเฉพาะรายละเอียดบางประเภท เช่น อีเมล
ขั้นตอนในบทเรียนนี้จะถือว่าคุณมีแถว ContactsContract.Contacts
สำหรับรายชื่อติดต่อที่ผู้ใช้เลือกไว้แล้ว
บทเรียนการดึงข้อมูลชื่อผู้ติดต่อจะแสดงวิธีดึงข้อมูลรายชื่อติดต่อ
เรียกดูรายละเอียดทั้งหมดของรายชื่อติดต่อ
หากต้องการดึงรายละเอียดทั้งหมดของรายชื่อติดต่อ ให้ค้นหาแถวที่มีLOOKUP_KEY
ของรายชื่อติดต่อในตารางContactsContract.Data
คอลัมน์นี้พร้อมใช้งานในตาราง ContactsContract.Data
เนื่องจากผู้ให้บริการรายชื่อติดต่อทำการรวมข้อมูลโดยนัยระหว่างตาราง ContactsContract.Contacts
กับตาราง ContactsContract.Data
เราจะอธิบายคอลัมน์ LOOKUP_KEY
โดยละเอียดในบทเรียนการดึงข้อมูลรายชื่อติดต่อ
หมายเหตุ: การดึงรายละเอียดทั้งหมดของรายชื่อติดต่อจะลดประสิทธิภาพของอุปกรณ์ เนื่องจากต้องดึงข้อมูลทุกคอลัมน์ในตาราง ContactsContract.Data
โปรดพิจารณาผลกระทบด้านประสิทธิภาพก่อนใช้เทคนิคนี้
ขอสิทธิ์
หากต้องการอ่านจากผู้ให้บริการรายชื่อติดต่อ แอปของคุณต้องมีสิทธิ์ READ_CONTACTS
หากต้องการขอสิทธิ์นี้ ให้เพิ่มองค์ประกอบย่อยต่อไปนี้ของ
<manifest>
ลงในไฟล์ 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
ดึงข้อมูลจากผู้ให้บริการ Contacts (และผู้ให้บริการเนื้อหารายอื่นๆ ทั้งหมด) ในเธรดเบื้องหลังเสมอ ใช้เฟรมเวิร์ก 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 ";