Поставщик контактов – это мощный и гибкий компонент 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- Аккаунт в Твиттере "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());
Затем код создает строки данных для отображаемого имени, телефона и электронной почты.
Каждый объект OperationBuilder использует 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 assert после запроса одного необработанного контакта с помощью 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.RawContactsвсегда добавляйте строкуContactsContract.CommonDataKinds.StructuredName. -
Строка
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 thought
-
Текст записи в ленте, который может быть как контентом, опубликованным источником записи,
так и описанием действия, в результате которого была создана запись. Этот столбец может содержать любое форматирование и встроенные изображения ресурсов, которые могут быть отображены
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, чтобы сервис мог получать намерения. Входящий объект Intent содержит 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">
contained in:
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">
contained in:
<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, которая подробно описана в разделе Фотографии в ленте.