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

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

ขั้นตอนในบทเรียนนี้จะถือว่าคุณมีแถว 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 ";