dostawca kontaktów

Dostawca kontaktów to zaawansowany i elastyczny komponent Androida, który zarządza centralnym repozytorium danych o osobach na urządzeniu. Dostawca kontaktów jest źródłem danych, które widzisz w aplikacji do obsługi kontaktów na urządzeniu. Możesz też uzyskać dostęp do tych danych we własnej aplikacji i przesyłać je między urządzeniem a usługami online. Dostawca obsługuje szeroki zakres źródeł danych i stara się zarządzać jak największą ilością danych każdej osoby, co sprawia, że jego organizacja jest złożona. Z tego powodu interfejs API dostawcy zawiera obszerny zestaw klas kontraktowych i interfejsów, które ułatwiają zarówno pobieranie, jak i modyfikowanie danych.

W tym przewodniku opisano:

  • Podstawowa struktura dostawcy.
  • jak pobrać dane od dostawcy.
  • Jak zmodyfikować dane u dostawcy.
  • Jak napisać adapter synchronizacji, aby synchronizować dane z serwera z dostawcą kontaktów.

W tym przewodniku zakładamy, że znasz podstawy mechanizmów dostarczania treści na Androidzie. Aby dowiedzieć się więcej o mechanizmach dostarczania treści na Androidzie, przeczytaj przewodnik Podstawowe informacje o mechanizmach dostarczania treści.

Organizacja dostawcy kontaktów

Dostawca kontaktów to komponent mechanizmu dostarczania treści na Androidzie. Przechowuje 3 typy danych o osobie, z których każdy odpowiada tabeli oferowanej przez dostawcę, jak pokazano na ilustracji 1:

Rysunek 1. Struktura tabeli dostawcy kontaktów.

Te 3 tabele są zwykle nazywane nazwami ich klas kontraktowych. Klasy definiują stałe dla identyfikatorów URI treści, nazw kolumn i wartości kolumn używanych przez tabele:

ContactsContract.Contacts tabela
Wiersze reprezentujące różne osoby na podstawie agregacji wierszy kontaktów bazowych.
ContactsContract.RawContacts tabela
Wiersze zawierające podsumowanie danych osoby, dotyczące konkretnego konta i typu użytkownika.
ContactsContract.Data tabela
Wiersze zawierające szczegóły kontaktu bazowego, takie jak adresy e-mail czy numery telefonów.

Pozostałe tabele reprezentowane przez klasy kontraktowe w ContactsContract to tabele pomocnicze, których dostawca kontaktów używa do zarządzania swoimi operacjami IT lub obsługi określonych funkcji w aplikacjach do obsługi kontaktów lub połączeń telefonicznych na urządzeniu.

Kontakty bazowe

Kontakt bazowy reprezentuje dane osoby pochodzące z jednego rodzaju konta i nazwy konta. Dostawca kontaktów umożliwia korzystanie z więcej niż jednej usługi online jako źródła danych o osobie, dlatego może on mieć wiele kontaktów bazowych dotyczących tej samej osoby. Wiele kontaktów bazowych umożliwia też użytkownikowi łączenie danych osoby z więcej niż 1 konta tego samego rodzaju konta.

Większość danych kontaktu bazowego nie jest przechowywana w tabeli ContactsContract.RawContacts. Zamiast tego jest przechowywana w co najmniej 1 wierszu tabeli ContactsContract.Data. Każdy wiersz danych ma kolumnę Data.RAW_CONTACT_ID, która zawiera wartość RawContacts._ID nadrzędnego wiersza ContactsContract.RawContacts.

Ważne kolumny kontaktów bazowych

Ważne kolumny w tabeli ContactsContract.RawContacts są wymienione w tabeli 1. Zapoznaj się z uwagami pod tabelą:

Tabela 1. Ważne kolumny kontaktów bazowych.

Nazwa kolumny Użyj Uwagi
ACCOUNT_NAME Nazwa konta dla rodzaju konta, które jest źródłem tego kontaktu bazowego. Na przykład nazwa konta Google jest jednym z adresów Gmail właściciela urządzenia. Więcej informacji znajdziesz w następnym wpisie dotyczącym ACCOUNT_TYPE. Format tej nazwy zależy od rodzaju konta. Nie musi to być adres e-mail.
ACCOUNT_TYPE Rodzaj konta, które jest źródłem tego kontaktu bazowego. Na przykład typ konta Google to com.google. Zawsze określaj rodzaj konta za pomocą identyfikatora domeny, której jesteś właścicielem lub którą kontrolujesz. Dzięki temu rodzaj konta będzie unikalny. Rodzaj konta, który oferuje dane kontaktów, zwykle ma powiązany adapter synchronizacji, który synchronizuje się z dostawcą kontaktów.
DELETED Oznaczenie „usunięty” kontaktu bazowego. Ta flaga umożliwia dostawcy kontaktów wewnętrzne przechowywanie wiersza do momentu, gdy adaptery synchronizacji będą mogły usunąć go ze swoich serwerów, a następnie z repozytorium.

Uwagi

Poniżej znajdziesz ważne uwagi dotyczące tabeli ContactsContract.RawContacts:

  • Imię i nazwisko kontaktu bazowego nie są przechowywane w jego wierszu w ContactsContract.RawContacts. Zamiast tego jest przechowywana w tabeli ContactsContract.Data, w wierszu ContactsContract.CommonDataKinds.StructuredName. Kontakt bazowy ma tylko 1 wiersz tego typu w tabeli ContactsContract.Data.
  • Uwaga: aby używać danych własnego konta w wierszu kontaktu bazowego, musisz najpierw zarejestrować je w AccountManager. Aby to zrobić, poproś użytkowników o dodanie rodzaju konta i nazwy konta do listy kont. Jeśli tego nie zrobisz, dostawca kontaktów automatycznie usunie wiersz kontaktu bazowego.

    Jeśli na przykład chcesz, aby aplikacja przechowywała dane kontaktów dla usługi internetowej z domeną com.example.dataservice, a konto użytkownika w Twojej usłudze to becky.sharp@dataservice.example.com, użytkownik musi najpierw dodać „typ” konta (com.example.dataservice) i „nazwę” konta (becky.smart@dataservice.example.com), zanim aplikacja będzie mogła dodać wiersze kontaktów bazowych. Możesz wyjaśnić użytkownikowi to wymaganie w dokumentacji lub poprosić go o dodanie typu i nazwy (lub obu tych informacji). Rodzaje kont i nazwy kont zostały szczegółowo opisane w następnej sekcji.

Źródła danych kontaktów bazowych

Aby zrozumieć, jak działają kontakty bazowe, weźmy pod uwagę użytkowniczkę „Emily Dickinson”, która ma na urządzeniu zdefiniowane te 3 konta:

  • emily.dickinson@gmail.com
  • emilyd@gmail.com
  • Konto na Twitterze „belle_of_amherst”

Ten użytkownik włączył opcję Synchronizuj kontakty dla wszystkich 3 kont w ustawieniach Konta.

Załóżmy, że Emily Dickinson otwiera okno przeglądarki, loguje się w Gmailu jako emily.dickinson@gmail.com, otwiera Kontakty i dodaje kontakt „Thomas Higginson”. Później loguje się w Gmailu jako emilyd@gmail.com i wysyła e-maila do „Thomasa Higginsona”, który automatycznie zostaje dodany jako kontakt. Obserwuje też „colonel_tom” (identyfikator Thomasa Higginsona na Twitterze) na Twitterze.

W wyniku tego działania dostawca kontaktów tworzy 3 kontakty bazowe:

  1. Kontakt bazowy „Thomas Higginson” powiązany z aplikacją emily.dickinson@gmail.com. Rodzaj konta użytkownika to Google.
  2. Drugi kontakt bazowy dla „Thomasa Higginsona” powiązany z emilyd@gmail.com. Rodzaj konta użytkownika to również Google. Istnieje drugi kontakt bazowy, mimo że nazwa jest identyczna z poprzednią, ponieważ osoba została dodana do innego konta użytkownika.
  3. Trzeci kontakt bazowy dla „Thomas Higginson” powiązany z „belle_of_amherst”. Rodzaj konta użytkownika to Twitter.

Dane

Jak wspomnieliśmy wcześniej, dane kontaktu bazowego są przechowywane w wierszu ContactsContract.Data, który jest powiązany z wartością _ID kontaktu bazowego. Dzięki temu jeden kontakt bazowy może mieć wiele wystąpień tego samego typu danych, takich jak adresy e-mail czy numery telefonów. Jeśli na przykład „Thomas Higginson” dla emilyd@gmail.com (wiersz kontaktu bazowego dla Thomasa Higginsona powiązany z kontem Google emilyd@gmail.com) ma domowy adres e-mail thigg@gmail.com i służbowy adres e-mail thomas.higginson@gmail.com, dostawca kontaktów przechowuje 2 wiersze adresów e-mail i łączy je z kontaktem bazowym.

Zwróć uwagę, że w tej jednej tabeli przechowywane są różne typy danych. Wiersze z wyświetlaną nazwą, numerem telefonu, adresem e-mail, adresem pocztowym, zdjęciem i witryną znajdują się w tabeli ContactsContract.Data. Aby ułatwić zarządzanie tymi danymi, tabela ContactsContract.Data zawiera kolumny z nazwami opisowymi i ogólnymi. Zawartość kolumny z nazwą opisową ma takie samo znaczenie niezależnie od typu danych w wierszu, natomiast zawartość kolumny z nazwą ogólną ma różne znaczenia w zależności od typu danych.

Opisowe nazwy kolumn

Oto kilka przykładów opisowych nazw kolumn:

