检索联系人列表

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

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

注意:本课中的所有示例均使用 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 widget android:text1

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

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

定义显示联系人列表的 Fragment

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

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

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

import android.provider.ContactsContract
import android.provider.ContactsContract;

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

...
import android.support.v4.app.Fragment
import android.support.v4.app.LoaderManager
import android.widget.AdapterView
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
...
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 {

定义全局变量

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

...
/*
 * 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
    ...
    /*
     * 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 对象的界面。例如:

    // 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)
    }
    // 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。例如:

    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
        }
    }
    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。例如:

    fun onActivityCreated(savedInstanceState:Bundle) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.onItemClickListener = this
        ...
    }
    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。

...
@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
)
...
@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 列的索引定义常量,因为这些索引与映射中的列名称顺序相同。例如:

// 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
// 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 注入的可能性。例如:

// 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)
    // 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() 实现对监听器的操作:

    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.
             */
        }
    }
    @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 来检索数据,因此您必须初始化后台线程以及其他控制异步检索的变量。在 onCreate() 中执行初始化操作,如以下示例所示:

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

实现 onCreateLoader()

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

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

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

    ...
    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()
    }
    ...
    @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

    override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter?.swapCursor(cursor)
    }
    @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,从而导致内存泄露。例如:

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // Delete the reference to the existing Cursor
        cursorAdapter?.swapCursor(null)
    }
    @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 和其他表之间建立隐式联接,然后再返回行。例如:

@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
)
    @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。通过将“'”(英文单引号)字符连接到常量的开头和结尾,将值用英文单引号引起来;否则,提供程序会将值解释为变量名称,而不是字符串值。您不需要为此值使用占位符,因为您使用的是常量,而不是用户提供的值。

例如:

/*
 * 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}'"
    /*
     * 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 + "'";

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

    private var searchString: String? = null
    private val selectionArgs: Array<String> = arrayOf("")
    String searchString;
    String[] selectionArgs = { "" };

实现 onCreateLoader()

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

    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()
    }
@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 会自动触发对任何数据类型的搜索,如以下示例所示:

    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()
    }
    @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
        );
    }

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