Получение списка контактов

В этом уроке рассказывается, как получить список контактов, данные которых соответствуют строке поиска полностью или частично, используя следующие методы:

Сопоставлять имена контактов
Получить список контактов, сопоставив строку поиска со всем или частью имени контакта. Поставщик контактов допускает несколько экземпляров одного и того же имени, поэтому этот метод может возвращать список совпадений.
Сопоставление с определенным типом данных, например номером телефона
Получение списка контактов путем сопоставления строки поиска с определенным типом данных, например адресом электронной почты. Например, с помощью этого метода можно получить список всех контактов, адрес электронной почты которых соответствует строке поиска.
Сопоставить любой тип данных
Получить список контактов, соответствующих поисковому запросу, по любому типу данных, включая имя, номер телефона, почтовый адрес, адрес электронной почты и т. д. Например, этот метод позволяет принимать любые типы данных для строки поиска, а затем выводить список контактов, для которых данные соответствуют строке.

Примечание. Во всех примерах в этом уроке для получения данных от поставщика контактов используется CursorLoader. CursorLoader выполняет запрос в потоке, отдельном от потока UI. Это гарантирует, что запрос не замедлит работу интерфейса и не ухудшит удобство использования. Подробную информацию можно найти в статье Загрузка данных в фоновом режиме.

Запросить разрешение на чтение данных поставщика

Чтобы выполнять поиск в поставщике контактов, у приложения должно быть разрешение 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.

Определите файл макета элемента contacts_list_item.xml с помощью следующего 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, ознакомьтесь с учебным курсом Создание динамического пользовательского интерфейса с помощью фрагментов.

Чтобы вам было проще писать запросы к поставщику контактов, в платформе 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. Чтобы отключить это предупреждение, добавьте аннотацию @SuppressLint("InlinedApi") перед определением FROM_COLUMNS.

Инициализация фрагмента

Инициализируйте Fragment. Добавьте пустой общедоступный конструктор, необходимый для системы Android, и разверните пользовательский интерфейс объекта Fragment в методе обратного вызова onCreateView(). Пример:

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

Настройка CursorAdapter для ListView

Настройте SimpleCursorAdapter, который связывает результаты поиска с ListView. Чтобы получить объект ListView, который показывает контакты, вызовите Activity.findViewById(), используя родительское действие Fragment. При вызове setAdapter() используйте Context родительского класса activity. Пример:

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, как показано в разделе Определение фрагмента, который отображает список контактов.

Чтобы продолжить настройку прослушивателя, привяжите его к ListView, вызвав метод setOnItemClickListener() в onActivityCreated(). Пример:

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 содержит отображаемое название контакта, которое является основной формой имени контакта. В 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

        };

Define constants for the Cursor column indexes

Чтобы получить данные из отдельного столбца в 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);
    }

Метод onLoaderReset() вызывается, когда фреймворк загрузчика обнаруживает, что результат Cursor содержит устаревшие данные. Удалите ссылку SimpleCursorAdapter на существующий объект Cursor. В противном случае фреймворк загрузчика не будет повторно использовать 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 и элементов.
  • Определите фрагмент, который отображает список контактов.
  • Определите глобальные переменные.
  • Инициализируйте фрагмент.
  • Настройте CursorAdapter для ListView.
  • Настройте прослушиватель выбранного контакта.
  • Определите константы для индексов столбцов курсора.

    Хотя вы извлекаете данные из другой таблицы, порядок столбцов в проекции остается прежним, поэтому вы можете использовать те же индексы для курсора.

  • Определите метод 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
        );
    }

Эти фрагменты кода являются основой для базового обратного поиска на основе определенного типа подробных данных. Этот способ лучше всего подходит, если ваше приложение работает с определенным типом данных, например электронными письмами, и вы хотите, чтобы пользователи могли получать имена, связанные с этими данными.

Сопоставление контактов по любому типу данных

При поиске контакта по любому типу данных возвращаются контакты, у которых есть совпадения с поисковым запросом, включая имя, адрес электронной почты, почтовый адрес, номер телефона и т. д. В результате вы получите широкий набор результатов поиска. Например, если строка поиска – "Иванов", то при поиске по любому типу данных будет найден контакт "Иван Иванов", а также контакты, которые живут на улице Иванова.

Чтобы реализовать этот тип извлечения, сначала реализуйте следующий код, как описано в предыдущих разделах:

  • Запросите разрешение на чтение данных поставщика.
  • Определите макеты ListView и элементов.
  • Определите фрагмент, который отображает список контактов.
  • Определите глобальные переменные.
  • Инициализируйте фрагмент.
  • Настройте CursorAdapter для ListView.
  • Настройте прослушиватель выбранного контакта.
  • Определите проекцию.
  • Определите константы для индексов столбцов курсора.

    Для этого типа поиска используется та же таблица, что и в разделе Как найти контакт по имени и посмотреть результаты. Используйте те же индексы столбцов.

  • Определите метод 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
        );
    }

Эти фрагменты кода являются основой приложения, которое выполняет широкий поиск в поставщике контактов. Этот метод полезен для приложений, в которых нужно реализовать функциональность, аналогичную экрану списка контактов приложения "Люди".