RAW_CONTACT_ID
Wartość kolumny _ID kontaktu bazowego dla tych danych.
MIMETYPE
Typ danych przechowywanych w tym wierszu, wyrażony jako niestandardowy typ MIME. Dostawca kontaktów używa typów MIME zdefiniowanych w podklasach ContactsContract.CommonDataKinds. Te typy MIME są open source i mogą być używane przez dowolną aplikację lub adapter synchronizacji, który współpracuje z dostawcą kontaktów.
IS_PRIMARY
Jeśli ten typ wiersza danych może wystąpić więcej niż raz w przypadku kontaktu bazowego, kolumna IS_PRIMARY oznacza wiersz danych, który zawiera podstawowe dane dla tego typu. Jeśli na przykład użytkownik naciśnie i przytrzyma numer telefonu kontaktu, a następnie wybierze Ustaw jako domyślny, wiersz ContactsContract.Data zawierający ten numer będzie miał w kolumnie IS_PRIMARY wartość inną niż zero.

Ogólne nazwy kolumn

Dostępnych jest 15 ogólnych kolumn o nazwach od DATA1 do DATA15 oraz 4 dodatkowe kolumny ogólne od SYNC1 do SYNC4, które powinny być używane tylko przez adaptery synchronizacji. Stałe ogólnych nazw kolumn działają zawsze, niezależnie od typu danych zawartych w wierszu.

Kolumna DATA1 jest zindeksowana. Dostawca kontaktów zawsze używa tej kolumny do przechowywania danych, które według niego będą najczęstszym celem zapytania. Na przykład w wierszu adresu e-mail ta kolumna zawiera rzeczywisty adres e-mail.

Zgodnie z konwencją kolumna DATA15 jest zarezerwowana do przechowywania danych typu duży obiekt binarny (BLOB), takich jak miniatury zdjęć.

Nazwy kolumn zależne od typu

Aby ułatwić pracę z kolumnami dla określonego typu wiersza, dostawca kontaktów udostępnia też stałe nazw kolumn dla poszczególnych typów, zdefiniowane w podklasach ContactsContract.CommonDataKinds. Stałe nadają inną nazwę stałej tej samej nazwie kolumny, co ułatwia dostęp do danych w wierszu określonego typu.

Na przykład klasa ContactsContract.CommonDataKinds.Email definiuje stałe nazwy kolumn dla wiersza ContactsContract.Data, który ma typ MIME Email.CONTENT_ITEM_TYPE. Klasa zawiera stałą ADDRESS dla kolumny adresu e-mail. Rzeczywista wartość ADDRESS to „data1”, czyli taka sama jak ogólna nazwa kolumny.

Uwaga: nie dodawaj własnych danych niestandardowych do tabeli ContactsContract.Data za pomocą wiersza, który ma jeden ze wstępnie zdefiniowanych typów MIME dostawcy. Jeśli to zrobisz, możesz utracić dane lub spowodować nieprawidłowe działanie dostawcy. Nie dodawaj na przykład wiersza z typem MIME Email.CONTENT_ITEM_TYPE, który zawiera nazwę użytkownika zamiast adresu e-mail w kolumnie DATA1. Jeśli używasz własnego niestandardowego typu MIME dla wiersza, możesz zdefiniować własne nazwy kolumn dla danego typu i używać ich w dowolny sposób.

Rysunek 2 pokazuje, jak kolumny opisowe i kolumny danych wyglądają w wierszu ContactsContract.Data oraz jak nazwy kolumn dotyczące konkretnego typu „nakładają się” na ogólne nazwy kolumn.

Jak nazwy kolumn zależne od typu są mapowane na ogólne nazwy kolumn

Rysunek 2. Nazwy kolumn zależne od typu i nazwy ogólne.

Klasy nazw kolumn zależne od typu

Tabela 2 zawiera najczęściej używane klasy nazw kolumn dla poszczególnych typów:

Tabela 2. Klasy nazw kolumn zależne od typu

Klasa mapowania Typ danych Uwagi
ContactsContract.CommonDataKinds.StructuredName Dane imienia i nazwiska kontaktu bazowego powiązanego z tym wierszem danych. Kontakt bazowy ma tylko jeden z tych wierszy.
ContactsContract.CommonDataKinds.Photo Główne zdjęcie kontaktu bazowego powiązanego z tym wierszem danych. Kontakt bazowy ma tylko jeden z tych wierszy.
ContactsContract.CommonDataKinds.Email Adres e-mail kontaktu bazowego powiązanego z tym wierszem danych. Kontakt bazowy może mieć wiele adresów e-mail.
ContactsContract.CommonDataKinds.StructuredPostal Adres pocztowy kontaktu bazowego powiązanego z tym wierszem danych. Kontakt bazowy może mieć kilka adresów pocztowych.
ContactsContract.CommonDataKinds.GroupMembership Identyfikator, który łączy kontakt bazowy z jedną z grup u dostawcy kontaktów. Grupy są opcjonalną funkcją rodzaju konta i nazwy konta. Zostały one szczegółowo opisane w sekcji Grupy kontaktów.

Kontakty

Dostawca kontaktów łączy wiersze kontaktów bazowych ze wszystkich rodzajów i nazw kont, aby utworzyć kontakt. Ułatwia to wyświetlanie i modyfikowanie wszystkich danych zebranych przez użytkownika na temat danej osoby. Dostawca kontaktów zarządza tworzeniem nowych wierszy kontaktów i agregacją kontaktów bazowych z istniejącym wierszem kontaktu. Ani aplikacje, ani adaptery synchronizacji nie mogą dodawać kontaktów, a niektóre kolumny w wierszu kontaktu są tylko do odczytu.

Uwaga: jeśli spróbujesz dodać kontakt do dostawcy kontaktów za pomocą insert(), pojawi się wyjątek UnsupportedOperationException. Jeśli spróbujesz zaktualizować kolumnę oznaczoną jako „tylko do odczytu”, aktualizacja zostanie zignorowana.

Dostawca kontaktów tworzy nowy kontakt w odpowiedzi na dodanie nowego kontaktu bazowego, który nie pasuje do żadnego z istniejących kontaktów. Dostawca robi to również wtedy, gdy dane istniejącego kontaktu bazowego zmienią się w taki sposób, że nie będą już pasować do kontaktu, do którego były wcześniej dołączone. Jeśli aplikacja lub adapter synchronizacji utworzy nowy kontakt bazowy, który pasuje do istniejącego kontaktu, nowy kontakt bazowy zostanie zagregowany z istniejącym kontaktem.

Dostawca kontaktów łączy wiersz kontaktu z wierszami kontaktu bazowego za pomocą kolumny _ID wiersza kontaktu w tabeli Contacts. Kolumna CONTACT_ID tabeli kontaktów bazowych ContactsContract.RawContacts zawiera wartości _ID dla wiersza kontaktów powiązanego z każdym wierszem kontaktów bazowych.

Tabela ContactsContract.Contacts zawiera też kolumnę LOOKUP_KEY, która jest „stałym” linkiem do wiersza kontaktu. Dostawca kontaktów automatycznie zarządza kontaktami, więc może zmienić wartość _ID w wierszu kontaktu w odpowiedzi na agregację lub synchronizację. Nawet jeśli tak się stanie, identyfikator URI treści CONTENT_LOOKUP_URI w połączeniu z LOOKUP_KEY kontaktu nadal będzie wskazywać wiersz kontaktu, więc możesz użyć LOOKUP_KEY, aby zachować linki do „ulubionych” kontaktów itp. Ta kolumna ma własny format, który nie jest powiązany z formatem kolumny _ID.

Rysunek 3 pokazuje, jak te 3 główne tabele są ze sobą powiązane.

Główne tabele dostawcy kontaktów

Rysunek 3. Relacje między tabelami Contacts, Raw Contacts i Details.

Uwaga: jeśli opublikujesz aplikację w Sklepie Google Play lub jeśli Twoja aplikacja jest na urządzeniu z Androidem 10 (poziom interfejsu API 29) lub nowszym, pamiętaj, że ograniczony zestaw pól danych i metod kontaktów jest przestarzały.

W podanych warunkach system okresowo czyści wszystkie wartości zapisane w tych polach danych:

Interfejsy API używane do ustawiania poprzednich pól danych również są przestarzałe:

Dodatkowo te pola nie zwracają już częstych kontaktów. Pamiętaj, że niektóre z tych pól wpływają na ranking kontaktów tylko wtedy, gdy kontakty są częścią określonego rodzaju danych.

Jeśli Twoje aplikacje uzyskują dostęp do tych pól lub interfejsów API albo je aktualizują, użyj metod alternatywnych. Możesz na przykład spełnić wymagania niektórych przypadków użycia, korzystając z prywatnych mechanizmów dostarczania treści lub innych danych przechowywanych w aplikacji lub systemach backendu.

Aby sprawdzić, czy ta zmiana nie wpływa na działanie aplikacji, możesz ręcznie wyczyścić te pola danych. Aby to zrobić, uruchom to polecenie ADB na urządzeniu z Androidem 4.1 (interfejs API na poziomie 16) lub nowszym:

adb shell content delete \
--uri content://com.android.contacts/contacts/delete_usage

Dane z adapterów synchronizacji

Użytkownicy wpisują dane kontaktów bezpośrednio na urządzeniu, ale dane te trafiają też do dostawcy kontaktów z usług internetowych za pomocą adapterów synchronizacji, które automatyzują przesyłanie danych między urządzeniem a usługami. Adaptery synchronizacji działają w tle pod kontrolą systemu i wywołują metody ContentResolver, aby zarządzać danymi.

W Androidzie usługa internetowa, z którą współpracuje adapter synchronizacji, jest identyfikowana przez rodzaj konta. Każdy adapter synchronizacji działa z jednym rodzajem konta, ale może obsługiwać wiele nazw kont tego rodzaju. Rodzaje kont i nazwy kont są krótko opisane w sekcji Źródła danych kontaktów bazowych. Poniższe definicje zawierają więcej szczegółów i opisują, jak rodzaj i nazwa konta są powiązane z adapterami synchronizacji i usługami.

