检索联系人列表

本课程将向您介绍如何使用以下技术检索联系人列表,这些联系人的数据与 搜索字符串的全部或部分匹配:

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

注意 :本课程中的所有示例都使用 CursorLoader 从“通讯录提供程序”检索数据。A CursorLoader 会在与界面线程分开的 线程上运行其查询。这可确保查询不会降低界面 响应速度,也不会导致糟糕的用户体验。如需了解详情,请参阅 Android 培训课程 在后台加载数据。

请求读取提供程序的权限

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

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

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

这项技术会尝试将搜索字符串与“联系人提供程序”的 ContactsContract.Contacts 表中的一个或多个联系人的姓名进行匹配。您通常希望 在 ListView 中显示结果,以便用户在匹配的联系人中进行选择。

定义 ListView 和项布局

如需在 ListView 中显示搜索结果,您需要一个定义整个界面(包括 ListView)的主布局文件,以及一个定义 ListView 一行的项布局文件。例如,您可以创建 主布局文件 res/layout/contacts_list_view.xml,其中包含 以下 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 显示用户从列表中选择的联系人的详细信息。使用这种方法,您可以将本课程中介绍的一种技术与 检索联系人的详细信息课程中的一种技术相结合。

如需了解如何使用 Fragment Activity 对象,请阅读 使用 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

设置将搜索结果绑定到 ListView 的 SimpleCursorAdapter。如需获取显示联系人的 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);
        ...
    }

由于您已指定当前 Fragment 是 OnItemClickListener 的 ListView,因此现在需要实现其必需的方法 onItemClick(),该方法用于处理点击事件。这将在后续部分中介绍。

定义映射

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

列Contacts._ID由 SimpleCursorAdapter绑定过程 使用。Contacts._ID 和 LOOKUP_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 来检索数据,您必须初始化后台线程和其他控制异步检索的变量。在 onCreate() 中进行初始化,如 以下示例所示:

Kotlin

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)

Java

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 指的是整个表,如以下示例所示:

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()。删除对现有 Cursor 的 SimpleCursorAdapter 引用。否则,加载器框架不会 回收 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。使用此 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
        );
    }

这些代码段是用于对“联系人提供程序”执行广泛搜索的应用的基础。这项技术适用于希望实现与 “通讯录”应用的联系人列表界面类似的功能的应用。