Интеграция контактов (ContactsProvider) в Android-приложение

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Интеграция контактов (ContactsProvider) в Android-приложение
Простой
от 1 дня до 3 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Интеграция контактов (ContactsProvider) в Android-приложение

Android хранит контакты в трёхслойной схеме: RawContacts (запись от конкретного аккаунта — Google, Telegram, телефона), Contacts (агрегат из нескольких RawContacts) и Data (конкретные поля: номер, email, фото). Большинство разработчиков работают только с таблицей Contacts и Data, игнорируя агрегацию — и получают дубликаты.

Разрешения

READ_CONTACTS и WRITE_CONTACTSdangerous permissions, запрашиваются в рантайме. На Android 11+ добавляется нюанс: если пользователь отклонил разрешение дважды, повторный запрос ActivityCompat.requestPermissions() не откроет диалог — нужно вести пользователя в настройки приложения через ACTION_APPLICATION_DETAILS_SETTINGS.

Чтение контактов

Чтение через ContactsContract.CommonDataKinds:

val projection = arrayOf(
    ContactsContract.Contacts._ID,
    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
    ContactsContract.Contacts.HAS_PHONE_NUMBER
)

val cursor = context.contentResolver.query(
    ContactsContract.Contacts.CONTENT_URI,
    projection,
    null, null,
    "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} ASC"
)

cursor?.use {
    while (it.moveToNext()) {
        val id = it.getLong(it.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
        val name = it.getString(it.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY))
        val hasPhone = it.getInt(it.getColumnIndexOrThrow(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0

        if (hasPhone) {
            val phoneCursor = context.contentResolver.query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
                "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?",
                arrayOf(id.toString()),
                null
            )
            phoneCursor?.use { pc ->
                if (pc.moveToFirst()) {
                    val number = pc.getString(0)
                    // сохранение
                }
            }
        }
    }
}

Два вложенных запроса на каждый контакт — рабочий, но медленный подход. На списке 500+ контактов это даст ANR на слабых устройствах при выполнении на main thread. Решение — CursorLoader или Coroutines + Dispatchers.IO с Flow.

Производительный подход через Data URI

Вместо двойного запроса — один запрос к ContactsContract.Data.CONTENT_URI с фильтром по MIMETYPE:

val dataCursor = context.contentResolver.query(
    ContactsContract.Data.CONTENT_URI,
    arrayOf(
        ContactsContract.Data.CONTACT_ID,
        ContactsContract.Data.DISPLAY_NAME,
        ContactsContract.CommonDataKinds.Phone.NUMBER
    ),
    "${ContactsContract.Data.MIMETYPE} = ?",
    arrayOf(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
    "${ContactsContract.Data.DISPLAY_NAME} ASC"
)

Один запрос возвращает все контакты с телефонами сразу. На реальном проекте с 1200 контактами это сократило время загрузки с 3.2 секунды до 180 мс.

Создание контакта

val ops = ArrayList<ContentProviderOperation>()

ops.add(
    ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
        .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
        .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
        .build()
)

ops.add(
    ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
        .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
        .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
        .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Иван Петров")
        .build()
)

ops.add(
    ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
        .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
        .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
        .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, "+7 999 123 45 67")
        .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
        .build()
)

context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)

applyBatch — атомарная операция. Если один из шагов упал — вся транзакция откатывается. withValueBackReference(RAW_CONTACT_ID, 0) — ссылается на _ID из результата первой операции в батче, без него новый RawContact и данные не связаны.

Нюансы, которые забывают

На Android 13+ (API 33) появился READ_CONTACTS флаг для выборочного доступа — пользователь может предоставить доступ только к части контактов. Приложение должно корректно работать с этим ограничением и не ожидать полного списка.

Фото контакта — отдельный запрос через ContactsContract.Contacts.openContactPhotoInputStream(). Не стоит загружать фото в основном запросе — это сильно бьёт по памяти при большом списке.

Сроки интеграции: чтение контактов — 1 день, CRUD + синхронизация с аккаунтом — до 5 дней. Стоимость рассчитывается индивидуально после анализа требований.