Typ konta
Identyfikuje usługę, w której użytkownik przechowuje dane. W większości przypadków użytkownik musi uwierzytelnić się w usłudze. Na przykład Kontakty Google to typ konta identyfikowany przez kod google.com. Ta wartość odpowiada rodzajowi konta używanemu przez AccountManager.
Nazwa konta
Identyfikuje konkretne konto lub login dla rodzaju konta. Konta Kontaktów Google są takie same jak konta Google, których nazwa jest adresem e-mail. Inne usługi mogą używać jednowyrazowej nazwy użytkownika lub identyfikatora numerycznego.

Rodzaje kont nie muszą być unikalne. Użytkownik może skonfigurować kilka kont Kontaktów Google i pobrać dane do dostawcy kontaktów. Może się tak zdarzyć, jeśli użytkownik ma jeden zestaw kontaktów osobistych dla osobistej nazwy konta i drugi zestaw do pracy. Nazwy kont są zwykle unikalne. Razem identyfikują konkretny przepływ danych między dostawcą kontaktów a usługą zewnętrzną.

Jeśli chcesz przenieść dane usługi do dostawcy kontaktów, musisz napisać własny adapter synchronizacji. Zostało to szczegółowo opisane w sekcji Adaptery synchronizacji dostawcy kontaktów.

Rysunek 4 pokazuje, jak dostawca kontaktów pasuje do przepływu danych o osobach. W polu „adaptery synchronizacji” każdy adapter jest oznaczony rodzajem konta.

Przepływ danych o osobach

Rysunek 4. Przepływ danych dostawcy kontaktów.

Wymagane uprawnienia

Aplikacje, które chcą uzyskać dostęp do dostawcy kontaktów, muszą poprosić o te uprawnienia:

Dostęp z możliwością odczytu do co najmniej 1 tabeli
READ_CONTACTS, określony w AndroidManifest.xml z elementem <uses-permission> jako <uses-permission android:name="android.permission.READ_CONTACTS">.
Uprawnienia do zapisu w co najmniej 1 tabeli
WRITE_CONTACTS, określony w AndroidManifest.xml z elementem <uses-permission> jako <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Te uprawnienia nie obejmują danych profilu użytkownika. Profil użytkownika i wymagane uprawnienia zostały omówione w sekcji Profil użytkownika poniżej.

Pamiętaj, że dane kontaktowe użytkownika są danymi osobowymi i wrażliwymi. Użytkownicy obawiają się o swoją prywatność, więc nie chcą, aby aplikacje zbierały dane o nich lub ich kontaktach. Jeśli nie jest jasne, dlaczego potrzebujesz uprawnień dostępu do danych kontaktów, użytkownicy mogą wystawić Twojej aplikacji niską ocenę lub po prostu odmówić jej zainstalowania.

profil użytkownika,

Tabela ContactsContract.Contacts zawiera jeden wiersz z danymi profilu użytkownika urządzenia. Te dane opisują user urządzenia, a nie jeden z kontaktów użytkownika. Wiersz kontaktów profilu jest połączony z wierszem kontaktów bazowych w każdym systemie, który używa profilu. Każdy wiersz kontaktu bazowego profilu może mieć wiele wierszy danych. Stałe umożliwiające dostęp do profilu użytkownika są dostępne w klasie ContactsContract.Profile.

Dostęp do profilu użytkownika wymaga specjalnych uprawnień. Oprócz uprawnień READ_CONTACTS i WRITE_CONTACTS wymaganych do odczytu i zapisu dostęp do profilu użytkownika wymaga uprawnień android.Manifest.permission#READ_PROFILE i android.Manifest.permission#WRITE_PROFILE odpowiednio do odczytu i zapisu.

Pamiętaj, że profil użytkownika należy traktować jako poufny. Uprawnienie android.Manifest.permission#READ_PROFILE umożliwia dostęp do danych umożliwiających identyfikację użytkownika urządzenia. W opisie aplikacji wyjaśnij użytkownikowi, dlaczego potrzebujesz uprawnień dostępu do profilu użytkownika.

Aby pobrać wiersz kontaktu zawierający profil użytkownika, wywołaj ContentResolver.query(). Ustaw identyfikator URI treści na CONTENT_URI i nie podawaj żadnych kryteriów wyboru. Możesz też użyć tego identyfikatora URI treści jako podstawowego identyfikatora URI do pobierania kontaktów bazowych lub danych profilu. Ten fragment kodu pobiera na przykład dane profilu:

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

Uwaga: jeśli pobierzesz kilka wierszy kontaktów i chcesz sprawdzić, czy jeden z nich jest profilem użytkownika, sprawdź kolumnę IS_USER_PROFILE wiersza. Ta kolumna ma wartość „1”, jeśli kontakt jest profilem użytkownika.

Metadane dostawcy kontaktów

Dostawca kontaktów zarządza danymi, które śledzą stan danych kontaktów w repozytorium. Te metadane dotyczące repozytorium są przechowywane w różnych miejscach, m.in. w wierszach tabel kontaktów bazowych, danych i kontaktów, tabeli ContactsContract.Settings i tabeli ContactsContract.SyncState. W tabeli poniżej znajdziesz informacje o wpływie poszczególnych metadanych:

Tabela 3. Metadane u dostawcy kontaktów

Tabela Kolumna Wartości Znaczenie
ContactsContract.RawContacts DIRTY „0” – nie zmienił się od ostatniej synchronizacji. Oznacza kontakty bazowe, które zostały zmienione na urządzeniu i muszą zostać zsynchronizowane z serwerem. Wartość jest ustawiana automatycznie przez dostawcę kontaktów, gdy aplikacje na Androida aktualizują wiersz.

Adaptery synchronizacji, które modyfikują tabele kontaktów bazowych lub danych, powinny zawsze dołączać ciąg znaków CALLER_IS_SYNCADAPTER do używanego identyfikatora URI treści. Zapobiega to oznaczaniu wierszy przez dostawcę jako nieaktualnych. W przeciwnym razie zmiany wprowadzone przez adapter synchronizacji będą wyglądać jak zmiany lokalne i zostaną wysłane na serwer, mimo że to serwer był źródłem modyfikacji.

„1” – zmieniono od ostatniej synchronizacji, należy zsynchronizować z serwerem.
ContactsContract.RawContacts VERSION Numer wersji tego wiersza. Dostawca kontaktów automatycznie zwiększa tę wartość, gdy zmienia się wiersz lub powiązane z nim dane.
ContactsContract.Data DATA_VERSION Numer wersji tego wiersza. Dostawca kontaktów automatycznie zwiększa tę wartość po każdej zmianie wiersza danych.
ContactsContract.RawContacts SOURCE_ID Wartość ciągu znaków, która jednoznacznie identyfikuje ten kontakt bazowy na koncie, na którym został utworzony. Gdy adapter synchronizacji tworzy nowy kontakt bazowy, ta kolumna powinna być ustawiona na unikalny identyfikator serwera dla kontaktu bazowego. Gdy aplikacja na Androida tworzy nowy kontakt bazowy, powinna pozostawić tę kolumnę pustą. Sygnalizuje to adapterowi synchronizacji, że powinien utworzyć nowy kontakt bazowy na serwerze i pobrać wartość dla SOURCE_ID.

W szczególności identyfikator źródła musi być unikalny dla każdego typu konta i powinien być stabilny podczas synchronizacji:

  • Unikalny: każdy kontakt bazowy na koncie musi mieć własny identyfikator źródła. Jeśli nie będziesz tego egzekwować, spowoduje to problemy w aplikacji do obsługi kontaktów. Zwróć uwagę, że 2 kontakty bazowe dla tego samego typu konta mogą mieć ten sam identyfikator źródła. Na przykład kontakt bazowy „Thomas Higginson” na koncie emily.dickinson@gmail.com może mieć ten sam identyfikator źródła co kontakt bazowy „Thomas Higginson” na koncie emilyd@gmail.com.
  • Stabilne: identyfikatory źródła są stałą częścią danych usługi online dotyczących kontaktu bazowego. Jeśli na przykład użytkownik wyczyści pamięć kontaktów w ustawieniach aplikacji i ponownie przeprowadzi synchronizację, przywrócone kontakty bazowe powinny mieć te same identyfikatory źródła co wcześniej. Jeśli nie wymusisz tej opcji, skróty przestaną działać.
ContactsContract.Groups GROUP_VISIBLE „0” – kontakty w tej grupie nie powinny być widoczne w interfejsach aplikacji na Androida. Ta kolumna zapewnia zgodność z serwerami, które umożliwiają użytkownikowi ukrywanie kontaktów w określonych grupach.
„1” – kontakty z tej grupy mogą być widoczne w interfejsach aplikacji.
ContactsContract.Settings UNGROUPED_VISIBLE „0” – w przypadku tego konta i rodzaju konta kontakty, które nie należą do grupy, są niewidoczne w interfejsach aplikacji na Androida. Domyślnie kontakty są niewidoczne, jeśli żaden z ich kontaktów bazowych nie należy do grupy (przynależność kontaktu bazowego do grupy jest oznaczona przez co najmniej 1 wiersz ContactsContract.CommonDataKinds.GroupMembership w tabeli ContactsContract.Data). Ustawiając tę flagę w wierszu tabeli ContactsContract.Settings dla rodzaju konta i konta, możesz wymusić wyświetlanie kontaktów bez grup. Jednym z zastosowań tej flagi jest wyświetlanie kontaktów z serwerów, które nie używają grup.
„1” – w przypadku tego konta i rodzaju konta kontakty, które nie należą do grupy, są widoczne w interfejsach aplikacji.
ContactsContract.SyncState (wszystkie) Użyj tej tabeli do przechowywania metadanych adaptera synchronizacji. Za pomocą tej tabeli możesz trwale przechowywać na urządzeniu stan synchronizacji i inne dane związane z synchronizacją.

