ดึงข้อมูลรายชื่อติดต่อ

บทเรียนนี้แสดงวิธีเรียกข้อมูลรายละเอียดของรายชื่อติดต่อ เช่น อีเมล หมายเลขโทรศัพท์ ตัวเลข และอื่นๆ ซึ่งเป็นรายละเอียดที่ผู้ใช้ต้องการเมื่อดึงข้อมูลรายชื่อติดต่อ คุณสามารถระบุรายละเอียดทั้งหมดของผู้ติดต่อ หรือแสดงเฉพาะรายละเอียดบางประเภท เช่น อีเมล

ขั้นตอนในบทเรียนนี้ถือว่าคุณมีแถว ContactsContract.Contacts ของรายชื่อติดต่อที่ผู้ใช้เลือกไว้แล้ว บทเรียนการเรียกดูชื่อผู้ติดต่อจะแสดงวิธีการ เรียกดูรายชื่อผู้ติดต่อ

เรียกดูรายละเอียดทั้งหมดของรายชื่อติดต่อ

หากต้องการเรียกดูรายละเอียดทั้งหมดของผู้ติดต่อ ให้ค้นหา ตาราง ContactsContract.Data สำหรับแถวใดก็ตามที่มีของ LOOKUP_KEY คอลัมน์นี้พร้อมใช้งานในตาราง 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 ที่กําหนดโดยคลาส 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 ";