The Android Developer Challenge is back! Submit your idea before December 2.

检索联系人的详细信息

本课介绍了如何检索联系人的详细数据,例如电子邮件地址、电话号码等。用户在检索联系人时要查找的就是这些详细数据。您可以为用户提供某位联系人的所有详细信息,也可以仅显示特定类型的详细信息(如电子邮件地址)。

本课中的步骤假定您已有用户所选联系人的 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" />
    

设置映射

根据一行包含的数据类型,映射可能仅使用几列,也可能使用很多列。此外,这些数据会位于不同的列中,具体取决于数据类型。为确保您能够获得所有可能数据类型的所有可能列,您需要将所有列名称添加到映射中。如果您要将结果 Cursor 绑定到 ListView,请务必检索 Data._ID;否则,绑定将无效。此外,您还需要检索 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 类继承或在其中定义的任何其他列常量。不过请注意,SYNC1SYNC4 列旨在供同步适配器使用,因此其中的数据无用。

定义选择标准

为您的选择子句定义一个常量,同时定义一个数组来保存选择参数以及一个变量来保存选择值。使用 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,确定当前行的数据类型,并存储使用子类型的行的数据。读取光标后,就可以按子类型对每种数据类型进行排序并显示结果。

初始化加载器

务必在后台线程中检索联系人提供程序(以及所有其他内容提供程序)。使用由 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 onActivityCreated(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;
        ...
        /*
         * Invoked when the parent Activity is instantiated
         * and the Fragment's UI is ready. Put final initialization
         * steps here.
         */
        @Override
        onActivityCreated(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;
                ...
            }
        }
    

当加载器框架检测到支持结果 Cursor 的数据发生更改时,将调用 onLoaderReset() 方法。此时,可通过将对 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 值。
排序顺序
由于您仅选择一种详细信息类型,因此请不要按 Data.MIMETYPE 对返回的 Cursor 分组。

下面的部分介绍了这些具体修改。

定义映射

使用该数据类型的 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 ";