Dostęp do dostawcy kontaktów

W tej sekcji opisujemy wytyczne dotyczące uzyskiwania dostępu do danych od dostawcy kontaktów, ze szczególnym uwzględnieniem tych kwestii:

  • Zapytania dotyczące encji.
  • Modyfikacja zbiorcza.
  • pobieranie i modyfikowanie za pomocą intencji;
  • Integralność danych.

Wprowadzanie zmian za pomocą adaptera synchronizacji jest też szczegółowo opisane w sekcji Adaptery synchronizacji dostawcy kontaktów.

Encje, których mają dotyczyć zapytania

Ponieważ tabele dostawcy kontaktów są uporządkowane hierarchicznie, często przydaje się pobranie wiersza i wszystkich powiązanych z nim wierszy podrzędnych. Jeśli na przykład chcesz wyświetlić wszystkie informacje o osobie, możesz pobrać wszystkie wiersze ContactsContract.RawContacts dla pojedynczego wiersza ContactsContract.Contacts lub wszystkie wiersze ContactsContract.CommonDataKinds.Email dla pojedynczego wiersza ContactsContract.RawContacts. Aby to ułatwić, dostawca kontaktów udostępnia konstrukcje encji, które działają jak połączenia baz danych między tabelami.

Encja jest jak tabela składająca się z wybranych kolumn z tabeli nadrzędnej i jej tabeli podrzędnej. Gdy wysyłasz zapytanie do encji, podajesz projekcję i kryteria wyszukiwania na podstawie kolumn dostępnych w encji. Wynikiem jest obiekt Cursor, który zawiera po 1 wierszu na każdy pobrany wiersz tabeli podrzędnej. Jeśli na przykład wyślesz do ContactsContract.Contacts.Entity zapytanie o nazwę kontaktu i wszystkie wiersze ContactsContract.CommonDataKinds.Email dla wszystkich kontaktów bazowych o tej nazwie, otrzymasz obiekt Cursor zawierający po jednym wierszu dla każdego wiersza ContactsContract.CommonDataKinds.Email.

Encje upraszczają zapytania. Za pomocą encji możesz pobrać wszystkie dane kontaktowe kontaktu lub kontaktu bazowego naraz, zamiast najpierw wysyłać zapytanie do tabeli nadrzędnej, aby uzyskać identyfikator, a następnie wysyłać zapytanie do tabeli podrzędnej z tym identyfikatorem. Dostawca kontaktów przetwarza też zapytanie dotyczące encji w ramach jednej transakcji, co zapewnia wewnętrzną spójność pobranych danych.

Uwaga: encja zwykle nie zawiera wszystkich kolumn tabeli nadrzędnej i podrzędnej. Jeśli spróbujesz użyć nazwy kolumny, której nie ma na liście stałych nazw kolumn dla danego elementu, pojawi się błąd Exception.

Poniższy fragment kodu pokazuje, jak pobrać wszystkie wiersze kontaktów bazowych dla kontaktu. Fragment kodu jest częścią większej aplikacji, która ma 2 aktywności: „główną” i „szczegółową”. Główna aktywność wyświetla listę wierszy kontaktów. Gdy użytkownik wybierze jeden z nich, aktywność wysyła jego identyfikator do aktywności szczegółowej. Aktywność szczegółowa używa ContactsContract.Contacts.Entity, aby wyświetlić wszystkie wiersze danych ze wszystkich kontaktów bazowych powiązanych z wybranym kontaktem.

Ten fragment kodu pochodzi z aktywności „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.
}

Po zakończeniu wczytywania LoaderManager wywołuje wywołanie zwrotne do onLoadFinished(). Jednym z argumentów przychodzących do tej metody jest Cursor z wynikami zapytania. We własnej aplikacji możesz pobrać dane z tego elementu Cursor, aby je wyświetlić lub dalej z nimi pracować.

Modyfikacja zbiorcza

Jeśli to możliwe, dane w dostawcy kontaktów należy wstawiać, aktualizować i usuwać w „trybie wsadowym”, tworząc obiekt ArrayList obiektów ContentProviderOperation i wywołując applyBatch(). Dostawca kontaktów wykonuje wszystkie operacje w obiekcie applyBatch() w ramach jednej transakcji, dlatego Twoje modyfikacje nigdy nie pozostawią repozytorium kontaktów w niespójnym stanie. Modyfikacja zbiorcza ułatwia też jednoczesne wstawianie kontaktu bazowego i jego szczegółowych danych.

Uwaga: aby zmodyfikować pojedynczy kontakt bazowy, rozważ wysłanie intencji do aplikacji do obsługi kontaktów na urządzeniu, zamiast obsługiwać modyfikację w aplikacji. Więcej informacji na ten temat znajdziesz w sekcji Pobieranie i modyfikowanie za pomocą intencji.

Punkty wstrzymania

Modyfikacja zbiorcza zawierająca dużą liczbę operacji może blokować inne procesy, co może negatywnie wpłynąć na ogólne wrażenia użytkownika. Aby uporządkować wszystkie modyfikacje, które chcesz wprowadzić, w jak najmniejszej liczbie osobnych list, a jednocześnie zapobiec blokowaniu systemu, ustaw punkty wstrzymania dla co najmniej 1 operacji. Punkt wstrzymania to obiekt ContentProviderOperation, którego wartość isYieldAllowed() jest ustawiona na true. Gdy dostawca kontaktów napotka punkt wstrzymania, wstrzymuje działanie, aby umożliwić uruchomienie innych procesów, i zamyka bieżącą transakcję. Gdy dostawca uruchomi się ponownie, będzie kontynuować od następnej operacji w ArrayList i rozpocznie nową transakcję.

Punkty wstrzymania powodują więcej niż 1 transakcję na wywołanie funkcji applyBatch(). Z tego powodu należy ustawić punkt wstrzymania dla ostatniej operacji w zestawie powiązanych wierszy. Możesz na przykład ustawić punkt wstrzymania dla ostatniej operacji w zbiorze, która dodaje wiersze kontaktu bazowego i powiązane z nimi wiersze danych, lub dla ostatniej operacji w zbiorze wierszy powiązanych z pojedynczym kontaktem.

Punkty wstrzymania są też jednostką operacji niepodzielnej. Wszystkie dostępy między 2 punktami wstrzymania zakończą się powodzeniem lub niepowodzeniem jako pojedyncza jednostka. Jeśli nie ustawisz żadnych punktów wstrzymania, najmniejszą operacją niepodzielną będzie cała partia operacji. Jeśli używasz punktów wstrzymania, zapobiegasz pogorszeniu wydajności systemu przez operacje, a jednocześnie zapewniasz, że podzbiór operacji jest niepodzielny.

Odniesienia wsteczne do modyfikacji

Gdy wstawiasz nowy wiersz kontaktu bazowego i powiązane z nim wiersze danych jako zbiór obiektów ContentProviderOperation, musisz połączyć wiersze danych z wierszem kontaktu bazowego, wstawiając wartość _ID kontaktu bazowego jako wartość RAW_CONTACT_ID. Ta wartość nie jest jednak dostępna podczas tworzenia ContentProviderOperation dla wiersza danych, ponieważ nie zastosowano jeszcze ContentProviderOperation dla wiersza kontaktu bazowego. Aby obejść ten problem, klasa ContentProviderOperation.Builder ma metodę withValueBackReference(). Ta metoda umożliwia wstawienie lub zmodyfikowanie kolumny z wynikiem poprzedniej operacji.

Metoda withValueBackReference() ma 2 argumenty:

key
Klucz pary klucz-wartość. Wartością tego argumentu powinna być nazwa kolumny w modyfikowanej tabeli.
previousResult
Indeks (liczony od 0) wartości w tablicy obiektów ContentProviderResult z applyBatch(). Podczas stosowania operacji zbiorczych wynik każdej operacji jest przechowywany w pośredniej tablicy wyników. Wartość previousResult to indeks jednego z tych wyników, który jest pobierany i przechowywany z wartością key. Dzięki temu możesz wstawić nowy rekord kontaktu bazowego i uzyskać jego wartość _ID, a następnie utworzyć „odwołanie wsteczne” do tej wartości podczas dodawania wiersza ContactsContract.Data.

Cała tablica wyników jest tworzona przy pierwszym wywołaniu funkcji applyBatch(), a jej rozmiar jest równy rozmiarowi podanej przez Ciebie tablicy ArrayList obiektów ContentProviderOperation. Jednak wszystkie elementy w tablicy wyników są ustawione na null, a jeśli spróbujesz odwołać się do wyniku operacji, która nie została jeszcze zastosowana, withValueBackReference() zgłosi błąd Exception.

Poniższe fragmenty kodu pokazują, jak zbiorczo wstawić nowy kontakt bazowy i dane. Zawierają one kod, który ustanawia punkt wstrzymania i używa odwołania wstecznego.

Pierwszy fragment kodu pobiera dane kontaktowe z interfejsu. W tym momencie użytkownik wybrał już konto, do którego ma zostać dodany nowy kontakt bazowy.

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

Następny fragment kodu tworzy operację wstawiania wiersza kontaktu bazowego do tabeli 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());

Następnie kod tworzy wiersze danych dla wyświetlanej nazwy, numeru telefonu i adresu e-mail.

Każdy obiekt kreatora operacji używa metody withValueBackReference(), aby uzyskać RAW_CONTACT_ID. Odwołanie wskazuje obiekt ContentProviderResult z pierwszej operacji, która dodaje wiersz kontaktu bazowego i zwraca nową wartość _ID. W efekcie każdy wiersz danych jest automatycznie łączony za pomocą RAW_CONTACT_ID z nowym wierszem ContactsContract.RawContacts, do którego należy.

