Поставщик контактов – это мощный и гибкий компонент Android, который управляет центральным хранилищем данных о людях на устройстве. Поставщик контактов – это источник данных, которые вы видите в приложении для управления контактами на устройстве. Вы также можете получить доступ к этим данным в своем приложении и передавать их между устройством и онлайн-сервисами. Поставщик работает с большим количеством источников данных и старается управлять как можно большим объемом данных для каждого человека, в результате чего его организация является сложной. Поэтому API поставщика включает обширный набор классов контрактов и интерфейсов, которые упрощают как получение, так и изменение данных.
В этом руководстве рассматриваются следующие темы:
- Базовая структура поставщика.
- Как получить данные от поставщика.
- Как изменить данные в поставщике.
- Как написать адаптер синхронизации для переноса данных с сервера в поставщик контактов.
В этом руководстве предполагается, что вы знакомы с основами работы поставщиков контента Android. Подробнее о поставщиках контента Android рассказывается в руководстве по основам работы с поставщиками контента.
Организация поставщика контактов
Поставщик контактов – это компонент поставщика контента Android. Он поддерживает три типа данных о человеке, каждый из которых соответствует таблице, предлагаемой поставщиком, как показано на рисунке 1:
Рисунок 1. Структура таблицы поставщика контактов.
Эти три таблицы обычно называют по именам их классов контрактов. Классы определяют константы для URI контента, названий столбцов и значений столбцов, используемых таблицами:
-
Таблица "
ContactsContract.Contacts". - Строки, представляющие разных людей, на основе агрегаций строк необработанных контактов.
-
Таблица "
ContactsContract.RawContacts". - Строки, содержащие сводку данных пользователя, относящихся к определенному аккаунту и типу.
-
Таблица "
ContactsContract.Data". - Строки, содержащие сведения о необработанном контакте, например адреса электронной почты или номера телефонов.
Другие таблицы, представленные классами контрактов в ContactsContract, являются вспомогательными. Поставщик контактов использует их для управления своими операциями или поддержки определенных функций в приложениях для работы с контактами или телефонией на устройстве.
Необработанные контакты
Необработанный контакт представляет собой данные пользователя, полученные из одного типа аккаунта и названия аккаунта. Поскольку поставщик контактов позволяет использовать несколько онлайн-сервисов в качестве источника данных для одного человека, он также позволяет использовать несколько необработанных контактов для одного и того же человека. Кроме того, несколько необработанных контактов позволяют пользователю объединять данные одного человека из нескольких аккаунтов одного типа.
Большая часть данных необработанного контакта хранится не в таблице ContactsContract.RawContacts, Вместо этого они хранятся в одной или нескольких строках таблицы ContactsContract.Data. В каждой строке данных есть столбец Data.RAW_CONTACT_ID, содержащий значение RawContacts._ID родительской строки ContactsContract.RawContacts.
Важные столбцы необработанных контактов
Важные столбцы в таблице ContactsContract.RawContacts перечислены в таблице 1. Ознакомьтесь с примечаниями под таблицей.
Таблица 1. Важные столбцы необработанных контактов.
| Название столбца | Использовать | Примечания |
|---|---|---|
ACCOUNT_NAME
|
Название аккаунта для типа аккаунта, который является источником этого необработанного контакта.
Например, название аккаунта Google – это один из адресов Gmail владельца устройства. Подробную информацию вы найдете в следующем разделе, посвященном ACCOUNT_TYPE.
|
Формат названия зависит от типа аккаунта. Это не обязательно адрес электронной почты. |
ACCOUNT_TYPE
|
Тип аккаунта, который является источником этого необработанного контакта. Например, тип аккаунта Google – com.google. Всегда указывайте тип аккаунта с идентификатором домена, которым вы владеете или управляете. Это позволит обеспечить уникальность типа аккаунта.
|
Тип аккаунта, который предлагает данные контактов, обычно имеет связанный адаптер синхронизации, который синхронизируется с поставщиком контактов. |
DELETED
|
Пометка "удалено" для необработанного контакта. | Этот флаг позволяет поставщику контактов хранить строку до тех пор, пока адаптеры синхронизации не удалят ее со своих серверов, а затем удалить ее из хранилища. |
Примечания
Ниже приведены важные примечания о таблице ContactsContract.RawContacts.
-
Имя необработанного контакта не хранится в его строке в таблице
ContactsContract.RawContacts. Вместо этого она хранится в таблицеContactsContract.Dataв строкеContactsContract.CommonDataKinds.StructuredName. В таблицеContactsContract.Dataу необработанного контакта есть только одна строка этого типа. -
Внимание! Чтобы использовать данные своего аккаунта в строке необработанного контакта, сначала зарегистрируйте их в
AccountManager. Для этого попросите пользователей добавить тип аккаунта и название аккаунта в список аккаунтов. Если этого не сделать, поставщик контактов автоматически удалит строку необработанного контакта.Например, если вы хотите, чтобы ваше приложение поддерживало данные контактов для вашего веб-сервиса с доменом
com.example.dataservice, а аккаунт пользователя для вашего сервиса –becky.sharp@dataservice.example.com, пользователь должен сначала добавить "тип" аккаунта (com.example.dataservice) и "название" аккаунта (becky.smart@dataservice.example.com), прежде чем ваше приложение сможет добавлять строки необработанных контактов. Вы можете объяснить это требование пользователю в документации или предложить ему добавить тип и название, или и то, и другое. Типы и названия аккаунтов подробно описаны в следующем разделе.
Источники необработанных контактных данных
Чтобы понять, как работают необработанные контакты, рассмотрим пользователя "Emily Dickinson", у которого на устройстве определены следующие три аккаунта пользователя:
emily.dickinson@gmail.comemilyd@gmail.com- Аккаунт Twitter "belle_of_amherst"
В настройках Аккаунты для всех трех аккаунтов включена функция Синхронизация контактов.
Предположим, Эмили Дикинсон открывает окно браузера, входит в Gmail как emily.dickinson@gmail.com, открывает Контакты и добавляет Томаса Хиггинсона. Позже она входит в Gmail как emilyd@gmail.com и отправляет письмо пользователю Thomas Higginson, который автоматически добавляется в ее список контактов. Она также подписана на аккаунт colonel_tom (идентификатор Томаса Хиггинсона в Твиттере).
В результате поставщик контактов создает три необработанных контакта:
-
Необработанный контакт "Thomas Higginson", связанный с
emily.dickinson@gmail.com. Тип аккаунта пользователя – Google. -
Второй необработанный контакт "Thomas Higginson", связанный с
emilyd@gmail.com. Тип аккаунта пользователя – Google. Второй необработанный контакт имеет такое же имя, как и первый, но относится к другому аккаунту пользователя. - Третий необработанный контакт для Thomas Higginson, связанный с belle_of_amherst. Тип аккаунта пользователя – Twitter.
Данные
Как отмечалось ранее, данные необработанного контакта хранятся в строке ContactsContract.Data, связанной со значением _ID необработанного контакта. Это позволяет одному необработанному контакту иметь несколько экземпляров одного и того же типа данных, например адресов электронной почты или номеров телефонов. Например, если у необработанного контакта "Thomas Higginson" для emilyd@gmail.com (строка необработанного контакта для Thomas Higginson, связанная с аккаунтом Google emilyd@gmail.com) есть домашний адрес электронной почты thigg@gmail.com и рабочий адрес электронной почты thomas.higginson@gmail.com, поставщик контактов сохраняет две строки адресов электронной почты и связывает их с необработанным контактом.
Обратите внимание, что в этой таблице хранятся разные типы данных. Строки с отображаемым названием, номером телефона, адресом электронной почты, почтовым адресом, фотографией и сайтом находятся в таблице ContactsContract.Data. Чтобы упростить работу с таблицей ContactsContract.Data, мы дали некоторым столбцам описательные названия, а другим – общие. Содержимое столбца с описательным названием имеет одинаковое значение независимо от типа данных в строке, а содержимое столбца с общим названием имеет разные значения в зависимости от типа данных.
Описательные названия столбцов
Примеры описательных названий столбцов:
-
RAW_CONTACT_ID -
Значение столбца
_IDнеобработанного контакта для этих данных. -
MIMETYPE -
Тип данных, хранящихся в этой строке, выраженный в виде специального MIME-типа. Поставщик контактов использует MIME-типы, определенные в подклассах
ContactsContract.CommonDataKinds. Эти MIME-типы имеют открытый исходный код и могут использоваться любым приложением или адаптером синхронизации, работающим с поставщиком контактов. -
IS_PRIMARY -
Если для необработанного контакта может быть несколько строк данных этого типа, столбец
IS_PRIMARYпомечает строку, содержащую основные данные этого типа. Например, если пользователь нажмет и будет удерживать номер телефона контакта, а затем выберет Установить по умолчанию, то в строкеContactsContract.Data, содержащей этот номер, в столбцеIS_PRIMARYбудет установлено ненулевое значение.
Общие названия столбцов
Существует 15 общих столбцов с именами от DATA1 до DATA15, которые являются общедоступными, и ещё четыре общих столбца от SYNC1 до SYNC4, которые должны использоваться только адаптерами синхронизации. Константы общих названий столбцов всегда работают независимо от типа данных, содержащихся в строке.
Столбец DATA1 проиндексирован. Поставщик контактов всегда использует этот столбец для данных, которые, по его мнению, будут наиболее частой целью запроса. Например, в строке с адресом электронной почты в этом столбце будет указан сам адрес.
По соглашению столбец DATA15 зарезервирован для хранения данных BLOB (Binary Large Object), например уменьшенных изображений.
Названия столбцов для разных типов
Чтобы упростить работу со столбцами для определенного типа строк, поставщик контактов также предоставляет константы названий столбцов для конкретных типов, определенные в подклассах ContactsContract.CommonDataKinds. Константы просто дают другое название константы тому же названию столбца, что помогает получить доступ к данным в строке определенного типа.
Например, класс ContactsContract.CommonDataKinds.Email определяет константы названий столбцов для строки ContactsContract.Data с MIME-типом Email.CONTENT_ITEM_TYPE. Класс содержит константу ADDRESS для столбца с адресами электронной почты. Фактическое значение ADDRESS – data1, что совпадает с общим названием столбца.
Внимание! Не добавляйте собственные данные в таблицу ContactsContract.Data, используя строку с одним из предопределенных MIME-типов поставщика. В противном случае вы можете потерять данные или вызвать сбой в работе поставщика. Например, не следует добавлять строку с MIME-типом Email.CONTENT_ITEM_TYPE, которая содержит имя пользователя вместо адреса электронной почты в столбце DATA1. Если вы используете собственный MIME-тип для строки, то можете задавать собственные названия столбцов и использовать их по своему усмотрению.
На рисунке 2 показано, как описательные столбцы и столбцы данных отображаются в строке ContactsContract.Data и как названия столбцов, относящихся к определенному типу, "накладываются" на общие названия столбцов.
Рисунок 2. Названия столбцов, относящихся к определенному типу, и общие названия столбцов.
Классы названий столбцов для определенных типов
В таблице 2 перечислены наиболее часто используемые классы названий столбцов для определенных типов:
Таблица 2. Классы названий столбцов для определенных типов
| Класс сопоставления | Тип данных | Примечания |
|---|---|---|
ContactsContract.CommonDataKinds.StructuredName |
Данные имени для необработанного контакта, связанного с этой строкой данных. | У необработанного контакта есть только одна из этих строк. |
ContactsContract.CommonDataKinds.Photo |
Основная фотография необработанного контакта, связанного с этой строкой данных. | У необработанного контакта есть только одна из этих строк. |
ContactsContract.CommonDataKinds.Email |
Адрес электронной почты необработанного контакта, связанного с этой строкой данных. | У необработанного контакта может быть несколько адресов электронной почты. |
ContactsContract.CommonDataKinds.StructuredPostal |
Почтовый адрес необработанного контакта, связанного с этой строкой данных. | У необработанного контакта может быть несколько почтовых адресов. |
ContactsContract.CommonDataKinds.GroupMembership |
Идентификатор, который связывает необработанный контакт с одной из групп в поставщике контактов. | Группы – это необязательная функция, которая зависит от типа и названия аккаунта. Подробнее о них рассказывается в разделе Группы контактов. |
Контакты
Поставщик контактов объединяет строки необработанных контактов по всем типам и названиям аккаунтов, чтобы сформировать контакт. Это упрощает просмотр и изменение всех данных, собранных пользователем для определенного человека. Поставщик контактов управляет созданием новых строк контактов и агрегированием необработанных контактов с существующей строкой контакта. Ни приложения, ни адаптеры синхронизации не могут добавлять контакты, а некоторые столбцы в строке контакта доступны только для чтения.
Примечание. Если вы попытаетесь добавить контакт в поставщик контактов с помощью insert(), то получите исключение UnsupportedOperationException. Если вы попытаетесь изменить столбец, помеченный как "только для чтения", изменения будут проигнорированы.
Поставщик контактов создает новый контакт в ответ на добавление нового необработанного контакта, который не соответствует ни одному из существующих контактов. Это также происходит, если данные существующего необработанного контакта изменяются так, что он больше не соответствует контакту, к которому был прикреплен ранее. Если приложение или адаптер синхронизации создает новый необработанный контакт, который совпадает с существующим, то новый необработанный контакт объединяется с существующим.
Поставщик контактов связывает строку контакта со строками необработанных контактов с помощью столбца _ID в таблице Contacts. Столбец CONTACT_ID таблицы необработанных контактов ContactsContract.RawContacts содержит значения _ID для строки контактов, связанной с каждой строкой необработанных контактов.
В таблице ContactsContract.Contacts также есть столбец LOOKUP_KEY, который является постоянной ссылкой на строку контакта. Поскольку поставщик контактов управляет контактами автоматически, он может изменить значение _ID в строке контакта в результате агрегирования или синхронизации. Даже если это произойдет, URI контента CONTENT_LOOKUP_URI в сочетании с LOOKUP_KEY контакта по-прежнему будет указывать на строку контакта, поэтому вы можете использовать LOOKUP_KEY для сохранения ссылок на избранные контакты и т. д. Этот столбец имеет собственный формат, не связанный с форматом столбца _ID.
На рисунке 3 показано, как связаны между собой три основные таблицы.
Рисунок 3. Связи между таблицами Contacts, Raw Contacts и Details.
Внимание! Если вы публикуете приложение в Google Play или оно установлено на устройстве с Android 10 (уровень API 29) или более поздней версии, помните, что некоторые поля данных и методы, связанные с контактами, устарели.
При соблюдении указанных условий система периодически очищает все значения, записанные в следующие поля данных:
-
ContactsContract.ContactOptionsColumns.LAST_TIME_CONTACTED -
ContactsContract.ContactOptionsColumns.TIMES_CONTACTED -
ContactsContract.DataUsageStatColumns.LAST_TIME_USED -
ContactsContract.DataUsageStatColumns.TIMES_USED
API, используемые для настройки вышеуказанных полей данных, также устарели:
Кроме того, следующие поля больше не возвращают часто используемые контакты: Обратите внимание, что некоторые из этих полей влияют на ранжирование контактов, только если контакты относятся к определенному типу данных.
-
ContactsContract.Contacts.CONTENT_FREQUENT_URI -
ContactsContract.Contacts.CONTENT_STREQUENT_URI -
ContactsContract.Contacts.CONTENT_STREQUENT_FILTER_URI -
CONTENT_FILTER_URI(влияет только на типы данных Email, Phone, Callable и Contactables). -
ENTERPRISE_CONTENT_FILTER_URI(влияет только на типы данных Email, Phone и Callable).
Если ваши приложения обращаются к этим полям или API или обновляют их, используйте альтернативные методы. Например, в некоторых случаях можно использовать частных поставщиков контента или другие данные, хранящиеся в приложении или серверных системах.
Чтобы убедиться, что это изменение не повлияет на работу приложения, вы можете вручную очистить эти поля данных. Для этого выполните следующую команду ADB на устройстве с Android 4.1 (уровень API 16) или более поздней версии:
adb shell content delete \ --uri content://com.android.contacts/contacts/delete_usage
Данные из адаптеров синхронизации
Пользователи вводят данные контактов непосредственно на устройстве, но данные также поступают в поставщик контактов из веб-сервисов через адаптеры синхронизации, которые автоматизируют передачу данных между устройством и сервисами. Адаптеры синхронизации работают в фоновом режиме под управлением системы и вызывают методы ContentResolver для управления данными.
В Android веб-сервис, с которым работает адаптер синхронизации, определяется типом аккаунта. Каждый адаптер синхронизации работает с одним типом аккаунта, но может поддерживать несколько названий аккаунтов для этого типа. Типы и названия аккаунтов кратко описаны в разделе Источники данных необработанных контактов. В приведенных ниже определениях содержится более подробная информация и описывается, как тип и имя аккаунта связаны с адаптерами и сервисами синхронизации.
- Тип аккаунта
-
Идентифицирует сервис, в котором пользователь хранит данные. В большинстве случаев пользователю необходимо выполнить аутентификацию в сервисе. Например, Google Контакты – это тип аккаунта, который обозначается кодом
google.com. Это значение соответствует типу аккаунта, который используется вAccountManager. - Название аккаунта
- Идентифицирует определенный аккаунт или учетные данные для типа аккаунта. Аккаунты Google Контактов – это то же самое, что и аккаунты Google, в которых в качестве имени аккаунта используется адрес электронной почты. В других сервисах имя пользователя может состоять из одного слова или числового идентификатора.
Типы аккаунтов не обязательно должны быть уникальными. Пользователь может настроить несколько аккаунтов Google Контактов и скачать их данные в поставщик контактов. Это может произойти, если у пользователя есть один набор личных контактов для личного аккаунта и другой – для рабочего. Обычно названия аккаунтов уникальны. Вместе они идентифицируют определенный поток данных между поставщиком контактов и внешним сервисом.
Если вы хотите перенести данные своего сервиса в поставщик контактов, вам нужно написать собственный адаптер синхронизации. Подробнее об этом рассказывается в разделе Адаптеры синхронизации поставщика контактов.
На рисунке 4 показано, как поставщик контактов вписывается в поток данных о людях. В поле "Адаптеры синхронизации" каждый адаптер помечен типом аккаунта.
Рисунок 4. Поток данных поставщика контактов.
Требуемые права доступа
Приложения, которым требуется доступ к поставщику контактов, должны запрашивать следующие разрешения:
- Доступ на чтение к одной или нескольким таблицам.
-
READ_CONTACTS, указанный вAndroidManifest.xmlс элементом<uses-permission>как<uses-permission android:name="android.permission.READ_CONTACTS">. - Доступ на запись к одной или нескольким таблицам.
-
WRITE_CONTACTS, указанный вAndroidManifest.xmlс элементом<uses-permission>как<uses-permission android:name="android.permission.WRITE_CONTACTS">.
Эти разрешения не распространяются на данные профиля пользователя. Профиль пользователя и необходимые разрешения описаны в разделе Профиль пользователя.
Помните, что контакты пользователя – это личные и конфиденциальные данные. Пользователи беспокоятся о конфиденциальности, поэтому не хотят, чтобы приложения собирали данные о них или их контактах. Если пользователям будет непонятно, зачем вам нужен доступ к их контактам, они могут поставить вашему приложению низкую оценку или просто отказаться от его установки.
Профиль пользователя
Таблица ContactsContract.Contacts содержит одну строку с данными профиля пользователя устройства. Эти данные описывают user устройства, а не контакт пользователя. Строка контактов профиля связана со строкой необработанных контактов для каждой системы, использующей профиль.
У каждой строки необработанного контакта профиля может быть несколько строк данных. Константы для доступа к профилю пользователя доступны в классе ContactsContract.Profile.
Для доступа к профилю пользователя требуются специальные разрешения. Помимо разрешений READ_CONTACTS и WRITE_CONTACTS, необходимых для чтения и записи, для доступа к профилю пользователя требуются разрешения android.Manifest.permission#READ_PROFILE и android.Manifest.permission#WRITE_PROFILE для чтения и записи соответственно.
Помните, что профиль пользователя содержит конфиденциальную информацию. Разрешение android.Manifest.permission#READ_PROFILE позволяет получить доступ к персональным данным пользователя устройства. В описании приложения обязательно укажите, зачем вам нужны разрешения на доступ к профилю пользователя.
Чтобы получить строку контакта, содержащую профиль пользователя, вызовите метод ContentResolver.query(). Задайте URI контента CONTENT_URI и не указывайте критерии выбора. Вы также можете использовать этот URI контента в качестве базового URI для получения необработанных контактов или данных профиля. Например, этот фрагмент кода извлекает данные для профиля:
Kotlin
// Sets the columns to retrieve for the user profile projection = arrayOf( ContactsContract.Profile._ID, ContactsContract.Profile.DISPLAY_NAME_PRIMARY, ContactsContract.Profile.LOOKUP_KEY, ContactsContract.Profile.PHOTO_THUMBNAIL_URI ) // Retrieves the profile from the Contacts Provider profileCursor = contentResolver.query( ContactsContract.Profile.CONTENT_URI, projection, null, null, null )
Java
// Sets the columns to retrieve for the user profile projection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.LOOKUP_KEY, Profile.PHOTO_THUMBNAIL_URI }; // Retrieves the profile from the Contacts Provider profileCursor = getContentResolver().query( Profile.CONTENT_URI, projection , null, null, null);
Примечание. Если вы извлекаете несколько строк контактов и хотите определить, является ли одна из них профилем пользователя, проверьте столбец IS_USER_PROFILE этой строки. Если контакт является профилем пользователя, в этом столбце указывается значение "1".
Метаданные поставщика контактов
Поставщик контактов управляет данными, которые отслеживают состояние данных контактов в хранилище. Эти метаданные о хранилище хранятся в разных местах, включая строки таблиц Raw Contacts, Data и Contacts, а также таблицы ContactsContract.Settings и ContactsContract.SyncState. В таблице ниже показано, как эти метаданные влияют на показ рекламы.
Таблица 3. Метаданные в поставщике контактов
| Таблица | Столбец | Значения | Значение |
|---|---|---|---|
ContactsContract.RawContacts |
DIRTY |
0 – не изменилось с момента последней синхронизации. |
Отмечает необработанные контакты, которые были изменены на устройстве и должны быть синхронизированы с сервером. Значение устанавливается автоматически поставщиком контактов, когда приложения Android обновляют строку.
Адаптеры синхронизации, которые изменяют необработанные контакты или таблицы данных, должны всегда добавлять строку |
| "1" – изменено с момента последней синхронизации, необходимо синхронизировать с сервером. | |||
ContactsContract.RawContacts |
VERSION |
Номер версии этой строки. | Поставщик контактов автоматически увеличивает это значение при изменении строки или связанных с ней данных. |
ContactsContract.Data |
DATA_VERSION |
Номер версии этой строки. | Поставщик контактов автоматически увеличивает это значение при каждом изменении строки данных. |
ContactsContract.RawContacts |
SOURCE_ID |
Строковое значение, которое уникально идентифицирует этот необработанный контакт в аккаунте, в котором он был создан. |
Когда адаптер синхронизации создает новый необработанный контакт, в этом столбце должен быть указан уникальный идентификатор сервера для необработанного контакта. При создании нового необработанного контакта приложение для Android должно оставить этот столбец пустым. Это указывает адаптеру синхронизации, что он должен создать новый необработанный контакт на сервере и получить значение для SOURCE_ID.
В частности, идентификатор источника должен быть уникальным для каждого типа аккаунта и не должен меняться при синхронизации.
|
ContactsContract.Groups |
GROUP_VISIBLE |
"0" – контакты из этой группы не должны быть видны в интерфейсе приложений Android. | Этот столбец предназначен для совместимости с серверами, которые позволяют пользователю скрывать контакты в определенных группах. |
| "1" – контакты из этой группы могут показываться в интерфейсах приложений. | |||
ContactsContract.Settings |
UNGROUPED_VISIBLE |
0 – для этого аккаунта и типа аккаунта контакты, не принадлежащие к группе, невидимы в интерфейсах приложений для Android. |
По умолчанию контакты невидимы, если ни один из их необработанных контактов не принадлежит к группе.
(Принадлежность необработанного контакта к группе обозначается одной или несколькими строками
ContactsContract.CommonDataKinds.GroupMembership
в таблице ContactsContract.Data).
Установив этот флаг в строке таблицы ContactsContract.Settings для типа аккаунта и аккаунта, вы можете принудительно сделать видимыми контакты без групп.
Этот флаг можно использовать, чтобы показывать контакты с серверов, на которых не используются группы.
|
| "1" – для этого аккаунта и типа аккаунта контакты, которые не принадлежат ни к одной группе, видны в интерфейсах приложений. | |||
ContactsContract.SyncState |
(все) | Используйте эту таблицу для хранения метаданных адаптера синхронизации. | С помощью этой таблицы вы можете хранить состояние синхронизации и другие связанные с ней данные на устройстве. |
Доступ к поставщику контактов
В этом разделе описываются рекомендации по доступу к данным от поставщика контактов, в частности:
- Запросы об объектах.
- Пакетное изменение.
- Извлечение и изменение с помощью намерений.
- Целостность данных.
Подробнее о том, как вносить изменения с помощью адаптера синхронизации, рассказывается в разделе Адаптеры синхронизации поставщика контактов.
Запрос объектов
Поскольку таблицы поставщика контактов организованы в виде иерархии, часто бывает полезно извлечь строку и все связанные с ней дочерние строки. Например, чтобы показать всю информацию о пользователе, вам может потребоваться получить все строки ContactsContract.RawContacts для одной строки ContactsContract.Contacts или все строки ContactsContract.CommonDataKinds.Email для одной строки ContactsContract.RawContacts. Для этого поставщик контактов предлагает конструкции сущностей, которые действуют как соединения между таблицами базы данных.
Объект – это таблица, состоящая из выбранных столбцов родительской и дочерней таблиц.
При запросе объекта вы указываете проекцию и критерии поиска на основе столбцов, доступных в объекте. В результате вы получите объект Cursor, содержащий по одной строке для каждой извлеченной строки дочерней таблицы. Например, если вы запрашиваете у ContactsContract.Contacts.Entity имя контакта и все строки ContactsContract.CommonDataKinds.Email для всех необработанных контактов с этим именем, вы получите Cursor, содержащий по одной строке для каждой строки ContactsContract.CommonDataKinds.Email.
Объекты упрощают запросы. С помощью объекта можно получить все данные о контакте или необработанном контакте сразу, не запрашивая сначала идентификатор из родительской таблицы, а затем данные из дочерней таблицы по этому идентификатору. Кроме того, поставщик контактов обрабатывает запрос к объекту в рамках одной транзакции, что обеспечивает внутреннюю согласованность полученных данных.
Примечание. Обычно объект не содержит все столбцы родительской и дочерней таблиц. Если вы попытаетесь использовать название столбца, которого нет в списке констант для объекта, то получите ошибку Exception.
В следующем фрагменте кода показано, как получить все строки необработанных контактов для контакта. Этот фрагмент кода является частью более крупного приложения, в котором есть два действия: "main" и "detail". В основном действии показывается список контактов. Когда пользователь выбирает один из них, действие отправляет его идентификатор в действие с подробными сведениями. В подробных сведениях используется ContactsContract.Contacts.Entity, чтобы показать все строки данных из всех необработанных контактов, связанных с выбранным контактом.
Этот фрагмент взят из действия detail:
Kotlin
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY ) // Initializes the loader identified by LOADER_ID. loaderManager.initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this // The context of the activity ) // Creates a new cursor adapter to attach to the list view cursorAdapter = SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0) // flags // Sets the ListView's backing adapter. rawContactList.adapter = cursorAdapter ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ val projection: Array<String> = arrayOf( ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE ) /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC" /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return CursorLoader( applicationContext, // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder // Sort by the raw contact ID. ) }
Java
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); // Initializes the loader identified by LOADER_ID. getLoaderManager().initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this); // The context of the activity // Creates a new cursor adapter to attach to the list view cursorAdapter = new SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0); // flags // Sets the ListView's backing adapter. rawContactList.setAdapter(cursorAdapter); ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ String[] projection = { ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE }; /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return new CursorLoader( getApplicationContext(), // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder); // Sort by the raw contact ID. }
После завершения загрузки LoaderManager вызывает обратный вызов для onLoadFinished(). Одним из входящих аргументов этого метода является Cursor с результатами запроса. В своем приложении вы можете получить данные из этого объекта Cursor, чтобы показать их или продолжить работу с ними.
Пакетное изменение
По возможности вставляйте, обновляйте и удаляйте данные в поставщике контактов в пакетном режиме, создавая ArrayList объектов ContentProviderOperation и вызывая applyBatch(). Поскольку поставщик контактов выполняет все операции в applyBatch() в рамках одной транзакции, ваши изменения никогда не приведут к несогласованности данных в хранилище контактов. Пакетное изменение также упрощает одновременную вставку необработанного контакта и его подробных данных.
Примечание. Чтобы изменить один необработанный контакт, отправьте намерение в приложение для управления контактами на устройстве, а не вносите изменения в своем приложении. Подробнее об этом рассказывается в разделе Получение и изменение данных с помощью намерений.
Точки приостановки
Пакетное изменение, содержащее большое количество операций, может заблокировать другие процессы, что приведет к ухудшению общего пользовательского опыта. Чтобы объединить все изменения в как можно меньшее количество списков и при этом не заблокировать систему, задайте точки приостановки для одной или нескольких операций.
Точка приостановки – это объект ContentProviderOperation, у которого значение isYieldAllowed() равно true. Когда поставщик контактов сталкивается с точкой приостановки, он приостанавливает свою работу, чтобы позволить другим процессам выполняться, и закрывает текущую транзакцию. При повторном запуске поставщик продолжает работу со следующей операцией в ArrayList и начинает новую транзакцию.
Точки приостановки приводят к тому, что за один вызов applyBatch() выполняется несколько транзакций. Поэтому для последней операции в наборе связанных строк следует задать точку приостановки.
Например, точку приостановки следует установить для последней операции в наборе, который добавляет строки необработанного контакта и связанные с ними строки данных, или для последней операции в наборе строк, связанных с одним контактом.
Точки приостановки также являются единицей атомарной операции. Все обращения между двумя точками приостановки будут либо выполнены, либо отклонены как единое целое. Если вы не зададите точки приостановки, наименьшей атомарной операцией будет весь пакет операций. Если вы используете точки приостановки, то предотвращаете снижение производительности системы и в то же время обеспечиваете атомарность подмножества операций.
Обратные ссылки на изменения
Когда вы вставляете новую строку необработанного контакта и связанные с ней строки данных в виде набора объектов ContentProviderOperation, вам нужно связать строки данных со строкой необработанного контакта, вставив значение _ID необработанного контакта в качестве значения RAW_CONTACT_ID. Однако это значение недоступно при создании ContentProviderOperation для строки данных, поскольку вы ещё не применили ContentProviderOperation для строки необработанного контакта. Чтобы обойти это ограничение, в классе ContentProviderOperation.Builder предусмотрен метод withValueBackReference().
Этот метод позволяет вставить или изменить столбец с результатом предыдущей операции.
Метод withValueBackReference() имеет два аргумента:
-
key - Ключ пары "ключ-значение". Значением этого аргумента должно быть название столбца в таблице, которую вы изменяете.
-
previousResult -
Индекс значения в массиве объектов
ContentProviderResultизapplyBatch(). Нумерация начинается с 0. По мере выполнения пакетных операций результат каждой из них сохраняется в промежуточном массиве результатов. ЗначениеpreviousResult– это индекс одного из этих результатов, который извлекается и сохраняется со значениемkey. Это позволяет вставить новую запись необработанного контакта, получить ее значение_ID, а затем сделать обратную ссылку на это значение при добавлении строкиContactsContract.Data.Весь массив результатов создается при первом вызове
applyBatch(). Его размер равен размеруArrayListобъектовContentProviderOperation, которые вы предоставляете. Однако всем элементам в массиве результатов присваивается значениеnull, и если вы попытаетесь сослаться на результат операции, которая ещё не была выполнена,withValueBackReference()выдаст ошибкуException.
В следующих фрагментах кода показано, как вставить новый необработанный контакт и данные в пакет. Они содержат код, который устанавливает точку приостановки и использует обратную ссылку.
Первый фрагмент извлекает данные о контактах из пользовательского интерфейса. К этому моменту пользователь уже выбрал аккаунт, в который нужно добавить новый необработанный контакт.
Kotlin
// Creates a contact entry from the current UI values, using the currently-selected account. private fun createContactEntry() { /* * Gets values from the UI */ val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition] val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]
Java
// Creates a contact entry from the current UI values, using the currently-selected account. protected void createContactEntry() { /* * Gets values from the UI */ String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); int phoneType = contactPhoneTypes.get( contactPhoneTypeSpinner.getSelectedItemPosition()); int emailType = contactEmailTypes.get( contactEmailTypeSpinner.getSelectedItemPosition());
Следующий фрагмент кода создает операцию по вставке строки необработанного контакта в таблицу ContactsContract.RawContacts:
Kotlin
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. val ops = arrayListOf<ContentProviderOperation>() /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ var op: ContentProviderOperation.Builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Builds the operation and adds it to the array of operations ops.add(op.build());
Затем код создает строки данных для отображаемого имени, телефона и электронной почты.
Каждый объект-конструктор операций использует withValueBackReference(), чтобы получить RAW_CONTACT_ID. Ссылка указывает на объект ContentProviderResult из первой операции, которая добавляет строку необработанного контакта и возвращает его новое значение _ID. В результате каждая строка данных автоматически связывается по RAW_CONTACT_ID с новой строкой ContactsContract.RawContacts, к которой она относится.
Объект ContentProviderOperation.Builder, который добавляет строку письма, помечен как withYieldAllowed(), что задает точку приостановки:
Kotlin
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType) /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true); // Builds the operation and adds it to the array of operations ops.add(op.build());
В последнем фрагменте показан вызов applyBatch(), который вставляет новые строки необработанного контакта и данных.
Kotlin
// Ask the Contacts Provider to create a new contact Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})") Log.d(TAG, "Creating contact: $name") /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) } catch (e: Exception) { // Display a warning val txt: String = getString(R.string.contactCreationFailure) Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show() // Log exception Log.e(TAG, "Exception encountered while inserting contact: $e") } }
Java
// Ask the Contacts Provider to create a new contact Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" + selectedAccount.getType() + ")"); Log.d(TAG,"Creating contact: " + name); /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // Display a warning Context ctx = getApplicationContext(); CharSequence txt = getString(R.string.contactCreationFailure); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(ctx, txt, duration); toast.show(); // Log exception Log.e(TAG, "Exception encountered while inserting contact: " + e); } }
Пакетные операции также позволяют реализовать оптимистичное управление параллелизмом – метод применения транзакций модификации без необходимости блокировать базовое хранилище. Чтобы использовать этот метод, примените транзакцию, а затем проверьте, не были ли одновременно внесены другие изменения. Если вы обнаружите, что произошло несогласованное изменение, вы откатите транзакцию и повторите попытку.
Оптимистичное управление параллелизмом полезно для мобильных устройств, где одновременно работает только один пользователь и одновременный доступ к хранилищу данных происходит редко. Поскольку блокировка не используется, время не тратится на установку блокировок или ожидание снятия блокировок другими транзакциями.
Чтобы использовать оптимистичное управление параллелизмом при обновлении одной строки ContactsContract.RawContacts, выполните следующие действия:
-
Извлеките столбец
VERSIONнеобработанного контакта вместе с другими данными. -
Создайте объект
ContentProviderOperation.Builder, подходящий для применения ограничения, с помощью методаnewAssertQuery(Uri). В качестве URI контента используйтеRawContacts.CONTENT_URIс добавленным к нему_IDнеобработанного контакта. -
Для объекта
ContentProviderOperation.Builderвызовите методwithValue(), чтобы сравнить столбецVERSIONс номером версии, который вы только что получили. -
Для того же
ContentProviderOperation.BuilderвызовитеwithExpectedCount(), чтобы убедиться, что это утверждение проверяет только одну строку. -
Вызовите
build(), чтобы создать объектContentProviderOperation, а затем добавьте его в качестве первого объекта вArrayList, который вы передаете вapplyBatch(). - Примените пакетную транзакцию.
Если строка необработанного контакта будет обновлена другой операцией между моментом, когда вы прочитали строку, и моментом, когда вы попытаетесь ее изменить, ContentProviderOperation не сработает и вся группа операций будет отменена. После этого вы можете повторить попытку или выполнить другое действие.
В следующем фрагменте кода показано, как создать утверждение ContentProviderOperation после запроса одного необработанного контакта с помощью CursorLoader:
Kotlin
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)) mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)) } ... // Sets up a Uri for the assert operation val rawContactUri: Uri = ContentUris.withAppendedId( ContactsContract.RawContacts.CONTENT_URI, rawContactID ) // Creates a builder for the assert operation val assertOp: ContentProviderOperation.Builder = ContentProviderOperation.newAssertQuery(rawContactUri).apply { // Adds the assertions to the assert operation: checks the version withValue(SyncColumns.VERSION, mVersion) // and count of rows tested withExpectedCount(1) } // Creates an ArrayList to hold the ContentProviderOperation objects val ops = arrayListOf<ContentProviderOperation>() ops.add(assertOp.build()) // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops) } catch (e: OperationApplicationException) { // Actions you want to take if the assert operation fails go here }
Java
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); } ... // Sets up a Uri for the assert operation Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID); // Creates a builder for the assert operation ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri); // Adds the assertions to the assert operation: checks the version and count of rows tested assertOp.withValue(SyncColumns.VERSION, mVersion); assertOp.withExpectedCount(1); // Creates an ArrayList to hold the ContentProviderOperation objects ArrayList ops = new ArrayList<ContentProviderOperation>; ops.add(assertOp.build()); // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { ContentProviderResult[] results = getContentResolver().applyBatch(AUTHORITY, ops); } catch (OperationApplicationException e) { // Actions you want to take if the assert operation fails go here }
Извлечение и изменение с помощью намерений
Отправка намерения в приложение для управления контактами позволяет получить косвенный доступ к поставщику контактов. Намерение запускает интерфейс приложения для управления контактами, в котором пользователи могут выполнять действия с контактами. Пользователи с таким доступом могут:
- Выберите контакт из списка и верните его в приложение для дальнейшей работы.
- Измените данные существующего контакта.
- Вставлять новые необработанные контакты в любой из аккаунтов.
- Удалять контакты и их данные.
Если пользователь вставляет или обновляет данные, вы можете сначала собрать их и отправить как часть намерения.
Если вы используете намерения для доступа к поставщику контактов через приложение для управления контактами на устройстве, вам не нужно создавать собственный интерфейс или код для доступа к поставщику. Вам также не нужно запрашивать разрешение на чтение или запись у поставщика. Приложение для управления контактами на устройстве может делегировать вам разрешение на чтение контакта, и, поскольку вы вносите изменения в поставщик через другое приложение, вам не нужно иметь разрешения на запись.
Общий процесс отправки намерения для доступа к поставщику подробно описан в разделе "Доступ к данным через намерения" руководства
Основы работы с поставщиками контента. Действие, MIME-тип и значения данных, которые вы используете для доступных задач, приведены в таблице 4, а дополнительные значения, которые вы можете использовать с putExtra(), перечислены в справочной документации по ContactsContract.Intents.Insert:
Таблица 4. Намерения поставщика контактов.
| Задача | Действие | Данные | MIME-тип | Примечания |
|---|---|---|---|---|
| Как выбрать контакт из списка | ACTION_PICK |
Один из вариантов:
|
Не используется |
Показывает список необработанных контактов или список данных из необработанного контакта в зависимости от указанного типа URI контента.
Вызовите метод |
| Как вставить новый необработанный контакт | Insert.ACTION |
Н/Д |
RawContacts.CONTENT_TYPE, MIME-тип для набора необработанных контактов.
|
Открывает экран Добавить контакт в приложении для управления контактами на устройстве. Появятся дополнительные значения, которые вы добавили в намерение. Если вы отправили намерение с помощью метода startActivityForResult(), URI контента нового необработанного контакта будет передан обратно в метод обратного вызова onActivityResult() вашего действия в аргументе Intent в поле data. Чтобы получить значение, вызовите getData().
|
| Как изменить контакт | ACTION_EDIT |
CONTENT_LOOKUP_URI для контакта. Операция редактирования позволит пользователю изменять любые данные, связанные с этим контактом.
|
Contacts.CONTENT_ITEM_TYPE, один контакт. |
Открывает экран изменения контакта в приложении для управления контактами. Появятся дополнительные значения, которые вы добавили в намерение. Когда пользователь нажимает Готово, чтобы сохранить изменения, ваше действие возвращается на передний план. |
| Показать окно выбора, которое также может добавлять данные. | ACTION_INSERT_OR_EDIT |
Н/Д |
CONTENT_ITEM_TYPE
|
Это намерение всегда открывает экран выбора в приложении "Контакты". Пользователь может выбрать контакт для редактирования или добавить новый. В зависимости от выбора пользователя откроется экран редактирования или добавления, и на нем будут показаны дополнительные данные, которые вы передаете в намерении. Если в вашем приложении отображаются контактные данные, например адрес электронной почты или номер телефона, используйте это намерение, чтобы пользователь мог добавить их в существующий контакт.
контакт,
Примечание. В дополнительных данных этого намерения не нужно указывать значение имени, поскольку пользователь всегда выбирает существующее имя или добавляет новое. Кроме того, если вы отправите имя, а пользователь решит его изменить, в приложении "Контакты" будет показано отправленное вами имя, которое перезапишет предыдущее значение. Если пользователь не заметит этого и сохранит изменения, старое значение будет потеряно. |
Приложение "Контакты" на устройстве не позволяет удалять необработанные контакты или их данные с помощью намерения. Чтобы удалить необработанный контакт, используйте ContentResolver.delete() или ContentProviderOperation.newDelete().
В следующем фрагменте кода показано, как создать и отправить намерение, которое вставляет новый необработанный контакт и данные:
Kotlin
// Gets values from the UI val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val company = companyName.text.toString() val jobtitle = jobTitle.text.toString() /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row val contactData = arrayListOf<ContentValues>() /* * Defines the raw contact row */ // Sets up the row as a ContentValues object val rawContactRow = ContentValues().apply { // Adds the account type and name to the row put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type) put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name) } // Adds the row to the array contactData.add(rawContactRow) /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object val phoneRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Adds the phone number and its type to the row put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) } // Adds the row to the array contactData.add(phoneRow) /* * Sets up the email data row */ // Sets up the row as a ContentValues object val emailRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Adds the email address and its type to the row put(ContactsContract.CommonDataKinds.Email.ADDRESS, email) } // Adds the row to the array contactData.add(emailRow) // Creates a new intent for sending to the device's contacts application val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply { // Sets the MIME type to the one expected by the insertion activity type = ContactsContract.RawContacts.CONTENT_TYPE // Sets the new contact name putExtra(ContactsContract.Intents.Insert.NAME, name) // Sets the new company and job title putExtra(ContactsContract.Intents.Insert.COMPANY, company) putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle) /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData) } // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent)
Java
// Gets values from the UI String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); String company = companyName.getText().toString(); String jobtitle = jobTitle.getText().toString(); // Creates a new intent for sending to the device's contacts application Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); // Sets the MIME type to the one expected by the insertion activity insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); // Sets the new contact name insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); // Sets the new company and job title insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); /* * Defines the raw contact row */ // Sets up the row as a ContentValues object ContentValues rawContactRow = new ContentValues(); // Adds the account type and name to the row rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()); rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Adds the row to the array contactData.add(rawContactRow); /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object ContentValues phoneRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) phoneRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ); // Adds the phone number and its type to the row phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); // Adds the row to the array contactData.add(phoneRow); /* * Sets up the email data row */ // Sets up the row as a ContentValues object ContentValues emailRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) emailRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ); // Adds the email address and its type to the row emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); // Adds the row to the array contactData.add(emailRow); /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent);
Целостность данных
Поскольку в хранилище контактов содержатся важные и конфиденциальные данные, которые должны быть точными и актуальными, для поставщика контактов действуют строгие правила обеспечения целостности данных. Вы должны соблюдать эти правила при изменении данных контактов. Ниже приведены основные правила.
-
Всегда добавляйте строку
ContactsContract.CommonDataKinds.StructuredNameдля каждой строкиContactsContract.RawContacts. -
Строка
ContactsContract.RawContactsбез строкиContactsContract.CommonDataKinds.StructuredNameв таблицеContactsContract.Dataможет вызвать проблемы при агрегировании. -
Всегда связывайте новые строки
ContactsContract.Dataс родительской строкойContactsContract.RawContacts. -
Строка
ContactsContract.Data, не связанная сContactsContract.RawContacts, не будет видна в приложении для управления контактами на устройстве и может вызвать проблемы с адаптерами синхронизации. - Изменяйте данные только тех необработанных контактов, которые принадлежат вам.
- Помните, что поставщик контактов обычно управляет данными из нескольких разных типов аккаунтов или онлайн-сервисов. Убедитесь, что ваше приложение изменяет или удаляет данные только в строках, которые принадлежат вам, и вставляет данные только с типом и названием аккаунта, которые вы контролируете.
-
Всегда используйте константы, определенные в
ContactsContractи его подклассах для источников, URI контента, путей URI, названий столбцов, MIME-типов и значенийTYPE. - Использование этих констант помогает избежать ошибок. Если какая-либо из констант устарела, вы также получите предупреждение компилятора.
Строки специальных данных
Создавая и используя собственные MIME-типы, вы можете вставлять, редактировать, удалять и извлекать собственные строки данных в таблице ContactsContract.Data. В строках можно использовать только столбцы, определенные в ContactsContract.DataColumns, но вы можете сопоставить названия столбцов, относящихся к определенному типу, с названиями столбцов по умолчанию. В приложении для управления контактами на устройстве данные из ваших строк будут показываться, но их нельзя будет изменить или удалить. Пользователи также не смогут добавлять дополнительные данные. Чтобы пользователи могли изменять строки с вашими данными, вы должны предоставить в своем приложении операцию редактирования.
Чтобы показать собственные данные, предоставьте файл contacts.xml, содержащий элемент <ContactsAccountType> и один или несколько его дочерних элементов <ContactsDataKind>. Подробнее об этом рассказывается в разделе <ContactsDataKind> element.
Подробнее о специальных MIME-типах рассказывается в руководстве Как создать поставщика контента.
Адаптеры синхронизации поставщика контактов
Поставщик контактов специально разработан для синхронизации данных о контактах между устройством и онлайн-сервисом. Это позволяет пользователям скачивать существующие данные на новое устройство и загружать их в новый аккаунт. Синхронизация также гарантирует, что у пользователей всегда будут под рукой актуальные данные, независимо от источника дополнений и изменений. Ещё одно преимущество синхронизации заключается в том, что она делает данные контактов доступными, даже когда устройство не подключено к сети.
Хотя вы можете реализовать синхронизацию различными способами, система Android предоставляет подключаемую платформу синхронизации, которая автоматизирует следующие задачи:
- Проверка доступности сети.
- Планирование и выполнение синхронизации на основе предпочтений пользователя.
- Перезапуск остановленных синхронизаций.
Чтобы использовать этот фреймворк, вам нужно предоставить плагин адаптера синхронизации. Каждый адаптер синхронизации уникален для сервиса и поставщика контента, но может обрабатывать несколько названий аккаунтов для одного и того же сервиса. Фреймворк также позволяет использовать несколько адаптеров синхронизации для одного сервиса и поставщика.
Классы и файлы адаптера синхронизации
Адаптер синхронизации реализуется как подкласс AbstractThreadedSyncAdapter и устанавливается как часть приложения Android. Система получает информацию об адаптере синхронизации из элементов манифеста приложения и из специального XML-файла, на который указывает манифест. XML-файл определяет тип аккаунта для онлайн-сервиса и полномочия поставщика контента, которые вместе однозначно идентифицируют адаптер. Адаптер синхронизации не активируется, пока пользователь не добавит аккаунт для типа аккаунта адаптера синхронизации и не включит синхронизацию для поставщика контента, с которым синхронизируется адаптер синхронизации. После этого система начнет управлять адаптером, вызывая его по мере необходимости для синхронизации между поставщиком контента и сервером.
Примечание. Использование типа аккаунта в качестве части идентификатора адаптера синхронизации позволяет системе обнаруживать и группировать адаптеры синхронизации, которые обращаются к разным сервисам одной организации. Например, адаптеры синхронизации для онлайн-сервисов Google имеют один и тот же тип аккаунта – com.google. Когда пользователи добавляют аккаунт Google на свои устройства, все установленные адаптеры синхронизации для сервисов Google отображаются в одном списке. Каждый из них синхронизируется с отдельным поставщиком контента на устройстве.
Поскольку большинство сервисов требуют от пользователей подтверждения личности перед доступом к данным, система Android предлагает платформу аутентификации, которая похожа на платформу адаптера синхронизации и часто используется вместе с ней. Фреймворк аутентификации использует подключаемые модули аутентификации, которые являются подклассами AbstractAccountAuthenticator. Аутентификатор подтверждает личность пользователя, выполняя следующие действия:
- Собирает имя, пароль или другие учетные данные пользователя.
- Отправит учетные данные сервису.
- Проверяет ответ сервиса.
Если сервис принимает учетные данные, аутентификатор может сохранить их для последующего использования. Благодаря фреймворку подключаемого аутентификатора AccountManager может предоставлять доступ к любым токенам аутентификации, которые поддерживает и предоставляет аутентификатор, например к токенам OAuth2.
Хотя аутентификация не требуется, большинство сервисов контактов используют ее. Однако использовать фреймворк аутентификации Android необязательно.
Реализация адаптера синхронизации
Чтобы реализовать адаптер синхронизации для поставщика контактов, сначала создайте приложение для Android, которое содержит следующее:
-
Компонент
Service, который отвечает на запросы системы о привязке к адаптеру синхронизации. -
Когда системе нужно выполнить синхронизацию, она вызывает метод
onBind()сервиса, чтобы получить объектIBinderдля адаптера синхронизации. Это позволяет системе выполнять межпроцессные вызовы методов адаптера. -
Фактический адаптер синхронизации, реализованный как конкретный подкласс
AbstractThreadedSyncAdapter. -
Этот класс выполняет скачивание данных с сервера, загрузку данных с устройства и разрешение конфликтов. Основная работа адаптера выполняется в методе
onPerformSync(). Этот класс должен быть создан как синглтон. -
Подкласс
Application. -
Этот класс выступает в качестве фабрики для синглтона адаптера синхронизации. Используйте метод
onCreate(), чтобы создать экземпляр адаптера синхронизации, и предоставьте статический метод получения, чтобы вернуть синглтон методуonBind()сервиса адаптера синхронизации. -
Необязательно. Компонент
Service, который отвечает на запросы системы об аутентификации пользователя. -
AccountManagerзапускает этот сервис, чтобы начать процесс аутентификации. МетодonCreate()сервиса создает экземпляр объекта аутентификатора. Когда системе нужно выполнить аутентификацию аккаунта пользователя для адаптера синхронизации приложения, она вызывает методonBind()сервиса, чтобы получитьIBinderдля аутентификатора. Это позволяет системе выполнять межпроцессные вызовы методов аутентификатора. -
Необязательно. Конкретный подкласс
AbstractAccountAuthenticator, который обрабатывает запросы на аутентификацию. -
Этот класс предоставляет методы, которые
AccountManagerвызывает для аутентификации учетных данных пользователя на сервере. Особенности процесса аутентификации зависят от используемой серверной технологии. Дополнительную информацию об аутентификации можно найти в документации к серверному ПО. - XML-файлы, которые определяют адаптер синхронизации и аутентификатор для системы.
-
Компоненты адаптера синхронизации и сервиса аутентификации, описанные выше, определяются в элементах
<service>манифеста приложения. Эти элементы содержат дочерние элементы<meta-data>, которые передают в систему определенные данные:-
Элемент
<meta-data>для сервиса адаптера синхронизации указывает на XML-файлres/xml/syncadapter.xml. В свою очередь, в этом файле указывается URI веб-сервиса, который будет синхронизироваться с поставщиком контактов, и тип аккаунта для веб-сервиса. -
Необязательно. Элемент
<meta-data>для аутентификатора указывает на XML-файлres/xml/authenticator.xml. В свою очередь, этот файл указывает тип аккаунта, который поддерживает этот аутентификатор, а также ресурсы пользовательского интерфейса, которые появляются в процессе аутентификации. Тип аккаунта, указанный в этом элементе, должен совпадать с типом аккаунта, указанным для адаптера синхронизации.
-
Элемент
Данные из социальных сетей
Таблицы android.provider.ContactsContract.StreamItems и android.provider.ContactsContract.StreamItemPhotos управляют входящими данными из социальных сетей. Вы можете написать адаптер синхронизации, который добавляет данные потока из вашей собственной сети в эти таблицы, или вы можете читать данные потока из этих таблиц и отображать их в своем собственном приложении, или и то, и другое. С помощью этих функций ваши сервисы и приложения социальных сетей могут быть интегрированы в социальные сети Android.
Текст в социальных сетях
Элементы ленты всегда связаны с необработанным контактом. android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID ссылается на значение _ID необработанного контакта. Тип и название аккаунта необработанного контакта также хранятся в строке элемента ленты.
Сохраните данные из потока в следующих столбцах:
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
- Обязательный параметр. Тип аккаунта пользователя для необработанного контакта, связанного с этим элементом ленты. Не забудьте задать это значение при вставке элемента ленты.
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
- Обязательный параметр. Название аккаунта пользователя для необработанного контакта, связанного с этим элементом ленты. Не забудьте задать это значение при вставке элемента ленты.
- Столбцы идентификаторов
-
Обязательный параметр. При вставке элемента потока необходимо добавить следующие столбцы идентификаторов:
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: значение android.provider.BaseColumns#_ID контакта, с которым связан этот элемент ленты.
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: значение android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY контакта, с которым связан этот элемент ленты.
- android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: значение android.provider.BaseColumns#_ID необработанного контакта, с которым связан этот элемент ленты.
- android.provider.ContactsContract.StreamItemsColumns#COMMENTS
- Необязательный параметр. Содержит сводную информацию, которая может показываться в начале элемента ленты.
- android.provider.ContactsContract.StreamItemsColumns#TEXT
-
Текст записи в ленте, то есть контент, опубликованный источником записи,
или описание действия, которое привело к созданию записи. Этот столбец может содержать любое форматирование и встроенные изображения ресурсов, которые могут быть отображены
fromHtml(). Поставщик может усечь или сократить длинный контент, но постарается не нарушить теги. - android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
- Текстовая строка, содержащая время добавления или обновления элемента ленты в миллисекундах с начала эпохи. Приложения, которые вставляют или обновляют элементы ленты, отвечают за ведение этого столбца. Он не поддерживается поставщиком контактов автоматически.
Чтобы отобразить идентифицирующую информацию для элементов ленты, используйте android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL и android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE для ссылки на ресурсы в вашем приложении.
Таблица android.provider.ContactsContract.StreamItems также содержит столбцы android.provider.ContactsContract.StreamItemsColumns#SYNC1 – android.provider.ContactsContract.StreamItemsColumns#SYNC4, предназначенные исключительно для адаптеров синхронизации.
Фотографии из социальных сетей
В таблице android.provider.ContactsContract.StreamItemPhotos хранятся фотографии, связанные с элементом ленты. Столбец android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID этой таблицы связан со значениями в столбце _ID таблицы android.provider.ContactsContract.StreamItems. Ссылки на фотографии хранятся в таблице в следующих столбцах:
- android.provider.ContactsContract.StreamItemPhotos#PHOTO (BLOB).
- Двоичное представление фотографии, размер которой изменен поставщиком для хранения и показа. Этот столбец доступен для обратной совместимости с предыдущими версиями поставщика контактов, в которых он использовался для хранения фотографий. Однако в текущей версии не следует использовать этот столбец для хранения фотографий. Вместо этого используйте android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID или android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (оба описаны ниже), чтобы хранить фотографии в файле. Теперь в этом столбце содержится уменьшенное изображение фотографии, доступное для чтения.
- android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
-
Числовой идентификатор фотографии необработанного контакта. Добавьте это значение к константе
DisplayPhoto.CONTENT_URI, чтобы получить URI контента, указывающий на один файл фотографии, а затем вызовитеopenAssetFileDescriptor(), чтобы получить дескриптор файла фотографии. - android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
-
URI контента, указывающий непосредственно на файл фотографии, представленной в этой строке.
Вызовите
openAssetFileDescriptor()с этим URI, чтобы получить дескриптор файла фотографии.
Как использовать таблицы социальных сетей
Эти таблицы работают так же, как и другие основные таблицы в поставщике контактов, за исключением следующего:
- Для работы с этими таблицами требуются дополнительные разрешения на доступ. Чтобы читать их, у приложения должно быть разрешение android.Manifest.permission#READ_SOCIAL_STREAM. Чтобы изменить их, у вашего приложения должно быть разрешение android.Manifest.permission#WRITE_SOCIAL_STREAM.
-
Для таблицы android.provider.ContactsContract.StreamItems количество строк, хранящихся для каждого необработанного контакта, ограничено. Когда этот лимит будет достигнут, поставщик контактов освободит место для новых строк, автоматически удалив строки с самыми старыми значениями android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP. Чтобы узнать ограничение, отправьте запрос к URI контента android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Для всех аргументов, кроме URI контента, можно задать значение
null. Запрос возвращает объект Cursor, содержащий одну строку с одним столбцом android.provider.ContactsContract.StreamItems#MAX_ITEMS.
Класс android.provider.ContactsContract.StreamItems.StreamItemPhotos определяет подтаблицу android.provider.ContactsContract.StreamItemPhotos, содержащую строки с фотографиями для одного элемента ленты.
Взаимодействия в социальных сетях
Данные ленты социальных сетей, управляемые поставщиком контактов, в сочетании с приложением для управления контактами на устройстве позволяют эффективно связать вашу систему социальных сетей с существующими контактами. Доступны следующие варианты:
- Синхронизировав социальную сеть с поставщиком контактов с помощью адаптера синхронизации, вы можете получать информацию о недавних действиях контактов пользователя и сохранять ее в таблицах android.provider.ContactsContract.StreamItems и android.provider.ContactsContract.StreamItemPhotos для последующего использования.
- Помимо регулярной синхронизации, вы можете настроить адаптер синхронизации так, чтобы он получал дополнительные данные, когда пользователь выбирает контакт для просмотра. Это позволяет адаптеру синхронизации получать фотографии высокого разрешения и самые последние элементы ленты для контакта.
- Зарегистрировав уведомление в приложении для управления контактами и поставщике контактов, вы сможете получать намерение при просмотре контакта и обновлять его статус из своего сервиса. Этот подход может быть быстрее и использовать меньше пропускной способности, чем полная синхронизация с адаптером синхронизации.
- Пользователи могут добавлять контакты в вашу социальную сеть, просматривая их в приложении для управления контактами на устройстве. Для этого используется функция "Пригласить контакт", которая активируется с помощью комбинации действия, добавляющего существующий контакт в вашу сеть, и XML-файла, предоставляющего приложению для управления контактами и поставщику контактов сведения о вашем приложении.
Регулярная синхронизация элементов ленты с поставщиком контактов выполняется так же, как и другие синхронизации. Подробнее о синхронизации рассказывается в разделе Адаптеры синхронизации поставщика контактов. Регистрация уведомлений и приглашение контактов рассматриваются в следующих двух разделах.
Регистрация для обработки просмотров в социальных сетях
Чтобы зарегистрировать адаптер синхронизации для получения уведомлений, когда пользователь просматривает контакт, управляемый вашим адаптером синхронизации:
-
Создайте файл с названием
contacts.xmlв каталогеres/xml/проекта. Если у вас уже есть этот файл, пропустите этот шаг. -
Добавьте в этот файл элемент
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Если этот элемент уже существует, вы можете пропустить этот шаг. -
Чтобы зарегистрировать сервис, который будет получать уведомления, когда пользователь открывает страницу приложения контакта в приложении для управления контактами на устройстве, добавьте атрибут
viewContactNotifyService="serviceclass"в элемент, гдеserviceclass– полное имя класса сервиса, который должен получать интент от приложения для управления контактами на устройстве. Для сервиса уведомлений используйте класс, расширяющийIntentService, чтобы сервис мог получать интенты. Входящее намерение содержит URI контента необработанного контакта, на который нажал пользователь. Из сервиса уведомлений можно выполнить привязку к адаптеру синхронизации и вызвать его, чтобы обновить данные необработанного контакта.
Чтобы зарегистрировать действие, которое будет вызываться, когда пользователь нажимает на элемент ленты, фотографию или и то, и другое, выполните следующие действия:
-
Создайте файл с названием
contacts.xmlв каталогеres/xml/проекта. Если у вас уже есть этот файл, пропустите этот шаг. -
Добавьте в этот файл элемент
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Если этот элемент уже существует, вы можете пропустить этот шаг. -
Чтобы зарегистрировать одно из действий для обработки нажатия пользователем на элемент потока в приложении для управления контактами на устройстве, добавьте атрибут
viewStreamItemActivity="activityclass"к элементу, гдеactivityclass– полное имя класса действия, которое должно получать намерение от приложения для управления контактами на устройстве. -
Чтобы зарегистрировать одно из действий для обработки нажатия пользователем на фотографию в ленте в приложении для управления контактами на устройстве, добавьте атрибут
viewStreamItemPhotoActivity="activityclass"к элементу, гдеactivityclass– полное имя класса действия, которое должно получить намерение от приложения для управления контактами на устройстве.
Элемент <ContactsAccountType> подробно описан в разделе Элемент<ContactsAccountType>.
Входящее намерение содержит URI контента объекта или фотографии, на которую нажал пользователь. Чтобы создать отдельные действия для текстовых элементов и фотографий, используйте оба атрибута в одном файле.
Взаимодействие с социальной сетью
Пользователям не нужно выходить из приложения для управления контактами, чтобы пригласить контакт в вашу социальную сеть. Вместо этого вы можете настроить отправку намерения из приложения "Контакты" на устройстве, чтобы пригласить контакт в одно из ваших действий. Как настроить контролируемый аккаунт:
-
Создайте файл с названием
contacts.xmlв каталогеres/xml/проекта. Если у вас уже есть этот файл, пропустите этот шаг. -
Добавьте в этот файл элемент
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Если этот элемент уже существует, вы можете пропустить этот шаг. -
Добавьте следующие атрибуты:
inviteContactActivity="activityclass"-
inviteContactActionLabel="@string/invite_action_label"
activityclass– это полное название класса действия, которое должно получить намерение. Значениеinvite_action_label– это текстовая строка, которая показывается в меню Добавить подключение в приложении для управления контактами на устройстве.
Примечание. ContactsSource – это устаревшее название тега ContactsAccountType.
Справочник по contacts.xml
Файл contacts.xml содержит элементы XML, которые управляют взаимодействием адаптера синхронизации и приложения с приложением для управления контактами и поставщиком контактов. Эти элементы описаны в следующих разделах.
Элемент <ContactsAccountType>
Элемент <ContactsAccountType> управляет взаимодействием вашего приложения с приложением для управления контактами. Синтаксис:
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android" inviteContactActivity="activity_name" inviteContactActionLabel="invite_command_text" viewContactNotifyService="view_notify_service" viewGroupActivity="group_view_activity" viewGroupActionLabel="group_action_text" viewStreamItemActivity="viewstream_activity_name" viewStreamItemPhotoActivity="viewphotostream_activity_name">
содержится в:
res/xml/contacts.xml
может содержать:
<ContactsDataKind>
Описание:
Объявляет компоненты Android и ярлыки пользовательского интерфейса, которые позволяют пользователям приглашать контакты в социальную сеть, уведомлять пользователей об обновлении ленты в социальной сети и т. д.
Обратите внимание, что префикс атрибута android: не требуется для атрибутов <ContactsAccountType>.
Атрибуты:
inviteContactActivity- Полное название класса действия в приложении, которое должно выполняться, когда пользователь выбирает Добавить связь в приложении для управления контактами на устройстве.
inviteContactActionLabel-
Текстовая строка, которая показывается для действия, указанного в
inviteContactActivity, в меню Добавить подключение. Например, можно использовать строку "Подписаться на мою сеть". Для этого ярлыка можно использовать идентификатор строкового ресурса. viewContactNotifyService- Полное имя класса сервиса в вашем приложении, который должен получать уведомления, когда пользователь просматривает контакт. Это уведомление отправляется приложением для управления контактами на устройстве. Оно позволяет вашему приложению отложить операции, требующие большого объема данных, до тех пор, пока они не понадобятся. Например, ваше приложение может реагировать на это уведомление, считывая и отображая фотографию контакта в высоком разрешении и последние элементы социальной ленты. Подробнее об этой функции рассказывается в разделе Взаимодействие с лентой социальных сетей.
viewGroupActivity- Полное название класса действия в приложении, которое может показывать информацию о группе. Когда пользователь нажимает на ярлык группы в приложении для управления контактами, открывается интерфейс этого действия.
viewGroupActionLabel-
Ярлык, который приложение для управления контактами показывает для элемента управления пользовательского интерфейса, позволяющего пользователю просматривать группы в вашем приложении.
Для этого атрибута разрешен идентификатор строкового ресурса.
viewStreamItemActivity- Полное имя класса действия в приложении, которое запускается приложением для управления контактами на устройстве, когда пользователь нажимает на элемент ленты для необработанного контакта.
viewStreamItemPhotoActivity- Полное имя класса действия в приложении, которое запускается приложением для управления контактами на устройстве, когда пользователь нажимает на фотографию в элементе ленты для необработанного контакта.
Элемент <ContactsDataKind>
Элемент <ContactsDataKind> управляет отображением строк пользовательских данных приложения в интерфейсе приложения для управления контактами. Синтаксис:
<ContactsDataKind android:mimeType="MIMEtype" android:icon="icon_resources" android:summaryColumn="column_name" android:detailColumn="column_name">
содержится в:
<ContactsAccountType>
Описание:
Используйте этот элемент, чтобы приложение для управления контактами показывало содержимое строки специальных данных в сведениях о необработанном контакте. Каждый дочерний элемент <ContactsDataKind> элемента <ContactsAccountType> представляет собой тип строки пользовательских данных, которую адаптер синхронизации добавляет в таблицу ContactsContract.Data. Добавьте по одному элементу <ContactsDataKind> для каждого используемого вами специального MIME-типа. Если вы не хотите показывать данные для определенной строки, добавлять этот элемент не нужно.
Атрибуты:
android:mimeType-
Специальный MIME-тип, который вы определили для одного из типов строк пользовательских данных в таблице
ContactsContract.Data. Например, значениеvnd.android.cursor.item/vnd.example.locationstatusможет быть пользовательским MIME-типом для строки данных, в которой записывается последнее известное местоположение контакта. android:icon- Прорисовываемый ресурс Android, который приложение для управления контактами показывает рядом с вашими данными. Это позволит пользователю понять, что данные получены из вашего сервиса.
android:summaryColumn- Название столбца для первого из двух значений, полученных из строки данных. Значение отображается в первой строке записи для этой строки данных. Первая строка предназначена для сводки данных, но это необязательно. См. также android:detailColumn.
android:detailColumn-
Название столбца для второго из двух значений, полученных из строки данных. Значение отображается во второй строке записи для этой строки данных. Дополнительная информация:
android:summaryColumn.
Дополнительные функции поставщика контактов
Помимо основных функций, описанных в предыдущих разделах, поставщик контактов предлагает следующие полезные функции для работы с данными контактов:
- Группы контактов
- Функции для съемки и обработки фотографий
Группы контактов
Поставщик контактов может помечать наборы связанных контактов данными группы. Если сервер, связанный с аккаунтом пользователя, должен поддерживать группы, адаптер синхронизации для типа аккаунта должен передавать данные групп между поставщиком контактов и сервером. Когда пользователи добавляют новый контакт на сервер, а затем помещают его в новую группу, адаптер синхронизации должен добавить новую группу в таблицу ContactsContract.Groups. Группа или группы, к которым относится необработанный контакт, хранятся в таблице ContactsContract.Data с использованием MIME-типа ContactsContract.CommonDataKinds.GroupMembership.
Если вы разрабатываете адаптер синхронизации, который будет добавлять необработанные контактные данные с сервера поставщику контактов, и вы не используете группы, вам нужно указать поставщику, чтобы он сделал ваши данные видимыми. В коде, который выполняется, когда пользователь добавляет аккаунт на устройство, обновите строку ContactsContract.Settings, которую поставщик контактов добавляет для аккаунта. В этой строке задайте для столбца Settings.UNGROUPED_VISIBLE значение 1. В этом случае поставщик контактов всегда будет показывать данные контактов, даже если вы не используете группы.
Фотографии контакта
В таблице ContactsContract.Data фотографии хранятся в виде строк с MIME-типом Photo.CONTENT_ITEM_TYPE. Столбец CONTACT_ID строки связан со столбцом _ID необработанного контакта, к которому он относится.
Класс ContactsContract.Contacts.Photo определяет подтаблицу ContactsContract.Contacts, содержащую информацию об основном фото контакта, которое является основным фото основного необработанного контакта. Аналог класса ContactsContract.RawContacts.DisplayPhoto определяет подтаблицу ContactsContract.RawContacts, содержащую информацию об основном фото необработанного контакта.
В справочной документации по ContactsContract.Contacts.Photo и ContactsContract.RawContacts.DisplayPhoto приведены примеры получения информации о фотографиях. Класса удобства для получения основного значка необработанного контакта нет, но вы можете отправить запрос в таблицу ContactsContract.Data, выбрав столбец _ID необработанного контакта, Photo.CONTENT_ITEM_TYPE и IS_PRIMARY, чтобы найти строку основной фотографии необработанного контакта.
Данные из ленты социальной сети для пользователя могут также включать фотографии. Они хранятся в таблице android.provider.ContactsContract.StreamItemPhotos, которая подробно описана в разделе Фотографии из ленты.