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

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

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

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

หากต้องการดึงรายละเอียดทั้งหมดของรายชื่อติดต่อ ให้ค้นหาตาราง ContactsContract.Data เพื่อหาแถวที่มี LOOKUP_KEY ของรายชื่อติดต่อ คอลัมน์นี้อยู่ใน ตาราง ContactsContract.Data เนื่องจากผู้ให้บริการรายชื่อติดต่อจะทำการรวมตาราง ContactsContract.Contacts และตาราง ContactsContract.Data โดยนัย เราจะอธิบายคอลัมน์ LOOKUP_KEY โดยละเอียดเพิ่มเติมในบทเรียนการดึงชื่อรายชื่อติดต่อ

หมายเหตุ: การดึงรายละเอียดทั้งหมดของรายชื่อติดต่อจะลดประสิทธิภาพของ อุปกรณ์ เนื่องจากต้องดึงคอลัมน์ทั้งหมดใน ContactsContract.Dataตาราง โปรดพิจารณาผลกระทบต่อประสิทธิภาพก่อนที่จะใช้ วิธีนี้

ขอสิทธิ์

หากต้องการอ่านข้อมูลจาก Contacts Provider แอปของคุณต้องมี READ_CONTACTS สิทธิ์ หากต้องการขอสิทธิ์นี้ ให้เพิ่มอิลิเมนต์ย่อยต่อไปนี้ของ <manifest> ลงในไฟล์ Manifest

    <uses-permission android:name="android.permission.READ_CONTACTS" />

ตั้งค่า Projection

แถวอาจใช้คอลัมน์เพียงไม่กี่คอลัมน์หรือหลายคอลัมน์ก็ได้ ทั้งนี้ขึ้นอยู่กับประเภทข้อมูลที่แถวนั้นมี นอกจากนี้ ข้อมูลยังอยู่ในคอลัมน์ต่างๆ กันด้วย ทั้งนี้ขึ้นอยู่กับประเภทข้อมูล หากต้องการให้แน่ใจว่าคุณได้รับคอลัมน์ทั้งหมดที่เป็นไปได้สำหรับประเภทข้อมูลทั้งหมดที่เป็นไปได้ คุณต้องเพิ่มชื่อคอลัมน์ทั้งหมดลงใน Projection ดึงข้อมูล 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
            };

Projection นี้จะดึงคอลัมน์ทั้งหมดสำหรับแถวในตาราง 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 Provider (และผู้ให้บริการเนื้อหาอื่นๆ ทั้งหมด) ในเทรดเบื้องหลังเสมอ ใช้เฟรมเวิร์ก Loader ที่กำหนดโดย LoaderManager คลาสและ LoaderManager.LoaderCallbacks อินเทอร์เฟซเพื่อดึงข้อมูลเบื้องหลัง

เมื่อพร้อมที่จะดึงแถวแล้ว ให้เริ่มต้นเฟรมเวิร์ก Loader โดย เรียกใช้ initLoader() ส่งตัวระบุจำนวนเต็มไปยังเมธอด ซึ่งตัวระบุนี้จะส่งไปยัง LoaderManager.LoaderCallbacks เมธอด ตัวระบุจะช่วยให้คุณ ใช้ Loader หลายรายการในแอปได้โดยให้คุณแยกความแตกต่างระหว่าง Loader เหล่านั้น

ข้อมูลโค้ดต่อไปนี้แสดงวิธีเริ่มต้นเฟรมเวิร์ก Loader

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() ซึ่งเฟรมเวิร์ก Loader จะเรียกใช้ทันทีหลังจากที่คุณเรียกใช้ 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() เฟรมเวิร์ก Loader จะเรียกใช้ onLoadFinished() เมื่อ Contacts Provider แสดงผลการค้นหา เช่น

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() เมื่อเฟรมเวิร์ก Loader ตรวจพบว่าข้อมูลที่สนับสนุนผลลัพธ์ Cursor มีการเปลี่ยนแปลง ในจุดนี้ ให้นำการอ้างอิงที่มีอยู่ไปยัง Cursor ออกโดยตั้งค่าการอ้างอิงเหล่านั้นเป็น null หากไม่ดำเนินการดังกล่าว เฟรมเวิร์ก Loader จะไม่ทำลาย 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;
    }

ดึงรายละเอียดที่เฉพาะเจาะจงของรายชื่อติดต่อ

การดึงข้อมูลบางประเภทของรายชื่อติดต่อ เช่น อีเมลทั้งหมด จะเป็นไปตามรูปแบบเดียวกับ การดึงรายละเอียดทั้งหมด คุณต้องทำการเปลี่ยนแปลงต่อไปนี้กับโค้ด ที่แสดงในดึงรายละเอียดทั้งหมดของรายชื่อติดต่อ:

Projection
แก้ไข Projection เพื่อดึงคอลัมน์ที่เฉพาะเจาะจงสำหรับ ประเภทข้อมูล นอกจากนี้ ให้แก้ไข Projection เพื่อใช้ค่าคงที่ของชื่อคอลัมน์ที่กำหนดไว้ใน ContactsContract.CommonDataKinds คลาสย่อยที่สอดคล้องกับ ประเภทข้อมูล
การเลือก
แก้ไขข้อความการเลือกเพื่อค้นหา MIMETYPE ค่าที่เฉพาะเจาะจงสำหรับ ประเภทข้อมูล
ลำดับการจัดเรียง
เนื่องจากคุณเลือกรายละเอียดเพียงประเภทเดียว จึงไม่ต้องจัดกลุ่ม Cursor ตาม Data.MIMETYPE

เราจะอธิบายการแก้ไขเหล่านี้ในส่วนต่อไปนี้

กำหนด Projection

กำหนดคอลัมน์ที่ต้องการดึงโดยใช้ค่าคงที่ของชื่อคอลัมน์ในคลาสย่อย ของ ContactsContract.CommonDataKinds สำหรับประเภทข้อมูล หากคุณวางแผนที่จะผูก Cursor กับ ListView, ให้ดึงคอลัมน์ _ID ตัวอย่างเช่น หากต้องการดึงข้อมูลอีเมล ให้กำหนด Projection ต่อไปนี้

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
            };

โปรดทราบว่า Projection นี้ใช้ชื่อคอลัมน์ที่กำหนดไว้ในคลาส ContactsContract.CommonDataKinds.Email แทนชื่อคอลัมน์ ที่กำหนดไว้ในคลาส ContactsContract.Data การใช้ชื่อคอลัมน์ที่เฉพาะเจาะจงสำหรับอีเมล จะช่วยให้อ่านโค้ดได้ง่ายขึ้น

ใน Projection คุณยังใช้คอลัมน์อื่นๆ ที่กำหนดไว้ใน 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 ";