Obiekt ContentProviderOperation.Builder, który dodaje wiersz e-maila, jest oznaczony flagą withYieldAllowed(), która ustawia punkt wstrzymania:

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

Ostatni fragment kodu pokazuje wywołanie metody applyBatch(), która wstawia nowe wiersze kontaktu bazowego i danych.

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

Operacje zbiorcze umożliwiają też wdrożenie optymistycznej kontroli równoczesności, czyli metody stosowania transakcji modyfikacji bez konieczności blokowania repozytorium bazowego. Aby użyć tej metody, zastosuj transakcję, a następnie sprawdź, czy nie wprowadzono innych zmian w tym samym czasie. Jeśli wykryjesz niespójną modyfikację, wycofaj transakcję i spróbuj ponownie.

Optymistyczna kontrola równoczesności jest przydatna w przypadku urządzeń mobilnych, na których jednocześnie jest tylko 1 użytkownik, a jednoczesny dostęp do repozytorium danych jest rzadki. Ponieważ blokowanie nie jest używane, nie marnuje się czasu na ustawianie blokad ani oczekiwanie na zwolnienie blokad przez inne transakcje.

Aby użyć optymistycznej kontroli równoczesności podczas aktualizowania pojedynczego wiersza ContactsContract.RawContacts, wykonaj te czynności:

  1. Pobierz kolumnę VERSION kontaktu bazowego wraz z innymi danymi.
  2. Utwórz obiekt ContentProviderOperation.Builder odpowiedni do wymuszania ograniczenia, używając metody newAssertQuery(Uri). W przypadku identyfikatora URI treści użyj RawContacts.CONTENT_URI z dołączonym identyfikatorem _ID kontaktu bazowego.
  3. W przypadku obiektu ContentProviderOperation.Builder wywołaj withValue(), aby porównać kolumnę VERSION z pobranym numerem wersji.
  4. W przypadku tego samego ContentProviderOperation.Builder wywołaj withExpectedCount(), aby upewnić się, że ta asercja testuje tylko 1 wiersz.
  5. Wywołaj build(), aby utworzyć obiekt ContentProviderOperation, a następnie dodaj go jako pierwszy obiekt w ArrayList, który przekazujesz do applyBatch().
  6. Zastosuj transakcję wsadową.

Jeśli wiersz kontaktu bazowego zostanie zaktualizowany przez inną operację między odczytaniem wiersza a próbą jego zmodyfikowania, „asercja” ContentProviderOperation nie powiedzie się, a cała seria operacji zostanie wycofana. Możesz wtedy spróbować ponownie przesłać partię lub wykonać inne działanie.

Poniższy fragment kodu pokazuje, jak utworzyć „asercję” ContentProviderOperation po wysłaniu zapytania o pojedynczy kontakt bazowy za pomocą 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
    }

Pobieranie i modyfikowanie za pomocą intencji

Wysłanie intencji do aplikacji do obsługi kontaktów na urządzeniu umożliwia pośredni dostęp do dostawcy kontaktów. Intencja uruchamia interfejs aplikacji do obsługi kontaktów na urządzeniu, w którym użytkownicy mogą wykonywać czynności związane z kontaktami. Użytkownicy z tym poziomem dostępu mogą:

  • Wybierz kontakt z listy i przekaż go do aplikacji, aby kontynuować pracę.
  • edytować dane istniejącego kontaktu,
  • wstawiać nowy kontakt bazowy na dowolnym koncie,
  • Usuwanie danych kontaktów.

Jeśli użytkownik wstawia lub aktualizuje dane, możesz najpierw zebrać dane i wysłać je jako część intencji.

Gdy używasz intencji, aby uzyskać dostęp do dostawcy kontaktów za pomocą aplikacji do obsługi kontaktów na urządzeniu, nie musisz pisać własnego interfejsu ani kodu, aby uzyskać dostęp do dostawcy. Nie musisz też prosić o uprawnienia do odczytu lub zapisu u dostawcy. Aplikacja do obsługi kontaktów na urządzeniu może przekazać Ci uprawnienia do odczytu kontaktu, a ponieważ wprowadzasz zmiany w dostawcy za pomocą innej aplikacji, nie musisz mieć uprawnień do zapisu.

Ogólny proces wysyłania intencji w celu uzyskania dostępu do dostawcy jest szczegółowo opisany w sekcji „Dostęp do danych za pomocą intencji” w przewodniku Podstawy mechanizmu dostarczania treści. Działanie, typ MIME i wartości danych, których używasz w przypadku dostępnych zadań, są podsumowane w tabeli 4, a wartości dodatkowe, których możesz używać z putExtra(), są wymienione w dokumentacji referencyjnej ContactsContract.Intents.Insert:

Tabela 4. Intencje dostawcy kontaktów.

Zadanie Działanie Dane Typ MIME Uwagi
Wybieranie kontaktu z listy ACTION_PICK Jedna z tych wartości: Nieużywane Wyświetla listę kontaktów bazowych lub listę danych z kontaktu bazowego, w zależności od podanego typu identyfikatora URI treści.

Wywołaj metodę startActivityForResult(), która zwraca identyfikator URI treści wybranego wiersza. Identyfikator URI ma postać identyfikatora URI treści tabeli z dołączonym identyfikatorem LOOKUP_ID wiersza. Aplikacja do obsługi kontaktów na urządzeniu przekazuje uprawnienia do odczytu i zapisu do tego identyfikatora URI treści na czas trwania aktywności. Więcej informacji znajdziesz w przewodniku Podstawy mechanizmu dostarczania treści.

Wstawianie nowego kontaktu bazowego Insert.ACTION Nie dotyczy RawContacts.CONTENT_TYPE, typ MIME dla zbioru kontaktów bazowych. Wyświetla ekran Dodaj kontakt w aplikacji do obsługi kontaktów na urządzeniu. Wyświetlą się wartości dodatkowe dodane do intencji. Jeśli zostanie wysłany z startActivityForResult(), identyfikator URI treści nowo dodanego kontaktu bazowego zostanie przekazany z powrotem do metody wywołania zwrotnego onActivityResult() aktywności w argumencie Intent w polu „data”. Aby uzyskać wartość, zadzwoń pod numer getData().
Edytowanie kontaktu ACTION_EDIT CONTENT_LOOKUP_URI dla kontaktu. Aktywność edytora pozwoli użytkownikowi edytować dowolne dane powiązane z tym kontaktem. Contacts.CONTENT_ITEM_TYPE, 1 kontakt. Wyświetla ekran edytowania kontaktu w aplikacji do obsługi kontaktów. Wyświetlą się wartości dodatkowe dodane do intencji. Gdy użytkownik kliknie Gotowe, aby zapisać zmiany, Twoja aktywność wróci na pierwszy plan.
Wyświetlanie selektora, który może też dodawać dane ACTION_INSERT_OR_EDIT Nie dotyczy CONTENT_ITEM_TYPE Ta intencja zawsze wyświetla ekran wyboru aplikacji do kontaktów. Użytkownik może wybrać kontakt do edycji lub dodać nowy kontakt. W zależności od wyboru użytkownika pojawi się ekran edycji lub dodawania, a także dane dodatkowe przekazane w intencji. Jeśli aplikacja wyświetla dane kontaktowe, takie jak adres e-mail lub numer telefonu, użyj tej intencji, aby umożliwić użytkownikowi dodanie tych danych do istniejącego kontaktu. kontakt,

Uwaga: nie musisz wysyłać wartości nazwy w dodatkach tej intencji, ponieważ użytkownik zawsze wybiera istniejącą nazwę lub dodaje nową. Co więcej, jeśli wyślesz nazwę, a użytkownik zdecyduje się na edycję, aplikacja do kontaktów wyświetli wysłaną przez Ciebie nazwę, zastępując poprzednią wartość. Jeśli użytkownik tego nie zauważy i zapisze zmiany, stara wartość zostanie utracona.

Aplikacja do obsługi kontaktów na urządzeniu nie pozwala na usunięcie kontaktu bazowego ani żadnych jego danych za pomocą intencji. Aby usunąć kontakt bazowy, użyj ContentResolver.delete() lub ContentProviderOperation.newDelete().

Poniższy fragment kodu pokazuje, jak utworzyć i wysłać intencję, która wstawia nowy kontakt bazowy i dane:

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

integralność danych,

Repozytorium kontaktów zawiera ważne i poufne dane, które powinny być prawidłowe i aktualne, dlatego dostawca kontaktów ma ściśle określone reguły dotyczące integralności danych. Twoim obowiązkiem jest przestrzeganie tych zasad podczas modyfikowania danych kontaktów. Oto najważniejsze zasady:

Zawsze dodawaj wiersz ContactsContract.CommonDataKinds.StructuredName do każdego dodawanego wiersza ContactsContract.RawContacts.
Wiersz ContactsContract.RawContacts bez wiersza ContactsContract.CommonDataKinds.StructuredName w tabeli ContactsContract.Data może powodować problemy podczas agregacji.
Zawsze łącz nowe wiersze ContactsContract.Data z ich nadrzędnym wierszem ContactsContract.RawContacts.
Wiersz ContactsContract.Data, który nie jest połączony z ContactsContract.RawContacts, nie będzie widoczny w aplikacji do obsługi kontaktów na urządzeniu i może powodować problemy z adapterami synchronizacji.
Zmieniaj dane tylko w przypadku kontaktów bazowych, które należą do Ciebie.
Pamiętaj, że dostawca kontaktów zwykle zarządza danymi z kilku różnych rodzajów kont i usług online. Musisz zadbać o to, aby aplikacja modyfikowała lub usuwała tylko dane z wierszy, które należą do Ciebie, oraz aby wstawiała tylko dane z rodzajem i nazwą konta, które kontrolujesz.
Zawsze używaj stałych zdefiniowanych w klasie ContactsContract i jej podklasach w przypadku urzędów, identyfikatorów URI treści, ścieżek URI, nazw kolumn, typów MIME i wartości TYPE.
Używanie tych stałych pomaga uniknąć błędów. Jeśli któraś ze stałych jest wycofana, otrzymasz też powiadomienie z ostrzeżeniami kompilatora.

