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

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

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

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

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

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

import android.provider.ContactsContract;

Поскольку код использует CursorLoader для извлечения данных из поставщика, необходимо указать, что он реализует интерфейс загрузчика LoaderManager.LoaderCallbacks. Кроме того, чтобы помочь определить, какой контакт пользователь выбирал из списка результатов поиска, реализуйте интерфейс адаптера 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 final static String[] FROM_COLUMNS = {
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    Contacts.DISPLAY_NAME_PRIMARY :
                    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 mContactsList;
    // Define variables for the contact the user selects
    // The contact's _ID value
    long mContactId;
    // The contact's LOOKUP_KEY
    String mContactKey;
    // A content URI for the selected contact
    Uri mContactUri;
    // An adapter that binds the result Cursor to the ListView
    private SimpleCursorAdapter mCursorAdapter;
    ...

Примечание: Так как Contacts.DISPLAY_NAME_PRIMARY требует Android 3.0 (API версии 11) или более поздней версии, установка для вашего приложения minSdkVersion в 10 или ниже вызывает Android Lint предупреждение в Eclipse с ADK. Чтобы отключить это предупреждение, добавить аннотацию @SuppressLint("InlinedApi") перед определением FROM_COLUMNS.

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

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

    // 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. Используйте Context родительской деятельности при вызове setAdapter(). Например:

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

Установка обработчика выбранных контактов

При отображении результатов поиска, вы, как правило, хотите позволить пользователю выбрать один контакт для дальнейшей обработки. Например, когда пользователь нажимает на контакт можно отобразить адрес контакта на карте. Для обеспечения этой функциональности, вы вначале определяете текущий Fragment в качестве слушателя кликов, указав, что класс реализует AdapterView.OnItemClickListener, как показано в разделе Определение Фрагмента, который отображает список контактов.

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

    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        mContactsList.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 содержимого для выбранного пользователем контакта.

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

        };

Определение констант для индексов столбцов курсора

Чтобы получить данные из отдельного столбца из Cursor, вы должны знать индекс столбца в пределах Cursor. Вы можете определить константы для индексов столбцов Cursor , потому что индексы такие же как и порядок названий столбцов в вашей проекции. Например:

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

Укажите критерии выбора

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

Для текстового выражения, определите константу, которая перечисляет столбцы поиска. Хотя это выражение может содержать значения, однако предпочтительной практикой для представления значений является использование заполнителя "?". Во время поиска, заполнитель заменяется значениями из массива. Использование заполнителя "?" гарантирует, что спецификация поиска генерируется путем связывания, а не SQL компиляции. Эта практика исключает возможность вредоносных SQL инъекций. Например:

    // 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 mSearchString;
    // Defines the array to hold values that replace the ?
    private String[] mSelectionArgs = { mSearchString };

Определите метод onItemClick()

В предыдущем разделе вы установили обработчик нажатий на элементы ListView. Теперь реализуйте действие для обработчика, определив метод AdapterView.OnItemClickListener.onItemClick():

    @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
        mContactId = getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        mContactKey = getString(CONTACT_KEY_INDEX);
        // Create the contact's content Uri
        mContactUri = Contacts.getLookupUri(mContactId, mContactKey);
        /*
         * You can use mContactUri as the content URI for retrieving
         * the details for a contact.
         */
    }

Инициализация загрузчика

Так как вы используете CursorLoader для получения данных, вы должны инициализировать фоновый поток и другие переменные, которые управляют асинхронным поиском. Выполните инициализацию в методе onActivityCreated(), который вызывается непосредственно перед тем как появляется пользовательский интерфейс для Fragment , как показано в следующем примере:

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(), настройте шаблон строки поиска. Чтобы создать строковый шаблон, вставьте символы "%" (процентов) для представления последовательности нуля или более символов, или символы "_" (подчеркивания) для представления одного символа, или обоих. Например, шаблон "%Джефферсон%" будет соответствовать как "Томас Джефферсон" так и "Джефферсон Дэвис".

Верните новый CursorLoader из метода. Для URI контента, используйте Contacts.CONTENT_URI. Этот URI относится ко всей таблице, как показано в следующем примере:

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

Реализация onLoadFinished() и onLoaderReset()

Реализуйте onLoadFinished() метод. Платформа загрузки вызывает onLoadFinished() когда поставщик контактов возвращает результаты запроса. В этом методе, поместите результирующий Cursor в SimpleCursorAdapter. Это автоматически обновляет ListView результатами поиска:

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

Метод onLoaderReset() вызывается, когда платформа загрузки обнаруживает, что результирующий Cursor содержит устаревшие данные. Удалите в SimpleCursorAdapter ссылку на существующий Cursor. Если этого не сделать, платформа не будет перерабатывать Cursor, что приводит к утечке памяти. Например:

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        mCursorAdapter.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 и другими таблицами, прежде чем он вернет строки. Например:

    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
        {
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            Data._ID,
            // The primary display name
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    Data.DISPLAY_NAME_PRIMARY :
                    Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            Data.CONTACT_ID
            // The contact's LOOKUP_KEY, to construct a content URI
            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 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.
             */
            Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";

Далее, определите переменные содержащие аргумент выбора:

    String mSearchString;
    String[] mSelectionArgs = { "" };

Реализация onCreateLoader()

Теперь, когда вы указали данные, которые вам нужны и как их искать, определите запрос в вашей реализации onCreateLoader(). Верните новый CursorLoader из этого метода, используя вашу проекцию, выражение выделения текста, и массив значений для поиска в качестве аргументов. В качестве URI содержимого, используйте Data.CONTENT_URI. Например:

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

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

Поиск по всем полям контактных данных

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

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

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

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

  • Определите метод onItemClick().
  • Инициализация загрузчика.
  • Реализация onLoadFinished() и onLoaderReset().

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

Удаление критериев отбора

Не определяйте SELECTION константы или mSelectionArgs переменную. Они не используются для этого типа поиска.

Реализация onCreateLoader()

Реализуйте onCreateLoader() метод, возвращающий новый CursorLoader. Вам не нужно преобразовывать строку поиска в шаблон, поскольку поставщик контактов делает это автоматически. Используйте Contacts.CONTENT_FILTER_URI в качестве базового URI, и добавьте в него строку поиска, вызвав Uri.withAppendedPath(). Использование этого URI автоматически инициирует поиск по всем полям данных, как показано в следующем примере:

    @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(mSearchString));
        // Starts the query
        return new CursorLoader(
                getActivity(),
                contentUri,
                PROJECTION,
                null,
                null,
                null
        );
    }

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