dostawca kontaktów

Dostawca kontaktów to zaawansowany i elastyczny komponent Androida, który służy do zarządzania centralnym repozytorium danych 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ż uzyskiwać dostęp do danych z tego konta w swojej własnej aplikacji oraz przenosić dane między urządzeniem a usługami online. Dostawca obsługuje szeroką gamę źródeł danych i stara się zarządzać jak największą ilością danych o każdej osobie, co sprawia, że organizacja jest złożona. Z tego powodu interfejs API dostawcy ma obszerny zestaw klas umów i interfejsów, które ułatwiają zarówno pobieranie, jak i modyfikowanie danych.

Tematy w tym przewodniku:

  • Podstawowa struktura dostawców.
  • Jak pobrać dane od dostawcy.
  • Jak zmodyfikować dane u dostawcy.
  • Jak utworzyć adapter synchronizacji w celu synchronizowania danych między Twoim serwerem a dostawcą kontaktów.

W tym przewodniku zakładamy, że znasz podstawy dostawców treści na Androida. Aby dowiedzieć się więcej o dostawcach treści na Androida, przeczytaj przewodnik – podstawy dostawców treści.

Organizacja dostawcy kontaktów

Dostawca kontaktów to komponent dostawcy treści na Androida. Zawiera 3 rodzaje danych o osobie. Każdy z nich odpowiada tabeli oferowanej przez dostawcę, jak widać na ilustracji 1:

Rysunek 1. Struktura tabeli dostawców kontaktów.

Te trzy tabele są zwykle nazywane nazwami klas umów. Klasy definiują stałe identyfikatory URI treści, nazwy kolumn i wartości kolumn używane przez tabele:

ContactsContract.Contacts tabela
Wiersze reprezentujące różne osoby na podstawie zagregowanych wierszy nieprzetworzonych kontaktów.
ContactsContract.RawContacts tabela
Wiersze zawierające podsumowanie danych użytkownika powiązane z kontem i typem.
ContactsContract.Data tabela
Wiersze zawierające szczegóły nieprzetworzonego kontaktu, takie jak adresy e-mail lub numery telefonów.

Inne tabele reprezentowane przez klasy umów w ContactsContract są tabelami pomocniczymi, których dostawca kontaktów używa do zarządzania operacjami lub do obsługi określonych funkcji kontaktów lub aplikacji telefonicznych na urządzeniu.

Nieprzetworzone kontakty

Nieprzetworzony kontakt reprezentuje dane osoby pochodzące z jednego typu konta z tą samą nazwą. Dostawca kontaktów dopuszcza więcej niż jedną usługę online jako źródło danych o danej osobie, dlatego dostawca kontaktów zezwala na wiele nieprzetworzonych kontaktów dla tej samej osoby. Wiele nieprzetworzonych kontaktów umożliwia też łączenie danych użytkownika z więcej niż 1 konta tego samego typu.

Większość danych nieprzetworzonego kontaktu nie jest przechowywana w tabeli ContactsContract.RawContacts. Jest przechowywany w co najmniej 1 wierszu w tabeli ContactsContract.Data. Każdy wiersz danych ma kolumnę Data.RAW_CONTACT_ID, w której znajduje się wartość RawContacts._ID w nadrzędnym wierszu ContactsContract.RawContacts.

Ważne kolumny nieprzetworzonych kontaktów

Ważne kolumny w tabeli ContactsContract.RawContacts są wymienione w tabeli 1. Przeczytaj poniższe uwagi:

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

Nazwa kolumny Używanie Uwagi
ACCOUNT_NAME Nazwa konta typu konta, które jest źródłem tego nieprzetworzonego kontaktu. Na przykład nazwa konta Google jest jednym z adresów Gmail należących do właściciela urządzenia. Aby dowiedzieć się więcej, zapoznaj się z następnym wpisem dotyczącym ACCOUNT_TYPE. Format nazwy zależy od rodzaju konta. Nie musi to być adres e-mail.
ACCOUNT_TYPE Rodzaj konta, który jest źródłem tego nieprzetworzonego kontaktu. Na przykład typ konta Google to com.google. Zawsze kwalifikuj rodzaj swojego konta za pomocą identyfikatora domeny, która należy do Ciebie lub którą kontrolujesz. Dzięki temu rodzaj konta będzie niepowtarzalny. Konto typu, w którym dostępne są dane kontaktów, ma zwykle powiązany adapter synchronizacji, który synchronizuje się z dostawcą kontaktów.
DELETED Flaga „deleted” dla nieprzetworzonego kontaktu. Ta flaga pozwala dostawcy kontaktów wewnętrznie przechowywać wiersz, dopóki adaptery synchronizacji nie będą w stanie usunąć go ze swoich serwerów, a na koniec usunąć go z repozytorium.

Uwagi

Oto ważne uwagi o tabeli ContactsContract.RawContacts:

  • Nieprzetworzona nazwa kontaktu nie jest przechowywana w odpowiednim wierszu w funkcji ContactsContract.RawContacts. Zamiast tego jest przechowywany w tabeli ContactsContract.Data w wierszu ContactsContract.CommonDataKinds.StructuredName. Nieprzetworzony kontakt ma tylko 1 wiersz tego typu w tabeli ContactsContract.Data.
  • Uwaga: aby używać danych z własnego konta w wierszu nieprzetworzonych danych kontaktowych, musisz je najpierw zarejestrować w AccountManager. Aby to zrobić, poproś użytkowników o dodanie rodzaju i nazwy konta do listy kont. Jeśli tego nie zrobisz, dostawca kontaktów automatycznie usunie nieprzetworzony wiersz kontaktów.

    Jeśli na przykład chcesz, aby aplikacja przechowujeła dane kontaktów Twojej usługi internetowej w domenie com.example.dataservice, a konto użytkownika usługi to becky.sharp@dataservice.example.com, użytkownik musi najpierw dodać konta „typ” (com.example.dataservice) i „nazwa” (becky.smart@dataservice.example.com), zanim aplikacja będzie mogła dodać nieprzetworzone wiersze kontaktów. Możesz wyjaśnić użytkownikowi ten wymóg w dokumentacji lub poprosić użytkownika o dodanie typu i nazwy lub oba te wymagania. Typy i nazwy kont zostały szczegółowo opisane w następnej sekcji.

Źródła nieprzetworzonych danych kontaktów

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

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

Ten użytkownik włączył Synchronizuj kontakty dla wszystkich 3 tych 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 „Thomasa Higginsona”. Później loguje się w Gmailu jako emilyd@gmail.com i wysyła e-maila do „Thomasa Higginsona”, który automatycznie dodaje go jako kontakt. Obserwuje też konto „colonel_tom” (identyfikator Thomasa Higginsona na Twitterze) na Twitterze.

W wyniku tej pracy dostawca kontaktów tworzy trzy nieprzetworzone kontakty:

  1. Nieprzetworzony kontakt użytkownika „Thomas Higginson” powiązany z kontem emily.dickinson@gmail.com. Rodzaj konta użytkownika to Google.
  2. Drugi nieprzetworzony kontakt „Thomas Higginson” powiązany z użytkownikiem emilyd@gmail.com. Typ konta użytkownika to także Google. Istnieje drugi nieprzetworzony kontakt, mimo że jego nazwa jest taka sama jak poprzednia, ponieważ osoba ta została dodana do innego konta użytkownika.
  3. Trzeci nieprzetworzony kontakt „Thomas Higginson” powiązany z adresem „belle_of_amherst”. Rodzaj konta użytkownika to Twitter.

Dane

