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

检索联系人列表

本课介绍了如何使用以下方法来检索数据与某个搜索字符串完全匹配或部分匹配的联系人列表:

匹配联系人姓名
检索姓名数据与搜索字符串完全匹配或部分匹配的一系列联系人。联系人提供程序支持姓名相同的多个实例,因此这种方法可以返回匹配项的列表。
匹配特定类型的数据(如电话号码)
通过将搜索字符串与特定类型的详细数据(如电子邮件地址)匹配来检索联系人列表。例如,使用这种方法,您可以列出电子邮件地址与搜索字符串匹配的所有联系人。
匹配任何类型的数据
通过将搜索字符串与任何类型的详细数据(包括姓名、电话号码、街道地址和电子邮件地址等)匹配来检索联系人列表。例如,使用这种方法,您可以接受任何数据类型的搜索字符串,然后列出数据与该字符串匹配的联系人。

注意:本课中的所有示例均使用 CursorLoader 检索联系人提供程序中的数据。CursorLoader 在一个独立于界面线程的线程上运行其查询。这样可以确保查询不会拖慢界面响应速度并导致糟糕的用户体验。如需了解详情,请参阅在后台加载数据这一 Android 培训课程。

申请读取提供程序的权限

要对联系人提供程序进行任何类型的搜索,您的应用必须具有 READ_CONTACTS 权限。要申请该权限,请将此 <uses-permission> 元素添加到您的清单文件中,作为 <manifest> 的子元素:

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

按姓名匹配联系人并列出结果

此方法尝试将搜索字符串与联系人提供程序 ContactsContract.Contacts 表格中的一个或多个联系人姓名匹配。您一般希望在 ListView 中显示这些结果,以便用户在匹配的联系人中进行选择。

定义 ListView 和项布局

要在 ListView 中显示搜索结果,您需要一个主布局文件来定义包括 ListView 在内的整个界面,以及一个项布局文件来定义 ListView 中的一行。例如,您可以使用以下 XML 创建主布局文件 res/layout/contacts_list_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@android:id/list"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
    

此 XML 使用内置的 Android ListView 微件 android:id/list

使用以下 XML 定义项布局文件 contacts_list_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@android:id/text1"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:clickable="true"/>
    

此 XML 使用内置的 Android TextView 微件 android:text1

注意:本课并未介绍从用户那里获取搜索字符串的界面,因为您可能希望间接获取字符串。例如,您可以为用户提供一个选项,使其可以搜索姓名与收到的短信中的某个字符串匹配的联系人。

您编写的两个布局文件定义了显示 ListView 的用户界面。下一步是编写使用此界面显示联系人列表的代码。

定义显示联系人列表的 Fragment

要显示联系人列表,请先定义一个由 Activity 加载的 Fragment。使用 Fragment 是一种更灵活的方法,因为您可以使用一个 Fragment 来显示列表,使用第二个 Fragment 来显示用户从列表中选择的联系人的详细信息。使用这种方法,您可以将本课中介绍的其中一个方法与检索联系人的详细信息一课中的方法结合使用。

要了解如何使用 Activity 中的一个或多个 Fragment 对象,请阅读使用 Fragment 构建动态界面这一培训课程。

为了帮助您编写适用于联系人提供程序的查询,Android 框架提供了一个名为 ContactsContract 的联系人类,此类定义了用于访问提供程序的实用常量和方法。使用此类时,无需为内容 URI、表格名称或列定义您自己的常量。要使用此类,请附上以下声明:

Kotlin

    import android.provider.ContactsContract
    

Java

    import android.provider.ContactsContract;
    

由于代码使用 CursorLoader 检索提供程序中的数据,因此您必须指定它实现加载器接口 LoaderManager.LoaderCallbacks。此外,为帮助检测用户从搜索结果列表中选择的联系人,请实现适配器接口 AdapterView.OnItemClickListener。例如:

Kotlin

    ...
    import android.support.v4.app.Fragment
    import android.support.v4.app.LoaderManager
    import android.widget.AdapterView
    ...
    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor>,
            AdapterView.OnItemClickListener {
    

Java

    ...
    import android.support.v4.app.Fragment;
    import android.support.v4.app.LoaderManager.LoaderCallbacks;
    import android.widget.AdapterView;
    ...
    public class ContactsFragment extends Fragment implements
            LoaderManager.LoaderCallbacks<Cursor>,
            AdapterView.OnItemClickListener {
    

定义全局变量

定义代码的其他部分中使用的全局变量:

Kotlin

    ...
    /*
     * Defines an array that contains column names to move from
     * the Cursor to the ListView.
     */
    @SuppressLint("InlinedApi")
    private val FROM_COLUMNS: Array<String> = arrayOf(
            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
            } else {
                ContactsContract.Contacts.DISPLAY_NAME
            }
    )
    /*
     * Defines an array that contains resource ids for the layout views
     * that get the Cursor column contents. The id is pre-defined in
     * the Android framework, so it is prefaced with "android.R.id"
     */
    private val TO_IDS: IntArray = intArrayOf(android.R.id.text1)
    ...
    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor>,
            AdapterView.OnItemClickListener {
        ...
        // Define global mutable variables
        // Define a ListView object
        lateinit var contactsList: ListView
        // Define variables for the contact the user selects
        // The contact's _ID value
        var contactId: Long = 0
        // The contact's LOOKUP_KEY
        var contactKey: String? = null
        // A content URI for the selected contact
        var contactUri: Uri? = null
        // An adapter that binds the result Cursor to the ListView
        private val cursorAdapter: SimpleCursorAdapter? = null

    

Java

        ...
        /*
         * Defines an array that contains column names to move from
         * the Cursor to the ListView.
         */
        @SuppressLint("InlinedApi")
        private final static String[] FROM_COLUMNS = {
                Build.VERSION.SDK_INT
                        >= Build.VERSION_CODES.HONEYCOMB ?
                        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                        ContactsContract.Contacts.DISPLAY_NAME
        };
        /*
         * Defines an array that contains resource ids for the layout views
         * that get the Cursor column contents. The id is pre-defined in
         * the Android framework, so it is prefaced with "android.R.id"
         */
        private final static int[] TO_IDS = {
               android.R.id.text1
        };
        // Define global mutable variables
        // Define a ListView object
        ListView contactsList;
        // Define variables for the contact the user selects
        // The contact's _ID value
        long contactId;
        // The contact's LOOKUP_KEY
        String contactKey;
        // A content URI for the selected contact
        Uri contactUri;
        // An adapter that binds the result Cursor to the ListView
        private SimpleCursorAdapter cursorAdapter;
        ...
    

注意:由于 Contacts.DISPLAY_NAME_PRIMARY 需要 Android 3.0(API 版本 11)或更高版本,因此将应用的 minSdkVersion 设置为 10 或更低版本会在 Android Studio 中生成一条 Android Lint 警告。要关闭这条警告,可在 FROM_COLUMNS 的定义前面添加注释 @SuppressLint("InlinedApi")

初始化 Fragment

初始化 Fragment。添加 Android 系统所需的空的公开构造函数,并在回调方法 onCreateView() 中扩充 Fragment 对象的界面。例如:

Kotlin

        // A UI Fragment must inflate its View
        override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
        ): View? {
            // Inflate the fragment layout
            return inflater.inflate(R.layout.contact_list_fragment, container, false)
        }
    

Java

        // Empty public constructor, required by the system
        public ContactsFragment() {}

        // A UI Fragment must inflate its View
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            // Inflate the fragment layout
            return inflater.inflate(R.layout.contact_list_fragment,
                container, false);
        }
    

为 ListView 设置 CursorAdapter

设置将搜索结果绑定到 ListViewSimpleCursorAdapter。要获取显示联系人的 ListView 对象,您需要使用 Fragment 的父 Activity 调用 Activity.findViewById()。调用 setAdapter() 时,请使用父 Activity 的 Context。例如:

Kotlin

        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            ...
            // Gets the ListView from the View list of the parent activity
            activity?.also {
                contactsList = it.findViewById<ListView>(R.id.contact_list_view)
                // Gets a CursorAdapter
                cursorAdapter = SimpleCursorAdapter(
                        it,
                        R.layout.contact_list_item,
                        null,
                        FROM_COLUMNS, TO_IDS,
                        0
                )
                // Sets the adapter for the ListView
                contactsList.adapter = cursorAdapter
            }
        }
    

Java

        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            ...
            // Gets the ListView from the View list of the parent activity
            contactsList =
                (ListView) getActivity().findViewById(R.layout.contact_list_view);
            // Gets a CursorAdapter
            cursorAdapter = new SimpleCursorAdapter(
                    getActivity(),
                    R.layout.contact_list_item,
                    null,
                    FROM_COLUMNS, TO_IDS,
                    0);
            // Sets the adapter for the ListView
            contactsList.setAdapter(cursorAdapter);
        }
    

设置选定的联系人监听器

在显示搜索结果时,您通常希望能够允许用户选择一位联系人以进一步处理。例如,当用户点击某位联系人时,您可以在地图上显示该联系人的地址。要提供此功能,您首先需要将当前 Fragment 定义为点击监听器(可通过指定该类实现 AdapterView.OnItemClickListener 执行此操作),如定义显示联系人列表的 Fragment 部分所示。

要继续设置监听器,可通过调用 onActivityCreated() 中的 setOnItemClickListener() 方法将其绑定到 ListView。例如:

Kotlin

        fun onActivityCreated(savedInstanceState:Bundle) {
            ...
            // Set the item click listener to be the current fragment.
            contactsList.onItemClickListener = this
            ...
        }
    

Java

        public void onActivityCreated(Bundle savedInstanceState) {
            ...
            // Set the item click listener to be the current fragment.
            contactsList.setOnItemClickListener(this);
            ...
        }
    

由于您指定当前 FragmentListViewOnItemClickListener,因此您现在需要实现其所需的方法 onItemClick(),此方法会处理点击事件。后面的部分会对此进行说明。

定义映射

定义一个常量,该常量包含要从查询中返回的列。ListView 中的每一项都显示联系人的显示姓名,其中包含主要形式的联系人姓名。在 Android 3.0(API 版本 11)及更高版本中,此列的名称为 Contacts.DISPLAY_NAME_PRIMARY;在之前版本中,其名称为 Contacts.DISPLAY_NAME

Contacts._IDSimpleCursorAdapter 绑定进程使用。 将 Contacts._IDLOOKUP_KEY 结合使用可为用户选择的联系人构建内容 URI。

Kotlin

    ...
    @SuppressLint("InlinedApi")
    private val PROJECTION: Array<out String> = arrayOf(
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.LOOKUP_KEY,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
            else
                ContactsContract.Contacts.DISPLAY_NAME
    )
    

Java

    ...
    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
            {
                Contacts._ID,
                Contacts.LOOKUP_KEY,
                Build.VERSION.SDK_INT
                        >= Build.VERSION_CODES.HONEYCOMB ?
                        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                        ContactsContract.Contacts.DISPLAY_NAME

            };
    

为 Cursor 列索引定义常量

要从 Cursor 中的单个列获取数据,您需要 Cursor 中该列的索引。您可以为 Cursor 列的索引定义常量,因为这些索引与映射中的列名称顺序相同。例如:

Kotlin

    // The column index for the _ID column
    private const val CONTACT_ID_INDEX: Int = 0
    // The column index for the CONTACT_KEY column
    private const val CONTACT_KEY_INDEX: Int = 1
    

Java

    // The column index for the _ID column
    private static final int CONTACT_ID_INDEX = 0;
    // The column index for the CONTACT_KEY column
    private static final int CONTACT_KEY_INDEX = 1;
    

指定选择条件