Wiersze danych niestandardowych

Tworząc i używając własnych niestandardowych typów MIME, możesz wstawiać, edytować, usuwać i pobierać własne wiersze danych w tabeli ContactsContract.Data. Wiersze mogą używać tylko kolumny zdefiniowanej w ContactsContract.DataColumns, ale możesz zmapować własne nazwy kolumn dla poszczególnych typów na domyślne nazwy kolumn. W aplikacji do obsługi kontaktów na urządzeniu dane z wierszy są wyświetlane, ale nie można ich edytować ani usuwać, a użytkownicy nie mogą dodawać dodatkowych danych. Aby umożliwić użytkownikom modyfikowanie niestandardowych wierszy danych, musisz udostępnić aktywność edytora we własnej aplikacji.

Aby wyświetlić dane niestandardowe, podaj plik contacts.xml zawierający element <ContactsAccountType> i co najmniej 1 element podrzędny <ContactsDataKind>. Szczegółowo opisujemy to w sekcji <ContactsDataKind> element.

Więcej informacji o niestandardowych typach MIME znajdziesz w przewodniku Tworzenie mechanizmu dostarczania treści.

Adaptery synchronizacji dostawcy kontaktów

Dostawca kontaktów został zaprojektowany specjalnie do obsługi synchronizacji danych kontaktów między urządzeniem a usługą online. Dzięki temu użytkownicy mogą pobierać istniejące dane na nowe urządzenie i przesyłać je na nowe konto. Synchronizacja zapewnia też użytkownikom dostęp do najnowszych danych, niezależnie od źródła dodanych informacji i zmian. Inną zaletą synchronizacji jest to, że dane kontaktów są dostępne nawet wtedy, gdy urządzenie nie jest połączone z siecią.

Chociaż synchronizację można wdrożyć na różne sposoby, system Android udostępnia platformę synchronizacji wtyczek, która automatyzuje te zadania:

  • Sprawdzam dostępność sieci.
  • planowanie i wykonywanie synchronizacji na podstawie preferencji użytkownika;
  • ponowne uruchamianie zatrzymanych synchronizacji;

Aby korzystać z tej platformy, musisz dostarczyć wtyczkę adaptera synchronizacji. Każdy adapter synchronizacji jest unikalny dla usługi i mechanizmu dostarczania treści, ale może obsługiwać wiele nazw kont w tej samej usłudze. Platforma umożliwia też korzystanie z wielu adapterów synchronizacji w przypadku tej samej usługi i tego samego dostawcy.

Klasy i pliki adaptera synchronizacji

Adapter synchronizacji implementuje się jako podklasę AbstractThreadedSyncAdapter i instaluje jako część aplikacji na Androida. System dowiaduje się o adapterze synchronizacji z elementów w manifeście aplikacji i ze specjalnego pliku XML wskazanego przez manifest. Plik XML definiuje rodzaj konta usługi online i urząd dostawcy treści, które razem jednoznacznie identyfikują adapter. Adapter synchronizacji nie zostanie aktywowany, dopóki użytkownik nie doda konta odpowiedniego rodzaju i nie włączy synchronizacji dla mechanizmu dostarczania treści, z którym adapter synchronizacji ma się synchronizować. W tym momencie system zaczyna zarządzać adapterem, wywołując go w razie potrzeby, aby zsynchronizować mechanizm dostarczania treści z serwerem.

Uwaga: użycie rodzaju konta jako części identyfikatora adaptera synchronizacji umożliwia systemowi wykrywanie i grupowanie adapterów synchronizacji, które uzyskują dostęp do różnych usług z tej samej organizacji. Na przykład adaptery synchronizacji usług online Google mają ten sam rodzaj konta: com.google. Gdy użytkownicy dodają konto Google do swoich urządzeń, wszystkie zainstalowane adaptery synchronizacji usług Google są wyświetlane razem. Każdy z nich synchronizuje się z innym mechanizmem dostarczania treści na urządzeniu.

Większość usług wymaga od użytkowników potwierdzenia tożsamości przed uzyskaniem dostępu do danych, dlatego system Android oferuje platformę uwierzytelniania, która jest podobna do platformy adaptera synchronizacji i często jest używana w połączeniu z nią. Platforma uwierzytelniania używa modułów uwierzytelniających wtyczki, które są podklasami AbstractAccountAuthenticator. Uwierzytelniacz weryfikuje tożsamość użytkownika w tych krokach:

  1. zbiera nazwę użytkownika, hasło lub podobne informacje (dane logowania użytkownika);
  2. wysyła dane logowania do usługi,
  3. Sprawdza odpowiedź usługi.

Jeśli usługa zaakceptuje dane logowania, mechanizm uwierzytelniający może je zapisać do późniejszego użycia. Dzięki platformie uwierzytelniania wtyczek AccountManager może zapewniać dostęp do dowolnych tokenów uwierzytelniania, które obsługuje i udostępnia uwierzytelniacz, takich jak tokeny uwierzytelniania OAuth2.

Chociaż uwierzytelnianie nie jest wymagane, większość usług kontaktów z niego korzysta. Nie musisz jednak używać platformy uwierzytelniania Androida do uwierzytelniania.

Implementacja adaptera synchronizacji

Aby zaimplementować adapter synchronizacji dla dostawcy kontaktów, zacznij od utworzenia aplikacji na Androida, która zawiera:

Komponent Service, który odpowiada na żądania systemu dotyczące powiązania z adapterem synchronizacji.
Gdy system chce przeprowadzić synchronizację, wywołuje metodę onBind() usługi, aby uzyskać obiekt IBinder dla adaptera synchronizacji. Dzięki temu system może wykonywać wywołania metod adaptera między procesami.
Właściwy adapter synchronizacji, zaimplementowany jako konkretna podklasa AbstractThreadedSyncAdapter.
Ta klasa odpowiada za pobieranie danych z serwera, przesyłanie danych z urządzenia i rozwiązywanie konfliktów. Główne zadanie adaptera jest wykonywane w metodzie onPerformSync(). Ta klasa musi być utworzona jako singleton.
Podklasa elementu Application.
Ta klasa działa jako fabryka dla obiektu singleton adaptera synchronizacji. Użyj metody onCreate(), aby utworzyć instancję adaptera synchronizacji, i podaj statyczną metodę „getter”, aby zwrócić singleton do metody onBind() usługi adaptera synchronizacji.
Opcjonalnie: komponent Service, który odpowiada na żądania uwierzytelnienia użytkownika wysyłane przez system.
AccountManager uruchamia tę usługę, aby rozpocząć proces uwierzytelniania. Metoda onCreate() usługi tworzy instancję obiektu uwierzytelniającego. Gdy system chce uwierzytelnić konto użytkownika na potrzeby adaptera synchronizacji aplikacji, wywołuje metodę onBind() usługi, aby uzyskać IBinder dla uwierzytelniacza. Dzięki temu system może wykonywać wywołania metod uwierzytelniającego między procesami.
Opcjonalnie: konkretna podklasa AbstractAccountAuthenticator, która obsługuje żądania uwierzytelniania.
Ta klasa udostępnia metody wywoływane przez AccountManager w celu uwierzytelnienia danych logowania użytkownika na serwerze. Szczegóły procesu uwierzytelniania różnią się znacznie w zależności od używanej technologii serwera. Więcej informacji o uwierzytelnianiu znajdziesz w dokumentacji oprogramowania serwerowego.
Pliki XML, które definiują adapter synchronizacji i uwierzytelniacz w systemie.
Opisane wcześniej komponenty usługi adaptera synchronizacji i uwierzytelniania są zdefiniowane w elementach <service> w pliku manifestu aplikacji. Te elementy zawierają elementy podrzędne <meta-data>, które dostarczają systemowi określone dane:
  • Element <meta-data> usługi adaptera synchronizacji wskazuje plik XML res/xml/syncadapter.xml. Z kolei ten plik określa identyfikator URI usługi internetowej, która będzie synchronizowana z dostawcą kontaktów, oraz rodzaj konta usługi internetowej.
  • Opcjonalnie: element <meta-data> modułu uwierzytelniającego wskazuje plik XML res/xml/authenticator.xml. Z kolei ten plik określa rodzaj konta obsługiwany przez ten moduł uwierzytelniający, a także zasoby interfejsu, które pojawiają się podczas procesu uwierzytelniania. Rodzaj konta określony w tym elemencie musi być taki sam jak rodzaj konta określony dla adaptera synchronizacji.

Dane strumienia społecznościowego

Tabele android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos zarządzają danymi przychodzącymi z sieci społecznościowych. Możesz napisać adapter synchronizacji, który dodaje dane strumienia z Twojej sieci do tych tabel, lub odczytywać dane strumienia z tych tabel i wyświetlać je we własnej aplikacji. Możesz też wykonywać obie te czynności. Dzięki tym funkcjom Twoje usługi i aplikacje społecznościowe mogą zostać zintegrowane z funkcjami społecznościowymi Androida.

Tekst w sieciach społecznościowych

Elementy strumienia są zawsze powiązane z kontaktem bazowym. android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID prowadzi do wartości _ID kontaktu bazowego. Rodzaj konta i nazwa konta kontaktu bazowego są również przechowywane w wierszu elementu strumienia.

Przechowuj dane ze strumienia w tych kolumnach:

android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
Wymagany. Rodzaj konta użytkownika dla kontaktu bazowego powiązanego z tym elementem strumienia. Pamiętaj, aby ustawić tę wartość podczas wstawiania elementu strumienia.
android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
Wymagany. Nazwa konta użytkownika dla kontaktu bazowego powiązanego z tym elementem strumienia. Pamiętaj, aby ustawić tę wartość podczas wstawiania elementu strumienia.
Kolumny identyfikatorów
Wymagany. Podczas wstawiania elementu strumienia musisz wstawić te kolumny identyfikatorów:
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: wartość android.provider.BaseColumns#_ID kontaktu, z którym jest powiązany ten element strumienia.
  • android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: wartość android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY kontaktu, z którym jest powiązany ten element strumienia.
  • android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: wartość android.provider.BaseColumns#_ID kontaktu bazowego, z którym jest powiązany ten element strumienia.
android.provider.ContactsContract.StreamItemsColumns#COMMENTS
Opcjonalnie. Przechowuje informacje podsumowujące, które możesz wyświetlić na początku elementu strumienia.
android.provider.ContactsContract.StreamItemsColumns#TEXT
Tekst elementu strumienia, czyli treść opublikowana przez źródło elementu lub opis działania, które wygenerowało element strumienia. Ta kolumna może zawierać dowolne formatowanie i obrazy zasobów, które mogą być renderowane przez fromHtml(). Dostawca może obciąć lub skrócić długą treść, ale będzie starał się unikać uszkodzenia tagów.
android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
Ciąg tekstowy zawierający czas wstawienia lub zaktualizowania elementu strumienia w formie milisekund od początku epoki. Aplikacje, które wstawiają lub aktualizują elementy strumienia, są odpowiedzialne za utrzymanie tej kolumny. Nie jest ona automatycznie utrzymywana przez dostawcę kontaktów.

Aby wyświetlić informacje umożliwiające identyfikację elementów strumienia, użyj android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL i android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE do utworzenia linków do zasobów w aplikacji.

Tabela android.provider.ContactsContract.StreamItems zawiera też kolumny android.provider.ContactsContract.StreamItemsColumns#SYNC1 do android.provider.ContactsContract.StreamItemsColumns#SYNC4, które są przeznaczone wyłącznie do użytku przez adaptery synchronizacji.

Zdjęcia ze strumienia społecznościowego

Tabela android.provider.ContactsContract.StreamItemPhotos przechowuje zdjęcia powiązane z elementem strumienia. Kolumna android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID tabeli zawiera linki do wartości w kolumnie _ID tabeli android.provider.ContactsContract.StreamItems. Odwołania do zdjęć są przechowywane w tabeli w tych kolumnach:

kolumna android.provider.ContactsContract.StreamItemPhotos#PHOTO (BLOB).
Reprezentacja binarna zdjęcia, którego rozmiar został zmieniony przez dostawcę na potrzeby przechowywania i wyświetlania. Ta kolumna jest dostępna ze względu na zgodność wsteczną z poprzednimi wersjami dostawcy kontaktów, które używały jej do przechowywania zdjęć. W obecnej wersji nie należy jednak używać tej kolumny do przechowywania zdjęć. Zamiast tego użyj android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID lub android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (oba są opisane w poniższych punktach), aby zapisać zdjęcia w pliku. Ta kolumna zawiera teraz miniaturę zdjęcia, które można odczytać.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
Identyfikator numeryczny zdjęcia kontaktu bazowego. Dołącz tę wartość do stałej DisplayPhoto.CONTENT_URI, aby uzyskać identyfikator URI treści wskazujący na pojedynczy plik zdjęcia, a następnie wywołaj openAssetFileDescriptor(), aby uzyskać uchwyt do pliku zdjęcia.
android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
Identyfikator URI treści wskazujący bezpośrednio na plik zdjęcia reprezentowanego przez ten wiersz. Wywołaj openAssetFileDescriptor() za pomocą tego identyfikatora URI, aby uzyskać uchwyt do pliku zdjęcia.

Korzystanie z tabel strumienia społecznościowego

Te tabele działają tak samo jak inne główne tabele w dostawcy kontaktów, z tymi wyjątkami:

  • Te tabele wymagają dodatkowych uprawnień dostępu. Aby odczytywać z nich dane, aplikacja musi mieć uprawnienie android.Manifest.permission#READ_SOCIAL_STREAM. Aby je zmodyfikować, aplikacja musi mieć uprawnienie android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • W przypadku tabeli android.provider.ContactsContract.StreamItems liczba wierszy przechowywanych dla każdego kontaktu bazowego jest ograniczona. Gdy zostanie osiągnięty, dostawca kontaktów zrobi miejsce na nowe wiersze elementów strumienia, automatycznie usuwając wiersze z najstarszymi wartościami android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP. Aby uzyskać limit, wyślij zapytanie do identyfikatora URI treści android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Możesz pozostawić wszystkie argumenty inne niż identyfikator URI treści ustawione na null. Zapytanie zwraca kursor zawierający pojedynczy wiersz z pojedynczą kolumną android.provider.ContactsContract.StreamItems#MAX_ITEMS.

Klasa android.provider.ContactsContract.StreamItems.StreamItemPhotos definiuje podtabelę android.provider.ContactsContract.StreamItemPhotos zawierającą wiersze zdjęć dla pojedynczego elementu strumienia.

Interakcje ze strumieniem społecznościowym

Dane strumienia społecznościowego zarządzane przez dostawcę kontaktów w połączeniu z aplikacją do obsługi kontaktów na urządzeniu umożliwiają skuteczne połączenie systemu sieci społecznościowej z istniejącymi kontaktami. Dostępne są te funkcje:

  • Synchronizując usługę sieci społecznościowej z dostawcą kontaktów za pomocą adaptera synchronizacji, możesz pobierać ostatnią aktywność kontaktów użytkownika i zapisywać ją w tabelach android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos do późniejszego wykorzystania.
  • Oprócz regularnej synchronizacji możesz aktywować adapter synchronizacji, aby pobierał dodatkowe dane, gdy użytkownik wybierze kontakt do wyświetlenia. Dzięki temu adapter synchronizacji może pobierać zdjęcia w wysokiej rozdzielczości i najnowsze elementy strumienia dla kontaktu.
  • Rejestrując powiadomienie w aplikacji do obsługi kontaktów na urządzeniu i u dostawcy kontaktów, możesz otrzymać intencję, gdy kontakt zostanie wyświetlony, i w tym momencie zaktualizować stan kontaktu z poziomu usługi. To podejście może być szybsze i zużywać mniej przepustowości niż pełna synchronizacja za pomocą adaptera synchronizacji.
  • Użytkownicy mogą dodać kontakt do Twojej usługi sieci społecznościowej, gdy wyświetlają go w aplikacji do obsługi kontaktów na urządzeniu. Możesz to zrobić za pomocą funkcji „Zaproś kontakt”, którą włączasz, łącząc aktywność dodającą istniejący kontakt do Twojej sieci z plikiem XML, który dostarcza aplikacji do obsługi kontaktów na urządzeniu i dostawcy kontaktów szczegółowe informacje o Twojej aplikacji.

Regularna synchronizacja elementów strumienia z dostawcą kontaktów przebiega tak samo jak inne synchronizacje. Więcej informacji o synchronizacji znajdziesz w sekcji Adaptery synchronizacji dostawcy kontaktów. Rejestrowanie powiadomień i zapraszanie kontaktów opisano w następnych 2 sekcjach.

Rejestrowanie obsługi widoków sieci społecznościowych

Aby zarejestrować adapter synchronizacji i otrzymywać powiadomienia, gdy użytkownik wyświetli kontakt zarządzany przez ten adapter:

  1. Utwórz plik o nazwie contacts.xml w katalogu res/xml/ projektu. Jeśli masz już ten plik, możesz pominąć ten krok.
  2. W tym pliku dodaj element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jeśli ten element już istnieje, możesz pominąć ten krok.
  3. Aby zarejestrować usługę, która jest powiadamiana, gdy użytkownik otwiera stronę z informacjami o kontakcie w aplikacji do obsługi kontaktów na urządzeniu, dodaj atrybut viewContactNotifyService="serviceclass" do elementu, gdzie serviceclass to pełna nazwa klasy usługi, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu. W przypadku usługi powiadomień użyj klasy, która rozszerza IntentService, aby umożliwić usłudze odbieranie intencji. Dane w przychodzącej intencji zawierają identyfikator URI treści kontaktu bazowego, który kliknął użytkownik. Z poziomu usługi powiadomień możesz powiązać i wywołać adapter synchronizacji, aby zaktualizować dane kontaktu bazowego.

Aby zarejestrować aktywność, która ma zostać wywołana, gdy użytkownik kliknie element strumienia lub zdjęcie (albo jedno i drugie):

  1. Utwórz plik o nazwie contacts.xml w katalogu res/xml/ projektu. Jeśli masz już ten plik, możesz pominąć ten krok.
  2. W tym pliku dodaj element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jeśli ten element już istnieje, możesz pominąć ten krok.
  3. Aby zarejestrować jedną ze swoich aktywności do obsługi kliknięcia przez użytkownika elementu strumienia w aplikacji do obsługi kontaktów na urządzeniu, dodaj atrybut viewStreamItemActivity="activityclass" do elementu, gdzie activityclass to pełna nazwa klasy aktywności, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu.
  4. Aby zarejestrować jedną ze swoich aktywności do obsługi kliknięcia przez użytkownika zdjęcia strumienia w aplikacji do obsługi kontaktów na urządzeniu, dodaj atrybut viewStreamItemPhotoActivity="activityclass" do elementu, gdzie activityclass to pełna nazwa klasy aktywności, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu.

Element <ContactsAccountType> został szczegółowo opisany w sekcji Element <ContactsAccountType>.