Jak już wspomnieliśmy, dane nieprzetworzonego kontaktu są przechowywane w wierszu ContactsContract.Data połączonym z wartością _ID nieprzetworzonego kontaktu. Dzięki temu jeden nieprzetworzony kontakt może mieć wiele wystąpień danych tego samego typu, takich jak adresy e-mail lub numery telefonów. Jeśli na przykład „Thomas Higginson” dla emilyd@gmail.com (wiersz nieprzetworzonych kontaktów Tomasza 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 oba wiersze adresów e-mail i łączy je z nieprzetworzonym kontaktem.

Zwróć uwagę, że w tej pojedynczej tabeli przechowywane są różne typy danych. Wyświetlana nazwa, numer telefonu, adres e-mail, adres pocztowy, zdjęcie i szczegóły witryny znajdują się w tabeli ContactsContract.Data. Aby ułatwić zarządzanie tą funkcją, tabela ContactsContract.Data zawiera niektóre kolumny z nazwami opisowymi, a inne z nazwami ogólnymi. Zawartość kolumny z nazwą opisową ma to samo znaczenie niezależnie od typu danych w wierszu, a zawartość kolumny z nazwą opisową ma różne znaczenie w zależności od typu danych.

Opisowe nazwy kolumn

Oto kilka przykładów opisowych nazw kolumn:

RAW_CONTACT_ID
Wartość kolumny _ID nieprzetworzonego kontaktu 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ą dostępne na licencji open source i mogą być używane przez dowolną aplikację lub adapter synchronizacji współpracujący z dostawcą kontaktów.
IS_PRIMARY
Jeśli ten typ wiersza danych może wystąpić więcej niż raz w przypadku nieprzetworzonego kontaktu, w kolumnie IS_PRIMARY będzie wskazywany wiersz danych zawierający dane podstawowe tego typu. Jeśli np. użytkownik przytrzyma i przytrzyma numer telefonu kontaktu i wybierze Ustaw jako domyślny, w wierszu ContactsContract.Data w kolumnie IS_PRIMARY wartość będzie inna niż zero.

Ogólne nazwy kolumn

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

Kolumna DATA1 została zindeksowana. Dostawca kontaktów zawsze używa tej kolumny w przypadku 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 BLOB, takich jak miniatury zdjęć.

Nazwy kolumn zależne od typu

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

Na przykład klasa ContactsContract.CommonDataKinds.Email definiuje stałe nazwy kolumny dla konkretnego typu w wierszu ContactsContract.Data z typem MIME Email.CONTENT_ITEM_TYPE. Klasa zawiera stałą ADDRESS w kolumnie adresu e-mail. Rzeczywista wartość kolumny ADDRESS to „dane1”, która jest taka sama jak ogólna nazwa kolumny.

Uwaga: nie dodawaj własnych danych niestandardowych do tabeli ContactsContract.Data za pomocą wiersza, który zawiera jeden ze wstępnie zdefiniowanych typów MIME dostawcy. Jeśli to zrobisz, możesz utracić dane lub spowodować nieprawidłowe działanie dostawcy. Na przykład nie dodawaj 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 wierszu własnego niestandardowego typu MIME, możesz definiować własne nazwy kolumn z konkretnymi typami i używać ich w dowolny sposób.

Rysunek 2 pokazuje, jak opisowe kolumny i kolumny danych są wyświetlane w wierszu ContactsContract.Data oraz jak nazwy kolumn „nakładki” o określonym typie nakładają się na ogólne nazwy kolumn.

Jak nazwy kolumn związane z typem są mapowane na ogólne nazwy kolumn

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

Klasy nazw kolumn dla danego typu

W tabeli 2 znajdziesz najczęściej używane klasy nazw kolumn z określonymi typami:

Tabela 2. Klasy nazw kolumn dla danego typu

Klasa mapowania Typ danych Uwagi
ContactsContract.CommonDataKinds.StructuredName Dane dotyczące imienia i nazwiska nieprzetworzonego kontaktu powiązanego z tym wierszem danych. Nieprzetworzony kontakt ma tylko jeden z tych wierszy.
ContactsContract.CommonDataKinds.Photo Główne zdjęcie nieprzetworzonego kontaktu powiązanego z tym wierszem danych. Nieprzetworzony kontakt ma tylko jeden z tych wierszy.
ContactsContract.CommonDataKinds.Email Adres e-mail nieprzetworzonego kontaktu powiązanego z tym wierszem danych. Kontakt nieprzetworzony może mieć kilka adresów e-mail.
ContactsContract.CommonDataKinds.StructuredPostal Adres pocztowy nieprzetworzonego kontaktu powiązanego z tym wierszem danych. Kontakt nieprzetworzony może mieć kilka adresów pocztowych.
ContactsContract.CommonDataKinds.GroupMembership Identyfikator, który łączy nieprzetworzony kontakt z jedną z grup u dostawcy kontaktów. Grupy są opcjonalną funkcją typu i nazwy konta. Szczegółowo opisujemy je w sekcji Grupy kontaktów.

Kontakty

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

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

Dostawca kontaktów tworzy nowy kontakt w odpowiedzi na dodanie nowego nieprzetworzonego kontaktu, który nie pasuje do żadnego istniejącego kontaktu. Dostawca robi to również wtedy, gdy dane istniejącego nieprzetworzonego kontaktu zmienią się w taki sposób, że nie będzie on już zgodny z kontaktem, do którego został wcześniej dołączony. Jeśli aplikacja lub adapter synchronizacji utworzy nowy nieprzetworzony kontakt pasujący do istniejącego kontaktu, ten nieprzetworzony kontakt zostanie zagregowany z istniejącym kontaktem.

Dostawca kontaktów łączy wiersz kontaktu ze swoimi nieprzetworzonymi wierszami kontaktów z kolumną _ID wiersza kontaktu w tabeli Contacts. Kolumna CONTACT_ID w tabeli nieprzetworzonych kontaktów ContactsContract.RawContacts zawiera wartości _ID w wierszu kontaktów powiązanym z każdym nieprzetworzonym wierszem kontaktów.

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

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

Tabele główne dostawcy kontaktów

Rysunek 3. Relacje w tabelach Kontakty, Nieprzetworzone kontakty i Szczegóły.

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

Zgodnie z wymienionymi warunkami system okresowo czyści wszystkie wartości zapisane w tych polach danych:

Interfejsy API używane do ustawiania powyższych pól danych również są nieaktualne:

Poniższe 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 należą do określonego rodzaju danych.

Jeśli Twoje aplikacje uzyskują dostęp do tych pól lub interfejsów API albo je aktualizują, użyj alternatywnych metod. Możesz na przykład w określonych przypadkach użycia korzystać z dostawców treści prywatnych lub innych danych przechowywanych w aplikacji bądź systemach backendu.

Aby sprawdzić, czy zmiana nie wpłynie na funkcje Twojej aplikacji, możesz ręcznie wyczyścić te pola danych. Aby to zrobić, uruchom poniższe polecenie ADB na urządzeniu z Androidem 4.1 (poziom interfejsu API 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 są one też przesyłane do dostawcy kontaktów z usług internetowych przez adaptery 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.

Na Androidzie usługa internetowa, z którą działa adapter synchronizacji, jest określana przez typ konta. Każdy adapter synchronizacji działa z jednym typem konta, ale może obsługiwać wiele nazw kont dla tego typu. Typy kont i ich nazwy są pokrótce opisane w sekcji Źródła nieprzetworzonych danych kontaktów. Poniższe definicje zawierają więcej szczegółów i opisują, w jaki sposób typ i nazwa konta są powiązane z adapterami synchronizacji i usługami.

Rodzaj rachunku
Identyfikuje usługę, w której użytkownik przechowuje dane. W większości przypadków użytkownik musi się uwierzytelnić w usłudze. Na przykład Kontakty Google to rodzaj konta oznaczony kodem google.com. Ta wartość odpowiada rodzajowi konta używanego przez usługę AccountManager.
nazwę konta.
Identyfikuje konkretne konto lub login danego typu konta. Konta Kontaktów Google nie różnią się od kont Google, które mają adres e-mail jako nazwę konta. Inne usługi mogą używać jednowyrazowej nazwy użytkownika lub identyfikatora liczbowego.

Rodzaje kont nie muszą być unikalne. Użytkownik może skonfigurować wiele kont Kontaktów Google i pobrać swoje dane do dostawcy kontaktów. Może się tak zdarzyć, jeśli użytkownik ma jeden zestaw kontaktów osobistych na potrzeby nazwy konta osobistego, a drugi zestaw do pracy. Nazwy kont są zwykle niepowtarzalne. Razem określają określony 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. Szczegółowo opisaliśmy to w sekcji Adaptery synchronizacji dostawcy kontaktów.

Rysunek 4 pokazuje, w jaki sposób dostawca kontaktów wchodzi w przepływ danych o użytkownikach. W polu „Synchronizuj adaptery” każdy adapter jest oznaczony etykietą według typu 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ą prosić o te uprawnienia:

Uprawnienia do odczytu co najmniej 1 tabeli
READ_CONTACTS, określone 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ślone w AndroidManifest.xml z elementem <uses-permission> jako <uses-permission android:name="android.permission.WRITE_CONTACTS">.

Te uprawnienia nie obejmują danych profilowych użytkownika. Profil użytkownika i jego wymagane uprawnienia zostały omówione w następnej sekcji: Profil użytkownika.

Pamiętaj, że dane kontaktowe użytkownika są osobiste i poufne. Użytkownicy martwią się o swoją prywatność, więc nie chcą, aby aplikacje gromadziły dane o nich i ich kontaktach. Jeśli nie jest jasne, dlaczego potrzebujesz uprawnień dostępu do danych kontaktów, aplikacja może przyznać Twojej aplikacji niskie oceny 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 na urządzeniu, a nie jeden z kontaktów użytkownika. Wiersz kontaktów w profilu jest połączony z nieprzetworzonym wierszem kontaktów dla każdego systemu, który używa profilu. Każdy wiersz nieprzetworzonego kontaktu w profilu może zawierać 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 odpowiednio uprawnień android.Manifest.permission#READ_PROFILE i android.Manifest.permission#WRITE_PROFILE 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. Pamiętaj, aby w opisie aplikacji wyjaśnić użytkownikowi, do czego potrzebujesz uprawnień dostępu do jego profilu.

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 nieprzetworzonych kontaktów lub danych dla profilu. Na przykład ten fragment kodu pobiera dane związane z profilem:

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 wiele wierszy kontaktów i chcesz ustalić, czy jeden z nich to profil użytkownika, przetestuj kolumnę IS_USER_PROFILE wiersza. Jeśli kontakt jest profilem użytkownika, ta kolumna ma wartość „1”.

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, w tym w wierszach tabel nieprzetworzonych kontaktów, danych i kontaktów, a także w tabelach ContactsContract.Settings i ContactsContract.SyncState. Poniższa tabela przedstawia wpływ poszczególnych elementów metadanych:

Tabela 3. Metadane w dostawcy kontaktów

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

Adaptery synchronizacji, które modyfikują tabele nieprzetworzonych danych kontaktowych lub tabeli danych, powinny zawsze dołączać ciąg CALLER_IS_SYNCADAPTER do używanego identyfikatora URI treści. Zapobiega to oznaczaniu wierszy jako zanieczyszczonych. W przeciwnym razie modyfikacje adaptera synchronizacji wydają się być modyfikacjami lokalnymi i wysyłanymi na serwer, mimo że serwer był źródłem modyfikacji.

„1” – zmiana od ostatniej synchronizacji wymaga synchronizacji z serwerem.
ContactsContract.RawContacts VERSION Numer wersji tego wiersza. Dostawca kontaktów automatycznie zwiększa tę wartość po każdej zmianie wiersza lub powiązanych z nim danych.
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 nieprzetworzony kontakt z kontem, na którym został utworzony. Gdy adapter synchronizacji tworzy nowy nieprzetworzony kontakt, w tej kolumnie należy ustawić unikalny identyfikator serwera dla nieprzetworzonego kontaktu. Gdy aplikacja na Androida tworzy nowy nieprzetworzony kontakt, ta kolumna powinna pozostać pusta. Informuje to adapter synchronizacji, że powinien utworzyć nowy nieprzetworzony kontakt na serwerze i uzyska wartość dla parametru SOURCE_ID.

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

  • Niepowtarzalny: każdy nieprzetworzony kontakt na koncie musi mieć własny identyfikator źródłowy. Jeśli go nie wymusisz stosowania, spowoduje to problemy w aplikacji do obsługi kontaktów. Zwróć uwagę, że 2 nieprzetworzone kontakty dla tego samego typu konta mogą mieć ten sam identyfikator źródła. Na przykład nieprzetworzony kontakt „Thomas Higginson” dla konta emily.dickinson@gmail.com może mieć taki sam identyfikator źródła jak nieprzetworzony kontakt „Thomas Higginson” dla konta emilyd@gmail.com.
  • Stabilny: identyfikatory źródeł są trwałą częścią danych usługi online dla nieprzetworzonego kontaktu. Jeśli na przykład użytkownik wyczyści pamięć kontaktów w ustawieniach Google Apps i ponownie zsynchronizuje dane, przywrócone nieprzetworzone kontakty powinny mieć te same identyfikatory źródeł co wcześniej. Jeśli nie wymusisz stosowania tego trybu, 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 odnosi się do zgodności 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” – przy tym koncie i typie konta kontakty nienależące do grupy są niewidoczne w interfejsach aplikacji na Androida. Domyślnie kontakty są niewidoczne, jeśli żaden z ich nieprzetworzonych kontaktów nie należy do grupy (przynależność do grupy nieprzetworzonego kontaktu jest oznaczona co najmniej jednym wierszem ContactsContract.CommonDataKinds.GroupMembership w tabeli ContactsContract.Data). Ustawiając tę flagę w wierszu tabeli ContactsContract.Settings dla typu 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” – dla tego typu konta i konta kontakty nienależące do grupy są widoczne w interfejsach aplikacji.
ContactsContract.SyncState (wszystkie) Ta tabela służy do przechowywania metadanych adaptera synchronizacji. Dzięki tej tabeli możesz trwale przechowywać na urządzeniu stan synchronizacji i inne dane związane z synchronizacją.

Dostęp dostawcy kontaktów

Ta sekcja zawiera wskazówki dotyczące uzyskiwania dostępu do danych dostawcy kontaktów. Obejmują one:

  • Zapytania dotyczące encji.
  • Zbiorcze modyfikowanie.
  • Pobieranie i modyfikowanie z intencjami.
  • Integralność danych.

Wprowadzanie zmian za pomocą adaptera synchronizacji zostało również szczegółowo omówione w sekcji Adaptery synchronizacji dostawców kontaktów.

Wysyłanie zapytań dotyczących encji

Tabele dostawców kontaktów mają hierarchię, dlatego często przydaje się możliwość pobrania wiersza i wszystkich połączonych z nim wierszy „podrzędnych”. Aby na przykład wyświetlić wszystkie informacje o osobie, możesz pobrać wszystkie wiersze ContactsContract.RawContacts z jednego wiersza ContactsContract.Contacts lub wszystkie wiersze ContactsContract.CommonDataKinds.Email z jednego wiersza ContactsContract.RawContacts. Aby to ułatwić, dostawca kontaktów udostępnia konstrukcje entity, które działają jak złączenia bazy danych między tabelami.

Encja jest podobna do tabeli składającej się z kolumn wybranych z tabeli nadrzędnej i jej tabeli podrzędnej. Wysyłając zapytanie do elementu, podajesz prognozę i kryteria wyszukiwania na podstawie kolumn dostępnych dla tego elementu. Wynik to Cursor, który zawiera po 1 wierszu na każdy pobrany wiersz tabeli podrzędnej. Jeśli na przykład wyślesz zapytanie ContactsContract.Contacts.Entity o nazwę kontaktu, a wszystkie wiersze ContactsContract.CommonDataKinds.Email zawierają wszystkie nieprzetworzone kontakty z tym imieniem i nazwiskiem, otrzymasz Cursor zawierający po 1 wierszu dla każdego wiersza ContactsContract.CommonDataKinds.Email.

Elementy upraszczają zapytania. Za pomocą encji możesz jednocześnie pobrać wszystkie dane dotyczące kontaktu lub nieprzetworzonego kontaktu bez konieczności wysyłania zapytania do tabeli nadrzędnej w celu uzyskania identyfikatora, a potem wysyłania z użyciem tego identyfikatora zapytania do tabeli podrzędnej. Ponadto dostawca kontaktów przetwarza zapytanie do elementu w ramach jednej transakcji, co zapewnia spójność pobranych danych.

Uwaga: element zwykle nie zawiera wszystkich kolumn tabeli nadrzędnej i podrzędnej. Jeśli spróbujesz pracować z nazwą kolumny, której nie ma na liście stałych nazw kolumny encji, otrzymasz Exception.

Poniższy fragment kodu pokazuje, jak pobrać wszystkie nieprzetworzone wiersze kontaktów dla danego kontaktu. Fragment kodu jest częścią większej aplikacji zawierającej 2 działania: „główne” i „szczegóły”. Główna aktywność zawiera listę wierszy kontaktów. Gdy użytkownik wybierze jeden z nich, aktywność wysyła swój identyfikator do działania szczegółów. W działaniu szczegółów działania używany jest ContactsContract.Contacts.Entity, aby wyświetlić wszystkie wiersze danych ze wszystkich nieprzetworzonych kontaktów powiązanych z wybranym kontaktem.

Ten fragment pochodzi z aktywności „szczegóły”:

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 tej metody jest Cursor z wynikami zapytania. Dane z tego urządzenia Cursor możesz pobrać w swojej aplikacji, aby je wyświetlić lub nad nimi pracować.

Modyfikowanie zbiorcze

Jeśli to możliwe, wstawiaj, aktualizuj i usuwaj dane dostawcy kontaktów w „trybie grupowym” przez utworzenie ArrayList z ContentProviderOperation obiektów i wywołanie applyBatch(). Dostawca kontaktów wykonuje wszystkie operacje na obiekcie applyBatch() w ramach jednej transakcji, dlatego Twoje modyfikacje nigdy nie pozostawiają repozytorium kontaktów w niespójnym stanie. Modyfikacja zbiorcza umożliwia również wstawienie nieprzetworzonego kontaktu i jego szczegółowych danych w tym samym czasie.

Uwaga: jeśli chcesz zmodyfikować pojedynczy nieprzetworzony kontakt, zamiast wprowadzać zmiany w aplikacji, wyślij intencję do aplikacji do obsługi kontaktów na urządzeniu. Szczegółowo opisujemy to w sekcji Pobieranie i modyfikowanie zgodnie z intencjami.

Punkty zysku

Modyfikacja zbiorcza obejmująca dużą liczbę operacji może zablokować inne procesy, co źle wpływa na ogólne wrażenia użytkownika. Aby uporządkować wszystkie modyfikacje, które chcesz wprowadzić, na jak najmniejszej liczbie oddzielnych list i jednocześnie zapobiec blokowaniu przez nie systemu, ustaw punkty zysku dla co najmniej 1 operacji. Punkt zysku to obiekt ContentProviderOperation, którego wartość isYieldAllowed() jest ustawiona na true. Gdy dostawca kontaktów napotyka punkt zysku, wstrzymuje działanie innych procesów i zamyka bieżącą transakcję. Gdy dostawca uruchomi się ponownie, kontynuuje następną operację w ArrayList i rozpoczyna nową transakcję.

Punkty zysku prowadzą do więcej niż 1 transakcji na wywołanie applyBatch(). Z tego powodu należy ustawić punkt zysku dla ostatniej operacji na zbiorze powiązanych wierszy. Na przykład ustaw punkt zysku dla ostatniej operacji w zbiorze, który dodaje wiersze nieprzetworzone kontaktów i powiązane z nimi wiersze danych, lub ostatnią operację dla zbioru wierszy powiązanych z jednym kontaktem.

Punkty zysku są również jednostką działania niepodzielnego. Wszystkie połączenia między 2 punktami zysku będą skuteczne lub nieskuteczne jako pojedyncza jednostka. Jeśli nie ustawisz żadnych punktów zysku, najmniejsza niepodzielna operacja to cała grupa operacji. Jeśli użyjesz punktów zysku, zapobiegniesz obniżeniu wydajności systemu przez operacje, a jednocześnie zapewnisz, że podzbiór operacji jest niepodzielny.

Wsteczne odniesienia do modyfikacji

Gdy wstawiasz nowy wiersz nieprzetworzonego kontaktu i powiązane z nim wiersze danych jako zbiór obiektów ContentProviderOperation, musisz połączyć wiersze danych z wierszem nieprzetworzonego kontaktu, wstawiając wartość _ID nieprzetworzonego kontaktu jako wartość RAW_CONTACT_ID. Ta wartość jest jednak niedostępna podczas tworzenia wiersza danych (ContentProviderOperation), ponieważ w wierszu nieprzetworzonych danych kontaktowych nie został jeszcze zastosowany parametr ContentProviderOperation. 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 w parze klucz-wartość. Wartość tego argumentu powinna być nazwą kolumny w tabeli, którą modyfikujesz.
previousResult
Oparty na 0 indeks wartości w tablicy obiektów ContentProviderResult z applyBatch(). W trakcie wykonywania operacji wsadowych wynik każdej z nich jest przechowywany w tablicy pośredniej wyników. Wartość previousResult to indeks jednego z tych wyników, który jest pobierany i zapisywany z wartością key. Dzięki temu możesz wstawić nowy nieprzetworzony rekord kontaktu i odzyskać jego wartość _ID, a następnie utworzyć odwołanie do tej wartości podczas dodawania wiersza ContactsContract.Data.

Przy pierwszym wywołaniu applyBatch() tworzona jest cała tablica wyników. Jej rozmiar jest równy rozmiarowi ArrayList z podanych przez Ciebie obiektów ContentProviderOperation. Wszystkie elementy tablicy wyników są jednak ustawione na null, a jeśli spróbujesz utworzyć odwołanie do wyniku operacji, która nie została jeszcze zastosowana, withValueBackReference() zwraca Exception.

Poniższe fragmenty kodu pokazują, jak wstawić nieprzetworzony kontakt i nowe dane zbiorczo. Zawierają one kod, który określa punkt zysku i korzysta z odwołania wstecznego.

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

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ę, która wstawia nieprzetworzony kontakt 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 z wierszami wyświetlanej nazwy, numeru telefonu i adresu e-mail.

Każdy obiekt konstruktora operacji używa withValueBackReference() do pobrania RAW_CONTACT_ID. Odniesienie wskazuje z powrotem do obiektu ContentProviderResult z pierwszej operacji, który dodaje nieprzetworzony kontakt i zwraca jego nową wartość _ID. W związku z tym każdy wiersz danych jest automatycznie połączony przez RAW_CONTACT_ID z nowym wierszem danych ContactsContract.RawContacts, do którego należy.

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

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 zawiera wywołanie applyBatch(), które wstawia nowe nieprzetworzone wiersze kontaktu 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 wsadowe umożliwiają również wdrożenie optymalnej kontroli równoczesności, czyli metody stosowania transakcji modyfikacji bez konieczności blokowania repozytorium bazowego. Aby użyć tej metody, zastosuj transakcję, a potem sprawdź, czy w tym samym czasie nie wprowadzono żadnych innych zmian. Jeśli zauważysz, że nastąpiła niespójna modyfikacja, wycofuj transakcję i spróbuj ją jeszcze raz.

Optymalna kontrola równoczesności jest przydatna w przypadku urządzeń mobilnych, na których w danym momencie jest tylko 1 użytkownik, a jednoczesne korzystanie z repozytorium danych jest rzadkie. Blokady nie są używane, więc nie marnujesz czasu na ustawianie blokad ani czekanie, aż inne transakcje je odblokują.

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

  1. Pobierz kolumnę VERSION nieprzetworzonego kontaktu wraz z innymi pobranymi danymi.
  2. Utwórz obiekt ContentProviderOperation.Builder odpowiedni do egzekwowania ograniczenia, używając metody newAssertQuery(Uri). W przypadku identyfikatora URI treści użyj elementu RawContacts.CONTENT_URI z dołączonym do niego atrybutem _ID nieprzetworzonego kontaktu.
  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 mieć pewność, że tylko 1 wiersz zostanie przetestowany przez to potwierdzenie.
  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ę zbiorczą.

Jeśli wiersz z nieprzetworzonym kontaktem zostanie zaktualizowany przez inną operację w okresie od odczytania wiersza do momentu, gdy spróbujesz go zmodyfikować, próba potwierdzenia adresu ContentProviderOperation zakończy się niepowodzeniem, a cała grupa operacji zostanie cofnięta. Możesz wtedy ponowić próbę przesłania wsadu lub wykonać inne działanie.

Ten fragment kodu pokazuje, jak utworzyć ContentProviderOperation „assert” po wysłaniu zapytania dotyczącego pojedynczego nieprzetworzonego kontaktu za pomocą metody 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 z intencjami

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. Ten typ dostępu umożliwia użytkownikom:

  • Wybierz kontakt z listy i poproś o jego zwrot do aplikacji w celu dalszej pracy.
  • Edycja danych istniejącego kontaktu.
  • Wstaw nowy nieprzetworzony kontakt dla dowolnego z kont.
  • Usuń dane kontaktów lub kontaktów.

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

Jeśli 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 umożliwiającego dostęp do dostawcy. Nie musisz też prosić dostawcy o uprawnienia do odczytu ani zapisu. 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 zamiaru uzyskania dostępu do dostawcy został szczegółowo opisany w przewodniku Podstawowe informacje o dostawcy treści w sekcji „Dostęp do danych przez intencje”. Podsumowanie czynności, typu MIME i wartości danych używanych w przypadku dostępnych zadań znajdziesz w tabeli 4. Wartości dodatkowe, których możesz używać z funkcją putExtra(), znajdziesz w dokumentacji referencyjnej ContactsContract.Intents.Insert:

Tabela 4. intencji dostawcy kontaktów.

Zadanie Działanie Dane Typ MIME Uwagi
Wybieranie kontaktu z listy ACTION_PICK Jedna z tych możliwości: Not used Wyświetla listę nieprzetworzonych kontaktów lub listę danych nieprzetworzonych kontaktów w zależności od podanego typu identyfikatora URI treści.

Wywołanie startActivityForResult(), które zwraca identyfikator URI treści wybranego wiersza. Forma identyfikatora URI to identyfikator URI treści tabeli z dołączonym do niego ciągiem znaków LOOKUP_ID. Aplikacja do obsługi kontaktów na urządzeniu przekazuje uprawnienia do odczytu i zapisu temu identyfikatorowi URI treści przez cały okres aktywności użytkownika. Więcej informacji znajdziesz w przewodniku Podstawowe informacje o dostawcach treści.

Wstawianie nowego nieprzetworzonego kontaktu Insert.ACTION Nie dotyczy RawContacts.CONTENT_TYPE, typ MIME dla zbioru nieprzetworzonych kontaktów. Wyświetla ekran Dodaj kontakt w aplikacji Kontakty na urządzeniu. Zostaną wyświetlone wartości dodatków, które dodasz do intencji. Jeśli zostanie wysłany za pomocą funkcji startActivityForResult(), identyfikator URI treści nowo dodanego nieprzetworzonego kontaktu jest przekazywany z powrotem do metody wywołania zwrotnego onActivityResult() aktywności w argumencie Intent, w polu „data”. Aby uzyskać wartość, wywołaj getData().
Edytowanie kontaktu ACTION_EDIT CONTENT_LOOKUP_URI dla kontaktu. Działanie edytora umożliwi użytkownikowi edytowanie wszystkich danych powiązanych z tym kontaktem. Contacts.CONTENT_ITEM_TYPE, jeden kontakt. Wyświetla ekran Edytuj kontakt w aplikacji do obsługi kontaktów. Wyświetlane są wartości dodatków, które dodasz do intencji. Gdy użytkownik kliknie Gotowe, aby zapisać zmiany, aktywność wróci na pierwszy plan.
Zawierać selektor, w którym można też dodawać dane. ACTION_INSERT_OR_EDIT Nie dotyczy CONTENT_ITEM_TYPE Ta intencja zawsze wyświetla ekran wyboru aplikacji do obsługi kontaktów. Użytkownik może wybrać kontakt do edycji lub dodać nowy. W zależności od wyboru użytkownika pojawi się ekran edycji lub ekran dodawania. Wyświetlane są też dane dodatkowe przekazane w intencji. Jeśli aplikacja wyświetla dane kontaktów, takie jak adres e-mail lub numer telefonu, użyj tego intencji, aby użytkownik mógł dodać te dane do istniejącego kontaktu. kontakt

Uwaga: nie ma potrzeby wysyłania wartości nazwy w dodatkach intencji, ponieważ użytkownik zawsze wybiera istniejącą nazwę lub dodaje nową. Co więcej, jeśli wyślesz nazwę użytkownika, a użytkownik postanowi edytować, aplikacja do obsługi kontaktów wyświetli nazwę, którą wyślesz, zastępując poprzednią wartość. Jeśli użytkownik nie zauważy tej zmiany i zapisze zmianę, stara wartość zostanie utracona.

Aplikacja do obsługi kontaktów na urządzeniu nie pozwala na celowe usunięcie nieprzetworzonego kontaktu ani żadnych jego danych. Aby usunąć nieprzetworzony kontakt, użyj właściwości ContentResolver.delete() lub ContentProviderOperation.newDelete().

Ten fragment kodu pokazuje, jak utworzyć i wysłać intencję, która wstawia nowy nieprzetworzony kontakt 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 wrażliwe dane, które według użytkowników powinny być poprawne i aktualne, dlatego dostawca kontaktów ma dobrze zdefiniowane reguły integralności danych. Podczas modyfikowania danych kontaktowych odpowiadasz za przestrzeganie tych reguł. Ważne reguły są wymienione tutaj:

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 nadrzędnym wierszem ContactsContract.RawContacts.
Wiersz ContactsContract.Data, który nie jest połączony z ContactsContract.RawContacts, nie będzie widoczny w aplikacji Kontakty na urządzeniu i może to powodować problemy z adapterami synchronizacji.
Zmieniaj dane tylko tych nieprzetworzonych kontaktów, które należą do Ciebie.
Pamiętaj, że dostawca kontaktów zarządza zwykle danymi z kilku różnych typów kont/usług online. Musisz się upewnić, że Twoja aplikacja modyfikuje lub usuwa dane tylko z wierszy należących do Ciebie i wstawia tylko dane o typie konta i nazwie, które kontrolujesz.
Zawsze używaj stałych zdefiniowanych w zadaniu ContactsContract i jego podklas na potrzeby urzędów, identyfikatorów URI treści, ścieżek URI, nazw kolumn, typów MIME i wartości TYPE.
Użycie tych stałych pomaga uniknąć błędów. Jeśli któraś ze stałych zostanie wycofana, otrzymasz też powiadomienie z ostrzeżeniami kompilatora.

Niestandardowe wiersze danych

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ą korzystać z kolumny zdefiniowanej w zadaniu ContactsContract.DataColumns, ale możesz zmapować własne nazwy kolumn specyficzne dla danego typu na domyślne. W aplikacji do obsługi kontaktów na urządzeniu dane wierszy są wyświetlane, ale nie można ich edytować ani usuwać, a użytkownicy nie mogą dodawać kolejnych danych. Aby umożliwić użytkownikom modyfikowanie niestandardowych wierszy danych, musisz udostępnić w swojej aplikacji działanie edytora.

Aby wyświetlić dane niestandardowe, udostępnij plik contacts.xml zawierający element <ContactsAccountType> i co najmniej 1 z jego elementów podrzędnych: <ContactsDataKind>. Szczegółowo opisaliśmy to w sekcji <ContactsDataKind> element.

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

Adaptery synchronizacji dostawcy kontaktów

Dostawca kontaktów został zaprojektowany do obsługi synchronizacji danych kontaktów między urządzeniem a usługą online. Dzięki temu użytkownicy będą mogli pobrać istniejące dane na nowe urządzenie i przesłać je na nowe konto. Synchronizacja zapewnia też, że użytkownicy mają dostęp do najnowszych danych niezależnie od źródła dodatków 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żesz 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 dostawcy usługi i treści, ale może obsługiwać wiele nazw kont w tej samej usłudze. Platforma umożliwia też użycie wielu adapterów synchronizacji dla tej samej usługi i dostawcy.

Synchronizuj klasy i pliki adaptera

Adaptera synchronizacji wdrażasz jako podklasę klasy AbstractThreadedSyncAdapter i instalujesz go jako część aplikacji na Androida. System uczy się o adapterze synchronizacji na podstawie elementów w pliku manifestu aplikacji oraz ze specjalnego pliku XML, na który wskazuje ten plik. Plik XML określa typ konta usługi online i uprawnienia dostawcy treści, które razem w unikalny sposób identyfikują adapter. Adapter synchronizacji stanie się aktywny dopiero po dodaniu przez użytkownika konta dla typu konta adaptera synchronizacji i włączenie synchronizacji dla dostawcy treści, z którym synchronizuje się adapter synchronizacji. W tym momencie system zacznie zarządzać adapterem, wywołując go zgodnie z potrzebą do przeprowadzenia synchronizacji między dostawcą treści a serwerem.

Uwaga: użycie typu konta do identyfikacji adaptera synchronizacji umożliwia systemowi wykrywanie i grupowanie adapterów synchronizacji, które uzyskują dostęp do różnych usług w tej samej organizacji. Na przykład wszystkie adaptery synchronizacji usług online Google mają ten sam typ konta: com.google. Gdy użytkownicy dodają konto Google na swoich urządzeniach, wszystkie zainstalowane adaptery synchronizacji dla usług Google są wymienione razem. Każdy wymieniony adapter synchronizacji synchronizuje się z innym dostawcą treści na urządzeniu.

Większość usług wymaga od użytkowników zweryfikowania tożsamości przed uzyskaniem dostępu do danych, dlatego system Android oferuje platformę uwierzytelniania podobną do platformy adaptera synchronizacji i często z nią wykorzystywaną. Platforma uwierzytelniania korzysta z mechanizmów uwierzytelniania wtyczek, które są podklasami AbstractAccountAuthenticator. Tożsamość użytkownika jest weryfikowana w ten sposób:

  1. Zbiera nazwę użytkownika, hasło lub podobne informacje (jego dane logowania).
  2. Wysyła dane logowania do usługi
  3. Analizuje odpowiedź usługi.

Jeśli usługa zaakceptuje dane logowania, mechanizm uwierzytelniania może je zapisać do późniejszego użycia. Ze względu na platformę uwierzytelniania wtyczek usługa AccountManager może zapewniać dostęp do wszystkich uwierzytelniania obsługiwanych przez ten mechanizm i decyduje się na ich udostępnienie (np. uwierzytelnianie OAuth2).

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

Implementacja adaptera synchronizacji

Aby wdrożyć adapter synchronizacji dla dostawcy kontaktów, zacznij od utworzenia aplikacji na Androida, która będzie zawierać:

Komponent Service, który odpowiada na żądania systemu, aby utworzyć powiązanie z adapterem synchronizacji.
Gdy system chce przeprowadzić synchronizację, wywołuje metodę onBind() usługi, aby uzyskać IBinder dla adaptera synchronizacji. Dzięki temu system będzie mógł wykonywać wywołania metod adaptera między procesami.
Rzeczywisty adapter synchronizacji wdrożony jako konkretną podklasę klasy AbstractThreadedSyncAdapter.
Ta klasa zajmuje się pobieraniem danych z serwera, przesyłaniem danych z urządzenia i rozwiązywaniem konfliktów. Główną pracę adaptera wykonuje się w metodzie onPerformSync(). Wystąpienie tej klasy musi być utworzone jako single.
Podklasa klasy Application.
Ta klasa działa jako fabryka singletonu adaptera synchronizacji. Utwórz instancję adaptera synchronizacji za pomocą metody onCreate() i podaj statyczną metodę „getter”, która zwróci singleton do metody onBind() usługi adaptera synchronizacji.
Opcjonalnie: komponent Service, który odpowiada na żądania systemu w celu uwierzytelnienia użytkowników.
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 usługi uwierzytelniającej. Dzięki temu system może wykonywać wywołania między procesami uwierzytelniania.
Opcjonalnie: konkretna podklasa typu AbstractAccountAuthenticator, która obsługuje żądania uwierzytelniania.
Ta klasa udostępnia metody wywoływane przez interfejs AccountManager w celu uwierzytelnienia danych logowania użytkownika na serwerze. Szczegóły procesu uwierzytelniania różnią się w zależności od używanej technologii serwera. Więcej informacji o uwierzytelnianiu znajdziesz w dokumentacji oprogramowania serwera.
Pliki XML, które definiują adapter synchronizacji i uwierzytelnianie w systemie.
Opisane wcześniej komponenty adaptera synchronizacji i usługi uwierzytelniającej są zdefiniowane w elementach <service> w pliku manifestu aplikacji. Elementy te zawierają<meta-data>elementy podrzędne, które przekazują do systemu określone dane:
  • Element <meta-data> usługi adaptera synchronizacji wskazuje plik XML res/xml/syncadapter.xml. Zawiera on z kolei identyfikator URI usługi internetowej, który będzie synchronizowany z dostawcą kontaktów, oraz typ konta dla usługi internetowej.
  • Opcjonalne: element <meta-data> elementu uwierzytelniającego wskazuje plik XML res/xml/authenticator.xml. Z kolei ten plik określa typ konta obsługiwany przez ten mechanizm uwierzytelniania, a także zasoby interfejsu użytkownika, które pojawiają się podczas procesu uwierzytelniania. Typ konta określony w tym elemencie musi być taki sam jak typ konta określony dla adaptera synchronizacji.

Dane strumienia danych społecznościowych

Tabele android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos zarządzają danymi przychodzącymi z sieci społecznościowych. Możesz utworzyć adapter synchronizacji, który doda do tych tabel dane strumienia z Twojej sieci, albo odczytać dane strumienia z tych tabel i wyświetlić je w swojej aplikacji lub w obu tych miejscach. Dzięki tym funkcjom Twoje usługi i aplikacje w sieciach społecznościowych można zintegrować z systemami Android.

Tekst strumienia społecznościowego

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

Przechowuj dane ze strumienia w tych kolumnach:

android.provider.ContactsContract.StreamItemsKolumny#ACCOUNT_TYPE
Wymagane. Rodzaj konta użytkownika dla nieprzetworzonego kontaktu powiązanego z tym elementem strumienia. Pamiętaj, aby ustawić tę wartość przy wstawianiu elementu strumienia.
android.provider.ContactsContract.StreamItemsKolumny#ACCOUNT_NAME
Wymagane. Nazwa konta użytkownika dla nieprzetworzonego kontaktu powiązanego z tym elementem strumienia. Pamiętaj, aby ustawić tę wartość przy wstawianiu elementu strumienia.
Kolumny identyfikatorów
Wymagane. Podczas wstawiania elementu strumienia musisz wstawić te kolumny z identyfikatorami:
  • android.provider.ContactsContract.StreamItemsColumn#CONTACT_ID: wartość android.provider.BaseColumn#_ID kontaktu, z którym jest powiązany ten element strumienia.
  • android.provider.ContactsContract.StreamItemsColumn#CONTACT_LOOKUP_KEY: Wartość android.provider.ContactsContract.ContactsColumn#LOOKUP_KEY kontaktu, z którym jest powiązany ten element strumienia.
  • android.provider.ContactsContract.StreamItemsColumn#RAW_CONTACT_ID: wartość android.provider.BaseColumn#_ID nieprzetworzonego kontaktu, z którym jest powiązany ten element strumienia.
android.provider.ContactsContract.StreamItemsKolumny#COMMENTS
Opcjonalne. Przechowuje podsumowanie informacji, które możesz wyświetlić na początku elementu strumienia.
android.provider.ContactsContract.StreamItemsKolumny#TEXT
Tekst elementu strumienia, treść opublikowana przez źródło elementu lub opis działania, które spowodowało wygenerowanie elementu strumienia. Ta kolumna może zawierać dowolne formatowanie i osadzone obrazy zasobów, które mogą być renderowane przez funkcję fromHtml(). Dostawca może skracać lub obcinać długie treści, ale stara się unikać naruszania tagów.
android.provider.ContactsContract.StreamItemsKolumny#TIMESTAMP
Ciąg tekstowy zawierający czas wstawienia lub aktualizacji elementu strumienia podany w postaci milisekundy od początku epoki. Za obsługę tej kolumny odpowiadają aplikacje, które wstawiają lub aktualizują elementy strumienia. Nie jest ona automatycznie utrzymywana przez dostawcę kontaktów.

Aby wyświetlić dane identyfikacyjne elementów strumienia, użyj funkcji android.provider.ContactsContract.StreamItemsColumn#RES_ICON, android.provider.ContactsContract.StreamItemsColumn#RES_LABEL i android.provider.ContactsContract.StreamItemsColumn#RES_PACKAGE, aby dodać link do zasobów w aplikacji.

Tabela android.provider.ContactsContract.StreamItems zawiera też kolumny od android.provider.ContactsContract.StreamItemsColumn#SYNC1 do android.provider.ContactsContract.StreamItemsColumn#SYNC4, aby umożliwić wyłączne korzystanie z adapterów synchronizacji.

Zdjęcia w strumieniu danych społecznościowych

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

Kolumna android.provider.ContactsContract.StreamItemPhotos#PHOTO (obiekt BLOB).
Plik binarny zdjęcia zmieniony przez dostawcę w celu przechowywania i wyświetlania. Ta kolumna jest dostępna na potrzeby zgodności wstecznej z poprzednimi wersjami dostawcy kontaktów, który używał jej do przechowywania zdjęć. Jednak w bieżącej wersji nie należy używać tej kolumny do przechowywania zdjęć. Zamiast tego do przechowywania zdjęć w pliku używaj funkcji android.provider.ContactsContract.StreamItemPhotosColumn#PHOTO_FILE_ID lub android.provider.ContactsContract.StreamItemPhotosColumn#PHOTO_URI (oba zostały opisane w poniższych punktach). Ta kolumna zawiera teraz miniaturę zdjęcia, którą można przeczytać.
android.provider.ContactsContract.StreamItemPhotosKolumny#PHOTO_FILE_ID
Liczbowy identyfikator zdjęcia nieprzetworzonego kontaktu. 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.StreamItemPhotosKolumny#PHOTO_uri
Identyfikator URI treści wskazujący bezpośrednio plik zdjęcia dla zdjęcia reprezentowanego w tym wierszu. Wywołaj funkcję openAssetFileDescriptor(), używając tego identyfikatora URI, aby uzyskać uchwyt dla pliku zdjęcia.

Korzystanie z tabel strumieni społecznościowych

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

  • Te tabele wymagają dodatkowych uprawnień dostępu. Aby odczytywać z nich dane, aplikacja musi mieć uprawnienie android.Manifest.permission#READ_SOCIAL_STREAM. Aby je modyfikować, Twoja aplikacja musi mieć uprawnienie android.Manifest.permission#WRITE_SOCIAL_STREAM.
  • W tabeli android.provider.ContactsContract.StreamItems liczba wierszy przechowywanych dla każdego nieprzetworzonego kontaktu jest ograniczona. Gdy ten limit zostanie osiągnięty, dostawca kontaktów zwolni miejsce na nowe wiersze elementów strumienia, automatycznie usuwając wiersze o najstarszej kolumnie android.provider.ContactsContract.StreamItemsColumn#TIMESTAMP. Aby uzyskać limit, wyślij zapytanie do identyfikatora URI treści android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_uri. Wszystkie argumenty inne niż identyfikator URI treści możesz pozostawić 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 strumieni społecznościowych zarządzane przez dostawcę kontaktów w połączeniu z aplikacją do obsługi kontaktów na urządzeniu oferują zaawansowane możliwości połączenia systemu sieci społecznościowych z istniejącymi kontaktami. Dostępne są te funkcje:

  • Synchronizując usługę sieci społecznościowych z dostawcą kontaktów za pomocą adaptera synchronizacji, możesz pobierać informacje o ostatniej aktywności związanej z kontaktami użytkownika i zapisywać je w tabelach android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos.
  • Oprócz regularnej synchronizacji możesz też aktywować adapter synchronizacji w celu pobrania dodatkowych danych, gdy użytkownik wybierze do wyświetlenia kontakt. Dzięki temu adapter synchronizacji będzie mógł pobierać zdjęcia w wysokiej rozdzielczości i najnowsze elementy strumienia danych z danego kontaktu.
  • Gdy zarejestrujesz powiadomienie w aplikacji do obsługi kontaktów na urządzeniu i w dostawcy kontaktów, możesz otrzymać intencję, gdy kontakt zostanie wyświetlony, i w tym momencie zaktualizować jego stan z poziomu Twojej usługi. To podejście może być szybsze i wykorzystywać mniejszą przepustowość niż przeprowadzanie pełnej synchronizacji przy użyciu adaptera synchronizacji.
  • Użytkownicy mogą dodać kontakt do usługi społecznościowej, przeglądając go w aplikacji Kontakty na urządzeniu. Aby ją włączyć, użyj funkcji „Zaproś kontakt”, którą włącza się przez połączenie działania, które dodaje istniejący kontakt do sieci, oraz pliku XML, który zawiera szczegóły aplikacji dla aplikacji do obsługi kontaktów na urządzeniu i dostawcy kontaktów.

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.

Rejestracja w celu obsługi wyświetleń w sieciach społecznościowych

Aby zarejestrować adapter synchronizacji w celu otrzymywania powiadomień, gdy użytkownik wyświetli kontakt zarządzany przez Twój adapter synchronizacji:

  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 otworzy stronę z informacjami o kontakcie w aplikacji do obsługi kontaktów na urządzeniu, dodaj do elementu atrybut viewContactNotifyService="serviceclass", gdzie serviceclass to w pełni kwalifikowana nazwa klasy usługi, która powinna otrzymywać 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ć jej odbieranie intencji. Dane w intencji przychodzącej zawierają identyfikator URI treści nieprzetworzonego kontaktu klikniętego przez użytkownika. W usłudze powiadomień możesz powiązać, a następnie wywołać adapter synchronizacji, aby zaktualizować dane nieprzetworzonego kontaktu.

Aby zarejestrować działanie, które ma być wywoływane, gdy użytkownik kliknie element w strumieniu, zdjęcie lub oba te elementy:

  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ć jedno z działań, które ma obsługiwać kliknięcie elementu strumienia w aplikacji do obsługi kontaktów na urządzeniu, dodaj do elementu atrybut viewStreamItemActivity="activityclass", gdzie activityclass jest pełną i jednoznaczną nazwą klasy działania, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu.
  4. Aby zarejestrować jedną z aktywności, która obsługuje użytkownika klikającego zdjęcie ze strumienia w aplikacji do obsługi kontaktów na urządzeniu, dodaj do elementu atrybut viewStreamItemPhotoActivity="activityclass", gdzie activityclass jest pełną i jednoznaczną nazwą klasy działania, 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>.

Intencja przychodzący zawiera identyfikator URI treści elementu lub zdjęcia klikniętego przez użytkownika. Aby utworzyć oddzielne działania dla elementów tekstowych i zdjęć, użyj obu atrybutów w tym samym pliku.

Interakcja z serwisem społecznościowym

Użytkownicy nie muszą zamykać aplikacji do obsługi kontaktów na urządzeniu, aby zaprosić kontakt do witryny społecznościowej. Możesz użyć aplikacji do obsługi kontaktów na urządzeniu, która wyśle Ci intencję zaproszenia kontaktu do jednego z Twoich działań. 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 w pełni kwalifikowana nazwa klasy działania, które powinno otrzymać intencję. Wartość invite_action_label jest ciągiem tekstowym wyświetlanym w menu Dodaj połączenie w aplikacji kontaktów na urządzeniu.

Uwaga: ContactsSource to wycofana nazwa tagu ContactsAccountType.

Dokumentacja contacts.xml

Plik contacts.xml zawiera elementy XML, które kontrolują interakcję adaptera synchronizacji i aplikacji z aplikacjami do obsługi kontaktów i dostawcą kontaktów. Elementy te zostały opisane w kolejnych sekcjach.

Element <ContactsAccountType>

Element <ContactsAccountType> kontroluje interakcję aplikacji z aplikacją do obsługi kontaktów. Ma taką składnię:

<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">

są zawarte w:

res/xml/contacts.xml

może zawierać:

<ContactsDataKind>

Opis:

Deklaruje komponenty Androida i etykiety interfejsu, które umożliwiają użytkownikom zapraszanie jednego z ich kontaktów do sieci społecznościowej, powiadamianie użytkowników o zaktualizowaniu jednego z ich strumieni w sieciach społecznościowych itd.

Zwróć uwagę, że w przypadku atrybutów <ContactsAccountType> prefiks atrybutu android: nie jest wymagany.

Atrybuty:

inviteContactActivity
Pełna 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 w przypadku aktywności określonej w polu inviteContactActivity w menu Dodaj połączenie. Możesz np. użyć tekstu „Obserwuj w mojej sieci”. Dla tej etykiety możesz użyć identyfikatora zasobu w postaci ciągu znaków.
viewContactNotifyService
Pełna i jednoznaczna nazwa klasy usługi w aplikacji, która powinna otrzymywać powiadomienia, gdy użytkownik wyświetli kontakt. To powiadomienie jest wysyłane przez aplikację do obsługi kontaktów na urządzeniu. Pozwala jej opóźnić operacje wymagające dużej ilości danych do momentu, 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 w strumieniach społecznościowych. Szczegółowo opisujemy tę funkcję w sekcji Interakcje ze strumieniem społecznościowym.
viewGroupActivity
Pełna nazwa klasy działania w aplikacji, która może wyświetlać informacje o grupach. Gdy użytkownik kliknie etykietę grupy w aplikacji kontaktów na urządzeniu, wyświetli się interfejs tej aktywności.
viewGroupActionLabel
Etykieta wyświetlana przez aplikację kontaktów na potrzeby elementu sterującego interfejsu, który umożliwia użytkownikowi przeglądanie grup w aplikacji.

Ten atrybut może zawierać identyfikator zasobu w formie ciągu znaków.

viewStreamItemActivity
Pełna nazwa klasy działania w aplikacji, która jest uruchamiana przez aplikację do obsługi kontaktów na urządzeniu, gdy użytkownik kliknie element strumienia w przypadku nieprzetworzonego kontaktu.
viewStreamItemPhotoActivity
Pełna nazwa klasy działania w aplikacji, która jest uruchamiana przez aplikację do obsługi kontaktów na urządzeniu, gdy użytkownik kliknie zdjęcie nieprzetworzonego kontaktu w elemencie strumienia.

Element <ContactsDataKind>

Element <ContactsDataKind> steruje wyświetlaniem w interfejsie aplikacji kontaktów wierszy niestandardowych danych. Ma taką składnię:

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

są zawarte w:

<ContactsAccountType>

Opis:

Użyj tego elementu, aby aplikacja do obsługi kontaktów wyświetlała zawartość niestandardowego wiersza danych jako część szczegółów nieprzetworzonego kontaktu. 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 używanego niestandardowego typu MIME. Nie musisz dodawać elementu, jeśli masz niestandardowy wiersz danych, w przypadku 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 wiersza danych, który rejestruje ostatnią znaną lokalizację kontaktu.
android:icon
Zasób rysowalny Androida, który aplikacja kontaktów wyświetla obok danych. Użyj tego atrybutu, aby wskazać użytkownikowi, ż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 powinien być używany jako podsumowanie danych, ale jest on opcjonalny. Zobacz też android:detailColumn.
android:detailColumn
Nazwa kolumny dla drugiej z 2 wartości pobranych z wiersza danych. Wartość jest wyświetlana jako drugi wiersz 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 związane z pracą z danymi kontaktów:

  • Grupy kontaktów
  • Funkcje zdjęć

Grupy kontaktów

Dostawca kontaktów może opcjonalnie oznaczać kolekcje powiązanych kontaktów etykietami z danymi grupy. Jeśli serwer powiązany z kontem użytkownika chce obsługiwać grupy, adapter synchronizacji dla danego typu konta powinien przenieść 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ć tę grupę do tabeli ContactsContract.Groups. Grupa lub grupy, do których należy nieprzetworzony kontakt, są przechowywane w tabeli ContactsContract.Data z użyciem typu MIME ContactsContract.CommonDataKinds.GroupMembership.

Jeśli projektujesz adapter synchronizacji, który doda nieprzetworzone dane kontaktów z serwera do dostawcy kontaktów, a nie używasz grup, musisz poprosić dostawcę o udostępnienie danych. W kodzie, który jest uruchamiany, gdy użytkownik dodaje konto na urządzeniu, zaktualizuj wiersz ContactsContract.Settings, który dostawca kontaktów dodaje do konta. W tym wierszu ustaw wartość kolumny Settings.UNGROUPED_VISIBLE na 1. Gdy to zrobisz, dostawca kontaktów zawsze udostępni dane Twoich kontaktów, nawet jeśli nie używasz grup.

Zdjęcia kontaktów

Tabela ContactsContract.Data zawiera zdjęcia jako wiersze o typie MIME Photo.CONTENT_ITEM_TYPE. Kolumna CONTACT_ID w wierszu jest połączona z kolumną _ID nieprzetworzonego kontaktu, do którego należy. Klasa ContactsContract.Contacts.Photo definiuje podtabelę tabeli ContactsContract.Contacts zawierającą informacje o zdjęciu głównym zdjęcia kontaktu, które jest zdjęciem podstawowego nieprzetworzonego kontaktu kontaktu. Podobnie klasa ContactsContract.RawContacts.DisplayPhoto określa podtabelę ContactsContract.RawContacts zawierającą informacje o zdjęciu głównym zdjęcia głównego kontaktu.

Dokumentacja referencyjna ContactsContract.Contacts.Photo i ContactsContract.RawContacts.DisplayPhoto zawiera przykłady pobierania informacji o zdjęciach. Nie ma klasy wygodnej do pobierania miniatury głównej dla nieprzetworzonego kontaktu, ale można wysłać zapytanie do tabeli ContactsContract.Data, wybierając kolumny _ID nieprzetworzonego kontaktu, Photo.CONTENT_ITEM_TYPE i IS_PRIMARY, aby znaleźć podstawowy wiersz zdjęcia nieprzetworzonego kontaktu.

Dane ze strumienia społecznościowego dotyczące użytkownika 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 ze strumienia społecznościowego.