要指定所需的数据,请创建文本表达式和变量的组合,告知提供程序要搜索的数据列及要查找的值。

对于文本表达式,请定义一个列出搜索列的常量。尽管此表达式也可以包含值,但建议您用“?”占位符表示这些值。在检索期间,该占位符会被替换为数组中的值。使用“?”作为占位符可确保搜索规范是通过绑定而不是 SQL 编译生成的。这种做法消除了恶意 SQL 注入的可能性。例如:

Kotlin

    // Defines the text expression
    @SuppressLint("InlinedApi")
    private val SELECTION: String =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
            else
                "${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
    ...
        // Defines a variable for the search string
        private val searchString: String = ...
        // Defines the array to hold values that replace the ?
        private val selectionArgs = arrayOf<String>(searchString)
    

Java

        // Defines the text expression
        @SuppressLint("InlinedApi")
        private static final String SELECTION =
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
                Contacts.DISPLAY_NAME + " LIKE ?";
        // Defines a variable for the search string
        private String searchString;
        // Defines the array to hold values that replace the ?
        private String[] selectionArgs = { searchString };
    

定义 onItemClick() 方法

在上一部分中,您为 ListView 设置了项点击监听器。现在,可通过定义方法 AdapterView.OnItemClickListener.onItemClick() 实现对监听器的操作:

Kotlin

        override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
            // Get the Cursor
            val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
                // Move to the selected contact
                moveToPosition(position)
                // Get the _ID value
                contactId = getLong(CONTACT_ID_INDEX)
                // Get the selected LOOKUP KEY
                contactKey = getString(CONTACT_KEY_INDEX)
                // Create the contact's content Uri
                contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
                /*
                 * You can use contactUri as the content URI for retrieving
                 * the details for a contact.
                 */
            }
        }
    

Java

        @Override
        public void onItemClick(
            AdapterView<?> parent, View item, int position, long rowID) {
            // Get the Cursor
            Cursor cursor = parent.getAdapter().getCursor();
            // Move to the selected contact
            cursor.moveToPosition(position);
            // Get the _ID value
            contactId = cursor.getLong(CONTACT_ID_INDEX);
            // Get the selected LOOKUP KEY
            contactKey = cursor.getString(CONTACT_KEY_INDEX);
            // Create the contact's content Uri
            contactUri = Contacts.getLookupUri(contactId, mContactKey);
            /*
             * You can use contactUri as the content URI for retrieving
             * the details for a contact.
             */
        }
    

初始化加载器

由于您使用 CursorLoader 来检索数据,因此您必须初始化后台线程以及其他控制异步检索的变量。在 onActivityCreated() 中执行初始化操作,该操作会在 Fragment 界面出现之前立即调用,如以下示例所示:

Kotlin

    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        // Called just before the Fragment displays its UI
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            // Always call the super method first
            super.onActivityCreated(savedInstanceState)
            ...
            // Initializes the loader
            loaderManager.initLoader(0, null, this)
    

Java

    public class ContactsFragment extends Fragment implements
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        // Called just before the Fragment displays its UI
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            // Always call the super method first
            super.onActivityCreated(savedInstanceState);
            ...
            // Initializes the loader
            getLoaderManager().initLoader(0, null, this);
    

实现 onCreateLoader()

实现 onCreateLoader() 方法,加载器框架会在您调用 initLoader() 之后立即调用此方法。

onCreateLoader() 中,设置搜索字符串格式。要为字符串指定格式,请插入“%”(百分比)字符以表示零个或多个字符序列,或插入“_”(下划线)字符以表示单个字符,或者同时插入这两个字符。例如,“%Jefferson%”格式会同时匹配“Thomas Jefferson”和“Jefferson Davis”。

从此方法返回一个新的 CursorLoader。对于内容 URI,请使用 Contacts.CONTENT_URI。该 URI 引用整个表格,如以下示例所示:

Kotlin

        ...
        override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
            /*
             * Makes search string into pattern and
             * stores it in the selection array
             */
            selectionArgs[0] = "%$mSearchString%"
            // Starts the query
            return activity?.let {
                return CursorLoader(
                        it,
                        ContactsContract.Contacts.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        null
                )
            } ?: throw IllegalStateException()
        }
    

Java

        ...
        @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            /*
             * Makes search string into pattern and
             * stores it in the selection array
             */
            selectionArgs[0] = "%" + searchString + "%";
            // Starts the query
            return new CursorLoader(
                    getActivity(),
                    ContactsContract.Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            );
        }
    

实现 onLoadFinished() 和 onLoaderReset()

实现 onLoadFinished() 方法。当联系人提供程序返回查询结果时,加载器框架会调用 onLoadFinished()。在此方法中,将结果 Cursor 放入 SimpleCursorAdapter 中。这会使用搜索结果自动更新 ListView

Kotlin

        override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
            // Put the result Cursor in the adapter for the ListView
            cursorAdapter?.swapCursor(cursor)
        }
    

Java

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            // Put the result Cursor in the adapter for the ListView
            cursorAdapter.swapCursor(cursor);
        }
    

当加载器框架检测到结果 Cursor 包含陈旧数据时,会调用 onLoaderReset() 方法。删除对现有 CursorSimpleCursorAdapter 引用。否则,加载器框架将不会回收利用 Cursor,从而导致内存泄漏。例如:

Kotlin

        override fun onLoaderReset(loader: Loader<Cursor>) {
            // Delete the reference to the existing Cursor
            cursorAdapter?.swapCursor(null)
        }
    

Java

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            // Delete the reference to the existing Cursor
            cursorAdapter.swapCursor(null);

        }
    

现在,您已经有了应用的关键部分,该部分将搜索字符串与联系人姓名匹配,并在 ListView 中返回结果。用户可以点击联系人姓名将其选中。这会触发监听器,您可以在其中进一步处理联系人的数据。例如,您可以检索联系人的详细信息。要了解如何执行此操作,请继续学习下一课:检索联系人的详细信息

要详细了解搜索用户界面,请阅读 API 指南:创建搜索界面

本课的其余部分介绍了在联系人提供程序中查找联系人的其他方法。

按特定类型的数据匹配联系人

通过使用这种方法,您可以指定要匹配的数据类型。按姓名检索就是此类查询的一个具体示例,但是您也可以对与联系人相关的任何类型的详细数据进行检索。例如,您可以检索具有特定邮政编码的联系人;在这种情况下,搜索字符串必须与存储在邮政编码行中的数据匹配。

要实现这种类型的检索,请先实现以下代码,如前几部分中所述:

  • 请求读取提供程序的权限。
  • 定义 ListView 和项布局。
  • 定义显示联系人列表的 Fragment。
  • 定义全局变量。
  • 初始化 Fragment。
  • 为 ListView 设置 CursorAdapter。
  • 设置选定的联系人监听器。
  • 为 Cursor 列索引定义常量。

    尽管您要从不同表格中检索数据,但是映射中各列的顺序是相同的,因此您可以对 Cursor 使用相同的索引。

  • 定义 onItemClick() 方法。
  • 初始化加载器。
  • 实现 onLoadFinished() 和 onLoaderReset()。

下面的步骤显示了将搜索字符串与特定类型的详细数据匹配并显示结果所需的其他代码。

选择数据类型和表格

要搜索特定类型的详细数据,您必须知道该数据类型的自定义 MIME 类型值。每个数据类型都有一个唯一的 MIME 类型值,该值由 CONTENT_ITEM_TYPE 常量定义,此常量位于与该数据类型关联的 ContactsContract.CommonDataKinds 的子类中。子类的名称可表示其数据类型。例如,电子邮件地址数据的子类是 ContactsContract.CommonDataKinds.Email,电子邮件地址数据的自定义 MIME 类型由常量 Email.CONTENT_ITEM_TYPE 定义。

使用 ContactsContract.Data 表格进行搜索。该表格定义或继承了您的映射、选择子句和排序顺序所需的所有常量。