Przychodząca intencja zawiera identyfikator URI treści elementu lub zdjęcia, które kliknął użytkownik. Aby mieć osobne aktywności dla elementów tekstowych i zdjęć, użyj obu atrybutów w tym samym pliku.

interakcja z Twoją usługą sieci społecznościowej;

Użytkownicy nie muszą opuszczać aplikacji do obsługi kontaktów na urządzeniu, aby zaprosić kontakt do Twojego serwisu społecznościowego. Zamiast tego możesz sprawić, że aplikacja do obsługi kontaktów na urządzeniu wyśle intencję zaproszenia kontaktu do jednej z Twoich aktywności. Aby to skonfigurować:

  1. Utwórz plik o nazwie contacts.xml w katalogu res/xml/ projektu. Jeśli masz już ten plik, możesz pominąć ten krok.
  2. W tym pliku dodaj element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jeśli ten element już istnieje, możesz pominąć ten krok.
  3. Dodaj te atrybuty:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    Wartość activityclass to pełna i jednoznaczna nazwa klasy aktywności, która powinna otrzymać intencję. Wartość invite_action_label to ciąg tekstowy wyświetlany w menu Dodaj połączenie w aplikacji do obsługi kontaktów na urządzeniu.

Uwaga: ContactsSource to wycofana nazwa tagu ContactsAccountType.

Dokumentacja pliku contacts.xml

Plik contacts.xml zawiera elementy XML, które kontrolują interakcję adaptera synchronizacji i aplikacji z aplikacją do obsługi kontaktów i dostawcą kontaktów. Te elementy opisujemy w sekcjach poniżej.

Element <ContactsAccountType>

Element <ContactsAccountType> kontroluje interakcję aplikacji z aplikacją do obsługi kontaktów. Składnia:

<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

może zawierać:

<ContactsDataKind>

Opis:

Deklaruje komponenty Androida i etykiety interfejsu, które umożliwiają użytkownikom zapraszanie kontaktów do sieci społecznościowej, powiadamianie użytkowników o aktualizacji strumienia sieci społecznościowej itp.

Zauważ, że prefiks atrybutu android: nie jest konieczny w przypadku atrybutów <ContactsAccountType>.

Atrybuty:

inviteContactActivity
W pełni kwalifikowana nazwa klasy aktywności w aplikacji, którą chcesz aktywować, gdy użytkownik wybierze Dodaj połączenie w aplikacji do obsługi kontaktów na urządzeniu.
inviteContactActionLabel
Ciąg tekstowy wyświetlany dla aktywności określonej w inviteContactActivity w menu Dodaj połączenie. Możesz na przykład użyć ciągu „Obserwuj w mojej sieci”. Jako etykiety możesz użyć identyfikatora zasobu tekstowego.
viewContactNotifyService
Pełna i jednoznaczna nazwa klasy usługi w aplikacji, która powinna otrzymywać powiadomienia, gdy użytkownik wyświetla kontakt. To powiadomienie jest wysyłane przez aplikację do obsługi kontaktów na urządzeniu. Pozwala ono aplikacji odłożyć operacje wymagające dużej ilości danych do czasu, gdy będą potrzebne. Na przykład aplikacja może zareagować na to powiadomienie, odczytując i wyświetlając zdjęcie kontaktu w wysokiej rozdzielczości oraz najnowsze elementy z jego strumienia społecznościowego. Ta funkcja została szczegółowo opisana w sekcji Interakcje w strumieniu społecznościowym.
viewGroupActivity
Pełna i jednoznaczna nazwa klasy aktywności w aplikacji, która może wyświetlać informacje o grupie. Gdy użytkownik kliknie etykietę grupy w aplikacji do obsługi kontaktów na urządzeniu, wyświetli się interfejs tej aktywności.
viewGroupActionLabel
Etykieta wyświetlana przez aplikację do obsługi kontaktów w przypadku elementu sterującego interfejsu, który umożliwia użytkownikowi przeglądanie grup w Twojej aplikacji.

W przypadku tego atrybutu dozwolony jest identyfikator zasobu tekstowego.

viewStreamItemActivity
W pełni kwalifikowana nazwa klasy aktywności w aplikacji, którą aplikacja do obsługi kontaktów na urządzeniu uruchamia, gdy użytkownik kliknie element strumienia dla kontaktu bazowego.
viewStreamItemPhotoActivity
Pełna nazwa klasy aktywności w aplikacji, którą aplikacja do obsługi kontaktów na urządzeniu uruchamia, gdy użytkownik kliknie zdjęcie w elemencie strumienia dla kontaktu bazowego.

Element <ContactsDataKind>

Element <ContactsDataKind> kontroluje wyświetlanie niestandardowych wierszy danych aplikacji w interfejsie aplikacji do obsługi kontaktów. Składnia:

<ContactsDataKind
        android:mimeType="MIMEtype"
        android:icon="icon_resources"
        android:summaryColumn="column_name"
        android:detailColumn="column_name">

contained in:

<ContactsAccountType>

Opis:

Użyj tego elementu, aby aplikacja do obsługi kontaktów wyświetlała zawartość niestandardowego wiersza danych jako część szczegółów kontaktu bazowego. Każdy element podrzędny <ContactsDataKind> elementu <ContactsAccountType> reprezentuje typ niestandardowego wiersza danych, który adapter synchronizacji dodaje do tabeli ContactsContract.Data. Dodaj po jednym elemencie <ContactsDataKind> dla każdego niestandardowego typu MIME, którego używasz. Nie musisz dodawać tego elementu, jeśli masz niestandardowy wiersz danych, dla którego nie chcesz wyświetlać danych.

Atrybuty:

android:mimeType
Niestandardowy typ MIME zdefiniowany dla jednego z niestandardowych typów wierszy danych w tabeli ContactsContract.Data. Na przykład wartość vnd.android.cursor.item/vnd.example.locationstatus może być niestandardowym typem MIME dla wiersza danych, który rejestruje ostatnią znaną lokalizację kontaktu.
android:icon
Obiekt rysowalny Androida, który aplikacja do obsługi kontaktów wyświetla obok Twoich danych. Użyj tego, aby poinformować użytkownika, że dane pochodzą z Twojej usługi.
android:summaryColumn
Nazwa kolumny dla pierwszej z 2 wartości pobranych z wiersza danych. Wartość jest wyświetlana jako pierwszy wiersz wpisu dla tego wiersza danych. Pierwszy wiersz ma służyć jako podsumowanie danych, ale jest to opcjonalne. Zobacz też android:detailColumn.
android:detailColumn
Nazwa kolumny dla drugiej z 2 wartości pobranych z wiersza danych. Wartość jest wyświetlana w drugim wierszu wpisu dla tego wiersza danych. Zobacz też android:summaryColumn.

Dodatkowe funkcje dostawcy kontaktów

Oprócz głównych funkcji opisanych w poprzednich sekcjach dostawca kontaktów oferuje te przydatne funkcje do pracy z danymi kontaktów:

  • Grupy kontaktów
  • Funkcje edycji zdjęć

Grupy kontaktów

Dostawca kontaktów może opcjonalnie oznaczać kolekcje powiązanych kontaktów danymi grupy. Jeśli serwer powiązany z kontem użytkownika chce zachować grupy, adapter synchronizacji dla danego rodzaju konta powinien przesyłać dane grup między dostawcą kontaktów a serwerem. Gdy użytkownicy dodają nowy kontakt do serwera, a następnie umieszczają go w nowej grupie, adapter synchronizacji musi dodać nową grupę do tabeli ContactsContract.Groups. Grupy, do których należy kontakt bazowy, są przechowywane w tabeli ContactsContract.Data z użyciem typu MIME ContactsContract.CommonDataKinds.GroupMembership.

Jeśli projektujesz adapter synchronizacji, który będzie dodawać dane kontaktów bazowych z serwera do dostawcy kontaktów, i nie używasz grup, musisz poinformować dostawcę, aby udostępnił Twoje dane. W kodzie, który jest wykonywany, gdy użytkownik dodaje konto do urządzenia, zaktualizuj wiersz ContactsContract.Settings dodawany przez dostawcę kontaktów dla tego konta. W tym wierszu ustaw wartość w kolumnie Settings.UNGROUPED_VISIBLE na 1. Dzięki temu dostawca kontaktów zawsze będzie udostępniać dane kontaktów, nawet jeśli nie używasz grup.

Zdjęcia kontaktu

Tabela ContactsContract.Data przechowuje zdjęcia jako wiersze z typem MIME Photo.CONTENT_ITEM_TYPE. Kolumna CONTACT_ID wiersza jest połączona z kolumną _ID kontaktu bazowego, do którego należy. Klasa ContactsContract.Contacts.Photo definiuje podtabelę ContactsContract.Contacts zawierającą informacje o głównym zdjęciu kontaktu, które jest głównym zdjęciem głównego kontaktu bazowego. Podobnie klasa ContactsContract.RawContacts.DisplayPhoto definiuje podtabelę klasy ContactsContract.RawContacts zawierającą informacje o głównym zdjęciu kontaktu bazowego.

Dokumentacja referencyjna dotycząca ContactsContract.Contacts.Photo i ContactsContract.RawContacts.DisplayPhoto zawiera przykłady pobierania informacji o zdjęciach. Nie ma klasy ułatwiającej pobieranie głównej miniatury kontaktu bazowego, ale możesz wysłać zapytanie do tabeli ContactsContract.Data, wybierając kolumny _ID, Photo.CONTENT_ITEM_TYPE i IS_PRIMARY kontaktu bazowego, aby znaleźć wiersz jego głównego zdjęcia.

Dane ze strumienia społecznościowego dotyczące danej osoby mogą też zawierać zdjęcia. Są one przechowywane w tabeli android.provider.ContactsContract.StreamItemPhotos, która została szczegółowo opisana w sekcji Zdjęcia w strumieniu społecznościowym.