检索联系人详细信息

本课程演示如何检索联系人的详细信息,例如电子邮件地址、电话号码等。当用户检索联系人时,他们会寻找这些详细信息。您可以向他们提供联系人的所有详细信息,或者仅显示特定类型的详细信息,例如电子邮件地址。

本课程中的步骤假设您已经拥有用户选择的联系人的 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 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;
            ...
        }
    }

当加载器框架检测到支持结果Cursor的数据已更改时,将调用onLoaderReset()方法。此时,通过将它们设置为 null 来删除对Cursor的所有现有引用。如果您不这样做,加载器框架将不会销毁旧的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 ";