定义映射

要定义映射,请选择 ContactsContract.Data 中定义的一列或多列或其继承的类。联系人提供程序会在 ContactsContract.Data 和其他表格之间建立隐式关联,然后再返回行。例如:

Kotlin

    @SuppressLint("InlinedApi")
    private val PROJECTION: Array<out String> = arrayOf(
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            ContactsContract.Data._ID,
            // The primary display name
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                ContactsContract.Data.DISPLAY_NAME_PRIMARY
            else
                ContactsContract.Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            ContactsContract.Data.CONTACT_ID,
            // The contact's LOOKUP_KEY, to construct a content URI
            ContactsContract.Data.LOOKUP_KEY
    )
    

Java

        @SuppressLint("InlinedApi")
        private static final String[] PROJECTION =
            {
                /*
                 * The detail data row ID. To make a ListView work,
                 * this column is required.
                 */
                ContactsContract.Data._ID,
                // The primary display name
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                        ContactsContract.Data.DISPLAY_NAME_PRIMARY :
                        ContactsContract.Data.DISPLAY_NAME,
                // The contact's _ID, to construct a content URI
                ContactsContract.Data.CONTACT_ID,
                // The contact's LOOKUP_KEY, to construct a content URI
                ContactsContract.Data.LOOKUP_KEY // A permanent link to the contact
            };
    

定义搜索条件

要在特定数据类型中搜索某个字符串,请根据以下内容构造一个选择子句:

  • 包含该搜索字符串的列名称。此名称因数据类型而异,因此您需要找到与该数据类型相对应的 ContactsContract.CommonDataKinds 子类,然后从该子类中选择列名称。例如,要搜索电子邮件地址,请使用 Email.ADDRESS 列。
  • 搜索字符串本身在选择子句中表示为“?”字符。
  • 包含自定义 MIME 类型值的列名称。此名称始终为 Data.MIMETYPE
  • 该数据类型的自定义 MIME 类型值。如前所述,这是 CONTENT_ITEM_TYPE 常量,该常量位于 ContactsContract.CommonDataKinds 子类中。例如,电子邮件地址数据的 MIME 类型值是 Email.CONTENT_ITEM_TYPE。通过将“'”(单引号)字符连接到常量的开头和结尾,将值用单引号引起来;否则,提供程序会将值解译为变量名称,而不是字符串值。您不需要为此值使用占位符,因为您使用的是常量,而不是用户提供的值。

例如:

Kotlin

    /*
     * Constructs search criteria from the search string
     * and email MIME type
     */
    private val SELECTION: String =
            /*
             * Searches for an email address
             * that matches the search string
             */
            "${Email.ADDRESS} LIKE ? AND " +
            /*
             * Searches for a MIME type that matches
             * the value of the constant
             * Email.CONTENT_ITEM_TYPE. Note the
             * single quotes surrounding Email.CONTENT_ITEM_TYPE.
             */
            "${ContactsContract.Data.MIMETYPE } = '${Email.CONTENT_ITEM_TYPE}'"
    

Java

        /*
         * Constructs search criteria from the search string
         * and email MIME type
         */
        private static final String SELECTION =
                /*
                 * Searches for an email address
                 * that matches the search string
                 */
                Email.ADDRESS + " LIKE ? " + "AND " +
                /*
                 * Searches for a MIME type that matches
                 * the value of the constant
                 * Email.CONTENT_ITEM_TYPE. Note the
                 * single quotes surrounding Email.CONTENT_ITEM_TYPE.
                 */
                ContactsContract.Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";
    

接着,定义变量以包含选择参数:

Kotlin

        private var searchString: String? = null
        private val selectionArgs: Array<String> = arrayOf("")
    

Java

        String searchString;
        String[] selectionArgs = { "" };
    

实现 onCreateLoader()

现在您已经指定了所需的数据以及查找它们的方式,请在 onCreateLoader() 实现中定义一个查询。将映射、选择文本表达式和选择数组作为参数,从此方法返回一个新的 CursorLoader。对于内容 URI,请使用 Data.CONTENT_URI。例如:

Kotlin

        override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
            // OPTIONAL: Makes search string into pattern
            searchString = "%$mSearchString%"

            searchString?.also {
                // Puts the search string into the selection criteria
                selectionArgs[0] = it
            }
            // Starts the query
            return activity?.let {
                CursorLoader(
                        it,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        null
                )
            } ?: throw IllegalStateException()
        }
    

Java

    @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            // OPTIONAL: Makes search string into pattern
            searchString = "%" + searchString + "%";
            // Puts the search string into the selection criteria
            selectionArgs[0] = searchString;
            // Starts the query
            return new CursorLoader(
                    getActivity(),
                    Data.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            );
        }
    

这些代码段是根据特定类型的详细数据进行简单反查的基础。如果您的应用侧重于特定类型的数据(例如电子邮件地址),并且您希望用户能够获取与某条数据相关的姓名,那么这无疑是最理想的方法。

按任何类型的数据匹配联系人

在根据任何类型的数据检索某位联系人时,如果一些联系人的任何数据(包括姓名、电子邮件地址、邮政地址和电话号码等)与搜索字符串匹配,则返回这些联系人。这会产生广泛的搜索结果。例如,如果搜索字符串为“Doe”,那么搜索任何类型的数据不仅会返回联系人“John Doe”,还会返回居住在“Doe Street”的联系人。

要实现这种类型的检索,请先实现以下代码,如前几部分中所述:

  • 请求读取提供程序的权限。
  • 定义 ListView 和项布局。
  • 定义显示联系人列表的 Fragment。
  • 定义全局变量。
  • 初始化 Fragment。
  • 为 ListView 设置 CursorAdapter。
  • 设置选定的联系人监听器。
  • 定义映射。
  • 为 Cursor 列索引定义常量。

    对于这种类型的检索,您可以使用在按姓名匹配联系人并列出结果部分所用的相同表格。同时使用相同的列索引。

  • 定义 onItemClick() 方法。
  • 初始化加载器。
  • 实现 onLoadFinished() 和 onLoaderReset()。

下面的步骤显示了将搜索字符串与任何类型的数据匹配并显示结果所需的其他代码。

移除选择条件

不要定义 SELECTION 常量或 mSelectionArgs 变量。此类检索中不使用这些内容。

实现 onCreateLoader()

实现 onCreateLoader() 方法,返回一个新的 CursorLoader。您不需要将搜索字符串转换为格式,因为联系人提供程序会自动执行该操作。将 Contacts.CONTENT_FILTER_URI 用作基本 URI,然后通过调用 Uri.withAppendedPath() 将搜索字符串附加到其中。使用此 URI 会自动触发搜索任何数据类型,如以下示例所示:

Kotlin

        override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
            /*
             * Appends the search string to the base URI. Always
             * encode search strings to ensure they're in proper
             * format.
             */
            val contentUri: Uri = Uri.withAppendedPath(
                    ContactsContract.Contacts.CONTENT_FILTER_URI,
                    Uri.encode(searchString)
            )
            // Starts the query
            return activity?.let {
                CursorLoader(
                        it,
                        contentUri,
                        PROJECTION2,
                        null,
                        null,
                        null
                )
            } ?: throw IllegalStateException()
        }
    

Java

        @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            /*
             * Appends the search string to the base URI. Always
             * encode search strings to ensure they're in proper
             * format.
             */
            Uri contentUri = Uri.withAppendedPath(
                    Contacts.CONTENT_FILTER_URI,
                    Uri.encode(searchString));
            // Starts the query
            return new CursorLoader(
                    getActivity(),
                    contentUri,
                    PROJECTION,
                    null,
                    null,
                    null
            );
        }
    

这些代码段是对联系人提供程序进行广泛搜索的应用的基础。这种方法适用于希望实现与“联系人”应用中的联系人列表屏幕功能相似的应用。