Dostawca kontaktów to zaawansowany i elastyczny komponent Androida, który zarządza centralnym repozytorium danych o osobach na urządzeniu. Dostawca kontaktów jest źródłem danych, które widzisz w aplikacji do obsługi kontaktów na urządzeniu. Możesz też uzyskać dostęp do tych danych we własnej aplikacji i przesyłać je między urządzeniem a usługami online. Dostawca obsługuje szeroki zakres źródeł danych i stara się zarządzać jak największą ilością danych każdej osoby, co sprawia, że jego organizacja jest złożona. Z tego powodu interfejs API dostawcy zawiera obszerny zestaw klas kontraktowych i interfejsów, które ułatwiają zarówno pobieranie, jak i modyfikowanie danych.
W tym przewodniku opisano:
- Podstawowa struktura dostawcy.
- jak pobrać dane od dostawcy.
- Jak zmodyfikować dane u dostawcy.
- Jak napisać adapter synchronizacji, aby synchronizować dane z serwera z dostawcą kontaktów.
W tym przewodniku zakładamy, że znasz podstawy mechanizmów dostarczania treści na Androidzie. Aby dowiedzieć się więcej o mechanizmach dostarczania treści na Androidzie, przeczytaj przewodnik Podstawowe informacje o mechanizmach dostarczania treści.
Organizacja dostawcy kontaktów
Dostawca kontaktów to komponent mechanizmu dostarczania treści na Androidzie. Przechowuje 3 typy danych o osobie, z których każdy odpowiada tabeli oferowanej przez dostawcę, jak pokazano na ilustracji 1:
Rysunek 1. Struktura tabeli dostawcy kontaktów.
Te 3 tabele są zwykle nazywane nazwami ich klas kontraktowych. Klasy definiują stałe dla identyfikatorów URI treści, nazw kolumn i wartości kolumn używanych przez tabele:
-
ContactsContract.Contactstabela - Wiersze reprezentujące różne osoby na podstawie agregacji wierszy kontaktów bazowych.
-
ContactsContract.RawContactstabela - Wiersze zawierające podsumowanie danych osoby, dotyczące konkretnego konta i typu użytkownika.
-
ContactsContract.Datatabela - Wiersze zawierające szczegóły kontaktu bazowego, takie jak adresy e-mail czy numery telefonów.
Pozostałe tabele reprezentowane przez klasy kontraktowe w ContactsContract to tabele pomocnicze, których dostawca kontaktów używa do zarządzania swoimi operacjami IT lub obsługi określonych funkcji w aplikacjach do obsługi kontaktów lub połączeń telefonicznych na urządzeniu.
Kontakty bazowe
Kontakt bazowy reprezentuje dane osoby pochodzące z jednego rodzaju konta i nazwy konta. Dostawca kontaktów umożliwia korzystanie z więcej niż jednej usługi online jako źródła danych o osobie, dlatego może on mieć wiele kontaktów bazowych dotyczących tej samej osoby. Wiele kontaktów bazowych umożliwia też użytkownikowi łączenie danych osoby z więcej niż 1 konta tego samego rodzaju konta.
Większość danych kontaktu bazowego nie jest przechowywana w tabeli ContactsContract.RawContacts. Zamiast tego jest przechowywana w co najmniej 1 wierszu tabeli ContactsContract.Data. Każdy wiersz danych ma kolumnę Data.RAW_CONTACT_ID, która zawiera wartość RawContacts._ID nadrzędnego wiersza ContactsContract.RawContacts.
Ważne kolumny kontaktów bazowych
Ważne kolumny w tabeli ContactsContract.RawContacts są wymienione w tabeli 1. Zapoznaj się z uwagami pod tabelą:
Tabela 1. Ważne kolumny kontaktów bazowych.
| Nazwa kolumny | Użyj | Uwagi |
|---|---|---|
ACCOUNT_NAME
|
Nazwa konta dla rodzaju konta, które jest źródłem tego kontaktu bazowego.
Na przykład nazwa konta Google jest jednym z adresów Gmail właściciela urządzenia. Więcej informacji znajdziesz w następnym wpisie dotyczącym ACCOUNT_TYPE.
|
Format tej nazwy zależy od rodzaju konta. Nie musi to być adres e-mail. |
ACCOUNT_TYPE
|
Rodzaj konta, które jest źródłem tego kontaktu bazowego. Na przykład typ konta Google to com.google. Zawsze określaj rodzaj konta za pomocą identyfikatora domeny, której jesteś właścicielem lub którą kontrolujesz. Dzięki temu rodzaj konta będzie unikalny.
|
Rodzaj konta, który oferuje dane kontaktów, zwykle ma powiązany adapter synchronizacji, który synchronizuje się z dostawcą kontaktów. |
DELETED
|
Oznaczenie „usunięty” kontaktu bazowego. | Ta flaga umożliwia dostawcy kontaktów wewnętrzne przechowywanie wiersza do momentu, gdy adaptery synchronizacji będą mogły usunąć go ze swoich serwerów, a następnie z repozytorium. |
Uwagi
Poniżej znajdziesz ważne uwagi dotyczące tabeli ContactsContract.RawContacts:
-
Imię i nazwisko kontaktu bazowego nie są przechowywane w jego wierszu w
ContactsContract.RawContacts. Zamiast tego jest przechowywana w tabeliContactsContract.Data, w wierszuContactsContract.CommonDataKinds.StructuredName. Kontakt bazowy ma tylko 1 wiersz tego typu w tabeliContactsContract.Data. -
Uwaga: aby używać danych własnego konta w wierszu kontaktu bazowego, musisz najpierw zarejestrować je w
AccountManager. Aby to zrobić, poproś użytkowników o dodanie rodzaju konta i nazwy konta do listy kont. Jeśli tego nie zrobisz, dostawca kontaktów automatycznie usunie wiersz kontaktu bazowego.Jeśli na przykład chcesz, aby aplikacja przechowywała dane kontaktów dla usługi internetowej z domeną
com.example.dataservice, a konto użytkownika w Twojej usłudze tobecky.sharp@dataservice.example.com, użytkownik musi najpierw dodać „typ” konta (com.example.dataservice) i „nazwę” konta (becky.smart@dataservice.example.com), zanim aplikacja będzie mogła dodać wiersze kontaktów bazowych. Możesz wyjaśnić użytkownikowi to wymaganie w dokumentacji lub poprosić go o dodanie typu i nazwy (lub obu tych informacji). Rodzaje kont i nazwy kont zostały szczegółowo opisane w następnej sekcji.
Źródła danych kontaktów bazowych
Aby zrozumieć, jak działają kontakty bazowe, weźmy pod uwagę użytkowniczkę „Emily Dickinson”, która ma na urządzeniu zdefiniowane te 3 konta:
emily.dickinson@gmail.comemilyd@gmail.com- Konto na Twitterze „belle_of_amherst”
Ten użytkownik włączył opcję Synchronizuj kontakty dla wszystkich 3 kont w ustawieniach Konta.
Załóżmy, że Emily Dickinson otwiera okno przeglądarki, loguje się w Gmailu jako emily.dickinson@gmail.com, otwiera Kontakty i dodaje kontakt „Thomas Higginson”. Później loguje się w Gmailu jako emilyd@gmail.com i wysyła e-maila do „Thomasa Higginsona”, który automatycznie zostaje dodany jako kontakt. Obserwuje też „colonel_tom” (identyfikator Thomasa Higginsona na Twitterze) na Twitterze.
W wyniku tego działania dostawca kontaktów tworzy 3 kontakty bazowe:
-
Kontakt bazowy „Thomas Higginson” powiązany z aplikacją
emily.dickinson@gmail.com. Rodzaj konta użytkownika to Google. -
Drugi kontakt bazowy dla „Thomasa Higginsona” powiązany z
emilyd@gmail.com. Rodzaj konta użytkownika to również Google. Istnieje drugi kontakt bazowy, mimo że nazwa jest identyczna z poprzednią, ponieważ osoba została dodana do innego konta użytkownika. - Trzeci kontakt bazowy dla „Thomas Higginson” powiązany z „belle_of_amherst”. Rodzaj konta użytkownika to Twitter.
Dane
Jak wspomnieliśmy wcześniej, dane kontaktu bazowego są przechowywane w wierszu ContactsContract.Data, który jest powiązany z wartością _ID kontaktu bazowego. Dzięki temu jeden kontakt bazowy może mieć wiele wystąpień tego samego typu danych, takich jak adresy e-mail czy numery telefonów. Jeśli na przykład „Thomas Higginson” dla emilyd@gmail.com (wiersz kontaktu bazowego dla Thomasa Higginsona powiązany z kontem Google emilyd@gmail.com) ma domowy adres e-mail thigg@gmail.com i służbowy adres e-mail thomas.higginson@gmail.com, dostawca kontaktów przechowuje 2 wiersze adresów e-mail i łączy je z kontaktem bazowym.
Zwróć uwagę, że w tej jednej tabeli przechowywane są różne typy danych. Wiersze z wyświetlaną nazwą, numerem telefonu, adresem e-mail, adresem pocztowym, zdjęciem i witryną znajdują się w tabeli ContactsContract.Data. Aby ułatwić zarządzanie tymi danymi, tabela ContactsContract.Data zawiera kolumny z nazwami opisowymi i ogólnymi. Zawartość kolumny z nazwą opisową ma takie samo znaczenie niezależnie od typu danych w wierszu, natomiast zawartość kolumny z nazwą ogólną ma różne znaczenia w zależności od typu danych.
Opisowe nazwy kolumn
Oto kilka przykładów opisowych nazw kolumn:
-
RAW_CONTACT_ID -
Wartość kolumny
_IDkontaktu bazowego dla tych danych. -
MIMETYPE -
Typ danych przechowywanych w tym wierszu, wyrażony jako niestandardowy typ MIME. Dostawca kontaktów używa typów MIME zdefiniowanych w podklasach
ContactsContract.CommonDataKinds. Te typy MIME są open source i mogą być używane przez dowolną aplikację lub adapter synchronizacji, który współpracuje z dostawcą kontaktów. -
IS_PRIMARY -
Jeśli ten typ wiersza danych może wystąpić więcej niż raz w przypadku kontaktu bazowego, kolumna
IS_PRIMARYoznacza wiersz danych, który zawiera podstawowe dane dla tego typu. Jeśli na przykład użytkownik naciśnie i przytrzyma numer telefonu kontaktu, a następnie wybierze Ustaw jako domyślny, wierszContactsContract.Datazawierający ten numer będzie miał w kolumnieIS_PRIMARYwartość inną niż zero.
Ogólne nazwy kolumn
Dostępnych jest 15 ogólnych kolumn o nazwach od DATA1 do DATA15 oraz 4 dodatkowe kolumny ogólne od SYNC1 do SYNC4, które powinny być używane tylko przez adaptery synchronizacji. Stałe ogólnych nazw kolumn działają zawsze, niezależnie od typu danych zawartych w wierszu.
Kolumna DATA1 jest zindeksowana. Dostawca kontaktów zawsze używa tej kolumny do przechowywania danych, które według niego będą najczęstszym celem zapytania. Na przykład w wierszu adresu e-mail ta kolumna zawiera rzeczywisty adres e-mail.
Zgodnie z konwencją kolumna DATA15 jest zarezerwowana do przechowywania danych typu duży obiekt binarny (BLOB), takich jak miniatury zdjęć.
Nazwy kolumn zależne od typu
Aby ułatwić pracę z kolumnami dla określonego typu wiersza, dostawca kontaktów udostępnia też stałe nazw kolumn dla poszczególnych typów, zdefiniowane w podklasach ContactsContract.CommonDataKinds. Stałe nadają inną nazwę stałej tej samej nazwie kolumny, co ułatwia dostęp do danych w wierszu określonego typu.
Na przykład klasa ContactsContract.CommonDataKinds.Email definiuje stałe nazwy kolumn dla wiersza ContactsContract.Data, który ma typ MIME Email.CONTENT_ITEM_TYPE. Klasa zawiera stałą ADDRESS dla kolumny adresu e-mail. Rzeczywista wartość ADDRESS to „data1”, czyli taka sama jak ogólna nazwa kolumny.
Uwaga: nie dodawaj własnych danych niestandardowych do tabeli ContactsContract.Data za pomocą wiersza, który ma jeden ze wstępnie zdefiniowanych typów MIME dostawcy. Jeśli to zrobisz, możesz utracić dane lub spowodować nieprawidłowe działanie dostawcy. Nie dodawaj na przykład wiersza z typem MIME Email.CONTENT_ITEM_TYPE, który zawiera nazwę użytkownika zamiast adresu e-mail w kolumnie DATA1. Jeśli używasz własnego niestandardowego typu MIME dla wiersza, możesz zdefiniować własne nazwy kolumn dla danego typu i używać ich w dowolny sposób.
Rysunek 2 pokazuje, jak kolumny opisowe i kolumny danych wyglądają w wierszu ContactsContract.Data oraz jak nazwy kolumn dotyczące konkretnego typu „nakładają się” na ogólne nazwy kolumn.
Rysunek 2. Nazwy kolumn zależne od typu i nazwy ogólne.
Klasy nazw kolumn zależne od typu
Tabela 2 zawiera najczęściej używane klasy nazw kolumn dla poszczególnych typów:
Tabela 2. Klasy nazw kolumn zależne od typu
| Klasa mapowania | Typ danych | Uwagi |
|---|---|---|
ContactsContract.CommonDataKinds.StructuredName |
Dane imienia i nazwiska kontaktu bazowego powiązanego z tym wierszem danych. | Kontakt bazowy ma tylko jeden z tych wierszy. |
ContactsContract.CommonDataKinds.Photo |
Główne zdjęcie kontaktu bazowego powiązanego z tym wierszem danych. | Kontakt bazowy ma tylko jeden z tych wierszy. |
ContactsContract.CommonDataKinds.Email |
Adres e-mail kontaktu bazowego powiązanego z tym wierszem danych. | Kontakt bazowy może mieć wiele adresów e-mail. |
ContactsContract.CommonDataKinds.StructuredPostal |
Adres pocztowy kontaktu bazowego powiązanego z tym wierszem danych. | Kontakt bazowy może mieć kilka adresów pocztowych. |
ContactsContract.CommonDataKinds.GroupMembership |
Identyfikator, który łączy kontakt bazowy z jedną z grup u dostawcy kontaktów. | Grupy są opcjonalną funkcją rodzaju konta i nazwy konta. Zostały one szczegółowo opisane w sekcji Grupy kontaktów. |
Kontakty
Dostawca kontaktów łączy wiersze kontaktów bazowych ze wszystkich rodzajów i nazw kont, aby utworzyć kontakt. Ułatwia to wyświetlanie i modyfikowanie wszystkich danych zebranych przez użytkownika na temat danej osoby. Dostawca kontaktów zarządza tworzeniem nowych wierszy kontaktów i agregacją kontaktów bazowych z istniejącym wierszem kontaktu. Ani aplikacje, ani adaptery synchronizacji nie mogą dodawać kontaktów, a niektóre kolumny w wierszu kontaktu są tylko do odczytu.
Uwaga: jeśli spróbujesz dodać kontakt do dostawcy kontaktów za pomocą insert(), pojawi się wyjątek UnsupportedOperationException. Jeśli spróbujesz zaktualizować kolumnę oznaczoną jako „tylko do odczytu”, aktualizacja zostanie zignorowana.
Dostawca kontaktów tworzy nowy kontakt w odpowiedzi na dodanie nowego kontaktu bazowego, który nie pasuje do żadnego z istniejących kontaktów. Dostawca robi to również wtedy, gdy dane istniejącego kontaktu bazowego zmienią się w taki sposób, że nie będą już pasować do kontaktu, do którego były wcześniej dołączone. Jeśli aplikacja lub adapter synchronizacji utworzy nowy kontakt bazowy, który pasuje do istniejącego kontaktu, nowy kontakt bazowy zostanie zagregowany z istniejącym kontaktem.
Dostawca kontaktów łączy wiersz kontaktu z wierszami kontaktu bazowego za pomocą kolumny _ID wiersza kontaktu w tabeli Contacts. Kolumna CONTACT_ID tabeli kontaktów bazowych ContactsContract.RawContacts zawiera wartości _ID dla wiersza kontaktów powiązanego z każdym wierszem kontaktów bazowych.
Tabela ContactsContract.Contacts zawiera też kolumnę LOOKUP_KEY, która jest „stałym” linkiem do wiersza kontaktu. Dostawca kontaktów automatycznie zarządza kontaktami, więc może zmienić wartość _ID w wierszu kontaktu w odpowiedzi na agregację lub synchronizację. Nawet jeśli tak się stanie, identyfikator URI treści CONTENT_LOOKUP_URI w połączeniu z LOOKUP_KEY kontaktu nadal będzie wskazywać wiersz kontaktu, więc możesz użyć LOOKUP_KEY, aby zachować linki do „ulubionych” kontaktów itp. Ta kolumna ma własny format, który nie jest powiązany z formatem kolumny _ID.
Rysunek 3 pokazuje, jak te 3 główne tabele są ze sobą powiązane.
Rysunek 3. Relacje między tabelami Contacts, Raw Contacts i Details.
Uwaga: jeśli opublikujesz aplikację w Sklepie Google Play lub jeśli Twoja aplikacja jest na urządzeniu z Androidem 10 (poziom interfejsu API 29) lub nowszym, pamiętaj, że ograniczony zestaw pól danych i metod kontaktów jest przestarzały.
W podanych warunkach system okresowo czyści wszystkie wartości zapisane w tych polach danych:
-
ContactsContract.ContactOptionsColumns.LAST_TIME_CONTACTED -
ContactsContract.ContactOptionsColumns.TIMES_CONTACTED -
ContactsContract.DataUsageStatColumns.LAST_TIME_USED -
ContactsContract.DataUsageStatColumns.TIMES_USED
Interfejsy API używane do ustawiania poprzednich pól danych również są przestarzałe:
Dodatkowo te pola nie zwracają już częstych kontaktów. Pamiętaj, że niektóre z tych pól wpływają na ranking kontaktów tylko wtedy, gdy kontakty są częścią określonego rodzaju danych.
-
ContactsContract.Contacts.CONTENT_FREQUENT_URI -
ContactsContract.Contacts.CONTENT_STREQUENT_URI -
ContactsContract.Contacts.CONTENT_STREQUENT_FILTER_URI -
CONTENT_FILTER_URI(dotyczy tylko rodzajów danych Email, Phone, Callable i Contactables) -
ENTERPRISE_CONTENT_FILTER_URI(dotyczy tylko rodzajów danych E-mail, Telefon i Możliwość połączenia)
Jeśli Twoje aplikacje uzyskują dostęp do tych pól lub interfejsów API albo je aktualizują, użyj metod alternatywnych. Możesz na przykład spełnić wymagania niektórych przypadków użycia, korzystając z prywatnych mechanizmów dostarczania treści lub innych danych przechowywanych w aplikacji lub systemach backendu.
Aby sprawdzić, czy ta zmiana nie wpływa na działanie aplikacji, możesz ręcznie wyczyścić te pola danych. Aby to zrobić, uruchom to polecenie ADB na urządzeniu z Androidem 4.1 (interfejs API na poziomie 16) lub nowszym:
adb shell content delete \ --uri content://com.android.contacts/contacts/delete_usage
Dane z adapterów synchronizacji
Użytkownicy wpisują dane kontaktów bezpośrednio na urządzeniu, ale dane te trafiają też do dostawcy kontaktów z usług internetowych za pomocą adapterów synchronizacji, które automatyzują przesyłanie danych między urządzeniem a usługami. Adaptery synchronizacji działają w tle pod kontrolą systemu i wywołują metody ContentResolver, aby zarządzać danymi.
W Androidzie usługa internetowa, z którą współpracuje adapter synchronizacji, jest identyfikowana przez rodzaj konta. Każdy adapter synchronizacji działa z jednym rodzajem konta, ale może obsługiwać wiele nazw kont tego rodzaju. Rodzaje kont i nazwy kont są krótko opisane w sekcji Źródła danych kontaktów bazowych. Poniższe definicje zawierają więcej szczegółów i opisują, jak rodzaj i nazwa konta są powiązane z adapterami synchronizacji i usługami.
- Typ konta
-
Identyfikuje usługę, w której użytkownik przechowuje dane. W większości przypadków użytkownik musi uwierzytelnić się w usłudze. Na przykład Kontakty Google to typ konta identyfikowany przez kod
google.com. Ta wartość odpowiada rodzajowi konta używanemu przezAccountManager. - Nazwa konta
- Identyfikuje konkretne konto lub login dla rodzaju konta. Konta Kontaktów Google są takie same jak konta Google, których nazwa jest adresem e-mail. Inne usługi mogą używać jednowyrazowej nazwy użytkownika lub identyfikatora numerycznego.
Rodzaje kont nie muszą być unikalne. Użytkownik może skonfigurować kilka kont Kontaktów Google i pobrać dane do dostawcy kontaktów. Może się tak zdarzyć, jeśli użytkownik ma jeden zestaw kontaktów osobistych dla osobistej nazwy konta i drugi zestaw do pracy. Nazwy kont są zwykle unikalne. Razem identyfikują konkretny przepływ danych między dostawcą kontaktów a usługą zewnętrzną.
Jeśli chcesz przenieść dane usługi do dostawcy kontaktów, musisz napisać własny adapter synchronizacji. Zostało to szczegółowo opisane w sekcji Adaptery synchronizacji dostawcy kontaktów.
Rysunek 4 pokazuje, jak dostawca kontaktów pasuje do przepływu danych o osobach. W polu „adaptery synchronizacji” każdy adapter jest oznaczony rodzajem konta.
Rysunek 4. Przepływ danych dostawcy kontaktów.
Wymagane uprawnienia
Aplikacje, które chcą uzyskać dostęp do dostawcy kontaktów, muszą poprosić o te uprawnienia:
- Dostęp z możliwością odczytu do co najmniej 1 tabeli
-
READ_CONTACTS, określony wAndroidManifest.xmlz elementem<uses-permission>jako<uses-permission android:name="android.permission.READ_CONTACTS">. - Uprawnienia do zapisu w co najmniej 1 tabeli
-
WRITE_CONTACTS, określony wAndroidManifest.xmlz elementem<uses-permission>jako<uses-permission android:name="android.permission.WRITE_CONTACTS">.
Te uprawnienia nie obejmują danych profilu użytkownika. Profil użytkownika i wymagane uprawnienia zostały omówione w sekcji Profil użytkownika poniżej.
Pamiętaj, że dane kontaktowe użytkownika są danymi osobowymi i wrażliwymi. Użytkownicy obawiają się o swoją prywatność, więc nie chcą, aby aplikacje zbierały dane o nich lub ich kontaktach. Jeśli nie jest jasne, dlaczego potrzebujesz uprawnień dostępu do danych kontaktów, użytkownicy mogą wystawić Twojej aplikacji niską ocenę lub po prostu odmówić jej zainstalowania.
profil użytkownika,
Tabela ContactsContract.Contacts zawiera jeden wiersz z danymi profilu użytkownika urządzenia. Te dane opisują user urządzenia, a nie jeden z kontaktów użytkownika. Wiersz kontaktów profilu jest połączony z wierszem kontaktów bazowych w każdym systemie, który używa profilu.
Każdy wiersz kontaktu bazowego profilu może mieć wiele wierszy danych. Stałe umożliwiające dostęp do profilu użytkownika są dostępne w klasie ContactsContract.Profile.
Dostęp do profilu użytkownika wymaga specjalnych uprawnień. Oprócz uprawnień READ_CONTACTS i WRITE_CONTACTS wymaganych do odczytu i zapisu dostęp do profilu użytkownika wymaga uprawnień android.Manifest.permission#READ_PROFILE i android.Manifest.permission#WRITE_PROFILE odpowiednio do odczytu i zapisu.
Pamiętaj, że profil użytkownika należy traktować jako poufny. Uprawnienie android.Manifest.permission#READ_PROFILE umożliwia dostęp do danych umożliwiających identyfikację użytkownika urządzenia. W opisie aplikacji wyjaśnij użytkownikowi, dlaczego potrzebujesz uprawnień dostępu do profilu użytkownika.
Aby pobrać wiersz kontaktu zawierający profil użytkownika, wywołaj ContentResolver.query(). Ustaw identyfikator URI treści na CONTENT_URI i nie podawaj żadnych kryteriów wyboru. Możesz też użyć tego identyfikatora URI treści jako podstawowego identyfikatora URI do pobierania kontaktów bazowych lub danych profilu. Ten fragment kodu pobiera na przykład dane profilu:
Kotlin
// Sets the columns to retrieve for the user profile projection = arrayOf( ContactsContract.Profile._ID, ContactsContract.Profile.DISPLAY_NAME_PRIMARY, ContactsContract.Profile.LOOKUP_KEY, ContactsContract.Profile.PHOTO_THUMBNAIL_URI ) // Retrieves the profile from the Contacts Provider profileCursor = contentResolver.query( ContactsContract.Profile.CONTENT_URI, projection, null, null, null )
Java
// Sets the columns to retrieve for the user profile projection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.LOOKUP_KEY, Profile.PHOTO_THUMBNAIL_URI }; // Retrieves the profile from the Contacts Provider profileCursor = getContentResolver().query( Profile.CONTENT_URI, projection , null, null, null);
Uwaga: jeśli pobierzesz kilka wierszy kontaktów i chcesz sprawdzić, czy jeden z nich jest profilem użytkownika, sprawdź kolumnę IS_USER_PROFILE wiersza. Ta kolumna ma wartość „1”, jeśli kontakt jest profilem użytkownika.
Metadane dostawcy kontaktów
Dostawca kontaktów zarządza danymi, które śledzą stan danych kontaktów w repozytorium. Te metadane dotyczące repozytorium są przechowywane w różnych miejscach, m.in. w wierszach tabel kontaktów bazowych, danych i kontaktów, tabeli ContactsContract.Settings i tabeli ContactsContract.SyncState. W tabeli poniżej znajdziesz informacje o wpływie poszczególnych metadanych:
Tabela 3. Metadane u dostawcy kontaktów
| Tabela | Kolumna | Wartości | Znaczenie |
|---|---|---|---|
ContactsContract.RawContacts |
DIRTY |
„0” – nie zmienił się od ostatniej synchronizacji. |
Oznacza kontakty bazowe, które zostały zmienione na urządzeniu i muszą zostać zsynchronizowane z serwerem. Wartość jest ustawiana automatycznie przez dostawcę kontaktów, gdy aplikacje na Androida aktualizują wiersz.
Adaptery synchronizacji, które modyfikują tabele kontaktów bazowych lub danych, powinny zawsze dołączać ciąg znaków |
| „1” – zmieniono od ostatniej synchronizacji, należy zsynchronizować z serwerem. | |||
ContactsContract.RawContacts |
VERSION |
Numer wersji tego wiersza. | Dostawca kontaktów automatycznie zwiększa tę wartość, gdy zmienia się wiersz lub powiązane z nim dane. |
ContactsContract.Data |
DATA_VERSION |
Numer wersji tego wiersza. | Dostawca kontaktów automatycznie zwiększa tę wartość po każdej zmianie wiersza danych. |
ContactsContract.RawContacts |
SOURCE_ID |
Wartość ciągu znaków, która jednoznacznie identyfikuje ten kontakt bazowy na koncie, na którym został utworzony. |
Gdy adapter synchronizacji tworzy nowy kontakt bazowy, ta kolumna powinna być ustawiona na unikalny identyfikator serwera dla kontaktu bazowego. Gdy aplikacja na Androida tworzy nowy kontakt bazowy, powinna pozostawić tę kolumnę pustą. Sygnalizuje to adapterowi synchronizacji, że powinien utworzyć nowy kontakt bazowy na serwerze i pobrać wartość dla SOURCE_ID.
W szczególności identyfikator źródła musi być unikalny dla każdego typu konta i powinien być stabilny podczas synchronizacji:
|
ContactsContract.Groups |
GROUP_VISIBLE |
„0” – kontakty w tej grupie nie powinny być widoczne w interfejsach aplikacji na Androida. | Ta kolumna zapewnia zgodność z serwerami, które umożliwiają użytkownikowi ukrywanie kontaktów w określonych grupach. |
| „1” – kontakty z tej grupy mogą być widoczne w interfejsach aplikacji. | |||
ContactsContract.Settings |
UNGROUPED_VISIBLE |
„0” – w przypadku tego konta i rodzaju konta kontakty, które nie należą do grupy, są niewidoczne w interfejsach aplikacji na Androida. |
Domyślnie kontakty są niewidoczne, jeśli żaden z ich kontaktów bazowych nie należy do grupy (przynależność kontaktu bazowego do grupy jest oznaczona przez co najmniej 1 wiersz ContactsContract.CommonDataKinds.GroupMembership w tabeli ContactsContract.Data).
Ustawiając tę flagę w wierszu tabeli ContactsContract.Settings dla rodzaju konta i konta, możesz wymusić wyświetlanie kontaktów bez grup.
Jednym z zastosowań tej flagi jest wyświetlanie kontaktów z serwerów, które nie używają grup.
|
| „1” – w przypadku tego konta i rodzaju konta kontakty, które nie należą do grupy, są widoczne w interfejsach aplikacji. | |||
ContactsContract.SyncState |
(wszystkie) | Użyj tej tabeli do przechowywania metadanych adaptera synchronizacji. | Za pomocą tej tabeli możesz trwale przechowywać na urządzeniu stan synchronizacji i inne dane związane z synchronizacją. |
Dostęp do dostawcy kontaktów
W tej sekcji opisujemy wytyczne dotyczące uzyskiwania dostępu do danych od dostawcy kontaktów, ze szczególnym uwzględnieniem tych kwestii:
- Zapytania dotyczące encji.
- Modyfikacja zbiorcza.
- pobieranie i modyfikowanie za pomocą intencji;
- Integralność danych.
Wprowadzanie zmian za pomocą adaptera synchronizacji jest też szczegółowo opisane w sekcji Adaptery synchronizacji dostawcy kontaktów.
Encje, których mają dotyczyć zapytania
Ponieważ tabele dostawcy kontaktów są uporządkowane hierarchicznie, często przydaje się pobranie wiersza i wszystkich powiązanych z nim wierszy podrzędnych. Jeśli na przykład chcesz wyświetlić wszystkie informacje o osobie, możesz pobrać wszystkie wiersze ContactsContract.RawContacts dla pojedynczego wiersza ContactsContract.Contacts lub wszystkie wiersze ContactsContract.CommonDataKinds.Email dla pojedynczego wiersza ContactsContract.RawContacts. Aby to ułatwić, dostawca kontaktów udostępnia konstrukcje encji, które działają jak połączenia baz danych między tabelami.
Encja jest jak tabela składająca się z wybranych kolumn z tabeli nadrzędnej i jej tabeli podrzędnej.
Gdy wysyłasz zapytanie do encji, podajesz projekcję i kryteria wyszukiwania na podstawie kolumn dostępnych w encji. Wynikiem jest obiekt Cursor, który zawiera po 1 wierszu na każdy pobrany wiersz tabeli podrzędnej. Jeśli na przykład wyślesz do ContactsContract.Contacts.Entity zapytanie o nazwę kontaktu i wszystkie wiersze ContactsContract.CommonDataKinds.Email dla wszystkich kontaktów bazowych o tej nazwie, otrzymasz obiekt Cursor zawierający po jednym wierszu dla każdego wiersza ContactsContract.CommonDataKinds.Email.
Encje upraszczają zapytania. Za pomocą encji możesz pobrać wszystkie dane kontaktowe kontaktu lub kontaktu bazowego naraz, zamiast najpierw wysyłać zapytanie do tabeli nadrzędnej, aby uzyskać identyfikator, a następnie wysyłać zapytanie do tabeli podrzędnej z tym identyfikatorem. Dostawca kontaktów przetwarza też zapytanie dotyczące encji w ramach jednej transakcji, co zapewnia wewnętrzną spójność pobranych danych.
Uwaga: encja zwykle nie zawiera wszystkich kolumn tabeli nadrzędnej i podrzędnej. Jeśli spróbujesz użyć nazwy kolumny, której nie ma na liście stałych nazw kolumn dla danego elementu, pojawi się błąd Exception.
Poniższy fragment kodu pokazuje, jak pobrać wszystkie wiersze kontaktów bazowych dla kontaktu. Fragment kodu jest częścią większej aplikacji, która ma 2 aktywności: „główną” i „szczegółową”. Główna aktywność wyświetla listę wierszy kontaktów. Gdy użytkownik wybierze jeden z nich, aktywność wysyła jego identyfikator do aktywności szczegółowej. Aktywność szczegółowa używa ContactsContract.Contacts.Entity, aby wyświetlić wszystkie wiersze danych ze wszystkich kontaktów bazowych powiązanych z wybranym kontaktem.
Ten fragment kodu pochodzi z aktywności „detail”:
Kotlin
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY ) // Initializes the loader identified by LOADER_ID. loaderManager.initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this // The context of the activity ) // Creates a new cursor adapter to attach to the list view cursorAdapter = SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0) // flags // Sets the ListView's backing adapter. rawContactList.adapter = cursorAdapter ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ val projection: Array<String> = arrayOf( ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE ) /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC" /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return CursorLoader( applicationContext, // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder // Sort by the raw contact ID. ) }
Java
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); // Initializes the loader identified by LOADER_ID. getLoaderManager().initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this); // The context of the activity // Creates a new cursor adapter to attach to the list view cursorAdapter = new SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0); // flags // Sets the ListView's backing adapter. rawContactList.setAdapter(cursorAdapter); ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ String[] projection = { ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE }; /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return new CursorLoader( getApplicationContext(), // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder); // Sort by the raw contact ID. }
Po zakończeniu wczytywania LoaderManager wywołuje wywołanie zwrotne do onLoadFinished(). Jednym z argumentów przychodzących do tej metody jest Cursor z wynikami zapytania. We własnej aplikacji możesz pobrać dane z tego elementu Cursor, aby je wyświetlić lub dalej z nimi pracować.
Modyfikacja zbiorcza
Jeśli to możliwe, dane w dostawcy kontaktów należy wstawiać, aktualizować i usuwać w „trybie wsadowym”, tworząc obiekt ArrayList obiektów ContentProviderOperation i wywołując applyBatch(). Dostawca kontaktów wykonuje wszystkie operacje w obiekcie applyBatch() w ramach jednej transakcji, dlatego Twoje modyfikacje nigdy nie pozostawią repozytorium kontaktów w niespójnym stanie. Modyfikacja zbiorcza ułatwia też jednoczesne wstawianie kontaktu bazowego i jego szczegółowych danych.
Uwaga: aby zmodyfikować pojedynczy kontakt bazowy, rozważ wysłanie intencji do aplikacji do obsługi kontaktów na urządzeniu, zamiast obsługiwać modyfikację w aplikacji. Więcej informacji na ten temat znajdziesz w sekcji Pobieranie i modyfikowanie za pomocą intencji.
Punkty wstrzymania
Modyfikacja zbiorcza zawierająca dużą liczbę operacji może blokować inne procesy, co może negatywnie wpłynąć na ogólne wrażenia użytkownika. Aby uporządkować wszystkie modyfikacje, które chcesz wprowadzić, w jak najmniejszej liczbie osobnych list, a jednocześnie zapobiec blokowaniu systemu, ustaw punkty wstrzymania dla co najmniej 1 operacji.
Punkt wstrzymania to obiekt ContentProviderOperation, którego wartość isYieldAllowed() jest ustawiona na true. Gdy dostawca kontaktów napotka punkt wstrzymania, wstrzymuje działanie, aby umożliwić uruchomienie innych procesów, i zamyka bieżącą transakcję. Gdy dostawca uruchomi się ponownie, będzie kontynuować od następnej operacji w ArrayList i rozpocznie nową transakcję.
Punkty wstrzymania powodują więcej niż 1 transakcję na wywołanie funkcji applyBatch(). Z tego powodu należy ustawić punkt wstrzymania dla ostatniej operacji w zestawie powiązanych wierszy.
Możesz na przykład ustawić punkt wstrzymania dla ostatniej operacji w zbiorze, która dodaje wiersze kontaktu bazowego i powiązane z nimi wiersze danych, lub dla ostatniej operacji w zbiorze wierszy powiązanych z pojedynczym kontaktem.
Punkty wstrzymania są też jednostką operacji niepodzielnej. Wszystkie dostępy między 2 punktami wstrzymania zakończą się powodzeniem lub niepowodzeniem jako pojedyncza jednostka. Jeśli nie ustawisz żadnych punktów wstrzymania, najmniejszą operacją niepodzielną będzie cała partia operacji. Jeśli używasz punktów wstrzymania, zapobiegasz pogorszeniu wydajności systemu przez operacje, a jednocześnie zapewniasz, że podzbiór operacji jest niepodzielny.
Odniesienia wsteczne do modyfikacji
Gdy wstawiasz nowy wiersz kontaktu bazowego i powiązane z nim wiersze danych jako zbiór obiektów ContentProviderOperation, musisz połączyć wiersze danych z wierszem kontaktu bazowego, wstawiając wartość _ID kontaktu bazowego jako wartość RAW_CONTACT_ID. Ta wartość nie jest jednak dostępna podczas tworzenia ContentProviderOperation dla wiersza danych, ponieważ nie zastosowano jeszcze ContentProviderOperation dla wiersza kontaktu bazowego. Aby obejść ten problem, klasa ContentProviderOperation.Builder ma metodę withValueBackReference().
Ta metoda umożliwia wstawienie lub zmodyfikowanie kolumny z wynikiem poprzedniej operacji.
Metoda withValueBackReference() ma 2 argumenty:
-
key - Klucz pary klucz-wartość. Wartością tego argumentu powinna być nazwa kolumny w modyfikowanej tabeli.
-
previousResult -
Indeks (liczony od 0) wartości w tablicy obiektów
ContentProviderResultzapplyBatch(). Podczas stosowania operacji zbiorczych wynik każdej operacji jest przechowywany w pośredniej tablicy wyników. WartośćpreviousResultto indeks jednego z tych wyników, który jest pobierany i przechowywany z wartościąkey. Dzięki temu możesz wstawić nowy rekord kontaktu bazowego i uzyskać jego wartość_ID, a następnie utworzyć „odwołanie wsteczne” do tej wartości podczas dodawania wierszaContactsContract.Data.Cała tablica wyników jest tworzona przy pierwszym wywołaniu funkcji
applyBatch(), a jej rozmiar jest równy rozmiarowi podanej przez Ciebie tablicyArrayListobiektówContentProviderOperation. Jednak wszystkie elementy w tablicy wyników są ustawione nanull, a jeśli spróbujesz odwołać się do wyniku operacji, która nie została jeszcze zastosowana,withValueBackReference()zgłosi błądException.
Poniższe fragmenty kodu pokazują, jak zbiorczo wstawić nowy kontakt bazowy i dane. Zawierają one kod, który ustanawia punkt wstrzymania i używa odwołania wstecznego.
Pierwszy fragment kodu pobiera dane kontaktowe z interfejsu. W tym momencie użytkownik wybrał już konto, do którego ma zostać dodany nowy kontakt bazowy.
Kotlin
// Creates a contact entry from the current UI values, using the currently-selected account. private fun createContactEntry() { /* * Gets values from the UI */ val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition] val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]
Java
// Creates a contact entry from the current UI values, using the currently-selected account. protected void createContactEntry() { /* * Gets values from the UI */ String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); int phoneType = contactPhoneTypes.get( contactPhoneTypeSpinner.getSelectedItemPosition()); int emailType = contactEmailTypes.get( contactEmailTypeSpinner.getSelectedItemPosition());
Następny fragment kodu tworzy operację wstawiania wiersza kontaktu bazowego do tabeli ContactsContract.RawContacts:
Kotlin
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. val ops = arrayListOf<ContentProviderOperation>() /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ var op: ContentProviderOperation.Builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Builds the operation and adds it to the array of operations ops.add(op.build());
Następnie kod tworzy wiersze danych dla wyświetlanej nazwy, numeru telefonu i adresu e-mail.
Każdy obiekt kreatora operacji używa metody withValueBackReference(), aby uzyskać RAW_CONTACT_ID. Odwołanie wskazuje obiekt ContentProviderResult z pierwszej operacji, która dodaje wiersz kontaktu bazowego i zwraca nową wartość _ID. W efekcie każdy wiersz danych jest automatycznie łączony za pomocą RAW_CONTACT_ID z nowym wierszem ContactsContract.RawContacts, do którego należy.
Obiekt ContentProviderOperation.Builder, który dodaje wiersz e-maila, jest oznaczony flagą withYieldAllowed(), która ustawia punkt wstrzymania:
Kotlin
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType) /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true); // Builds the operation and adds it to the array of operations ops.add(op.build());
Ostatni fragment kodu pokazuje wywołanie metody applyBatch(), która wstawia nowe wiersze kontaktu bazowego i danych.
Kotlin
// Ask the Contacts Provider to create a new contact Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})") Log.d(TAG, "Creating contact: $name") /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) } catch (e: Exception) { // Display a warning val txt: String = getString(R.string.contactCreationFailure) Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show() // Log exception Log.e(TAG, "Exception encountered while inserting contact: $e") } }
Java
// Ask the Contacts Provider to create a new contact Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" + selectedAccount.getType() + ")"); Log.d(TAG,"Creating contact: " + name); /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // Display a warning Context ctx = getApplicationContext(); CharSequence txt = getString(R.string.contactCreationFailure); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(ctx, txt, duration); toast.show(); // Log exception Log.e(TAG, "Exception encountered while inserting contact: " + e); } }
Operacje zbiorcze umożliwiają też wdrożenie optymistycznej kontroli równoczesności, czyli metody stosowania transakcji modyfikacji bez konieczności blokowania repozytorium bazowego. Aby użyć tej metody, zastosuj transakcję, a następnie sprawdź, czy nie wprowadzono innych zmian w tym samym czasie. Jeśli wykryjesz niespójną modyfikację, wycofaj transakcję i spróbuj ponownie.
Optymistyczna kontrola równoczesności jest przydatna w przypadku urządzeń mobilnych, na których jednocześnie jest tylko 1 użytkownik, a jednoczesny dostęp do repozytorium danych jest rzadki. Ponieważ blokowanie nie jest używane, nie marnuje się czasu na ustawianie blokad ani oczekiwanie na zwolnienie blokad przez inne transakcje.
Aby użyć optymistycznej kontroli równoczesności podczas aktualizowania pojedynczego wiersza ContactsContract.RawContacts, wykonaj te czynności:
-
Pobierz kolumnę
VERSIONkontaktu bazowego wraz z innymi danymi. -
Utwórz obiekt
ContentProviderOperation.Builderodpowiedni do wymuszania ograniczenia, używając metodynewAssertQuery(Uri). W przypadku identyfikatora URI treści użyjRawContacts.CONTENT_URIz dołączonym identyfikatorem_IDkontaktu bazowego. -
W przypadku obiektu
ContentProviderOperation.BuilderwywołajwithValue(), aby porównać kolumnęVERSIONz pobranym numerem wersji. -
W przypadku tego samego
ContentProviderOperation.BuilderwywołajwithExpectedCount(), aby upewnić się, że ta asercja testuje tylko 1 wiersz. -
Wywołaj
build(), aby utworzyć obiektContentProviderOperation, a następnie dodaj go jako pierwszy obiekt wArrayList, który przekazujesz doapplyBatch(). - Zastosuj transakcję wsadową.
Jeśli wiersz kontaktu bazowego zostanie zaktualizowany przez inną operację między odczytaniem wiersza a próbą jego zmodyfikowania, „asercja” ContentProviderOperation nie powiedzie się, a cała seria operacji zostanie wycofana. Możesz wtedy spróbować ponownie przesłać partię lub wykonać inne działanie.
Poniższy fragment kodu pokazuje, jak utworzyć „asercję” ContentProviderOperation po wysłaniu zapytania o pojedynczy kontakt bazowy za pomocą CursorLoader:
Kotlin
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)) mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)) } ... // Sets up a Uri for the assert operation val rawContactUri: Uri = ContentUris.withAppendedId( ContactsContract.RawContacts.CONTENT_URI, rawContactID ) // Creates a builder for the assert operation val assertOp: ContentProviderOperation.Builder = ContentProviderOperation.newAssertQuery(rawContactUri).apply { // Adds the assertions to the assert operation: checks the version withValue(SyncColumns.VERSION, mVersion) // and count of rows tested withExpectedCount(1) } // Creates an ArrayList to hold the ContentProviderOperation objects val ops = arrayListOf<ContentProviderOperation>() ops.add(assertOp.build()) // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops) } catch (e: OperationApplicationException) { // Actions you want to take if the assert operation fails go here }
Java
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); } ... // Sets up a Uri for the assert operation Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID); // Creates a builder for the assert operation ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri); // Adds the assertions to the assert operation: checks the version and count of rows tested assertOp.withValue(SyncColumns.VERSION, mVersion); assertOp.withExpectedCount(1); // Creates an ArrayList to hold the ContentProviderOperation objects ArrayList ops = new ArrayList<ContentProviderOperation>; ops.add(assertOp.build()); // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { ContentProviderResult[] results = getContentResolver().applyBatch(AUTHORITY, ops); } catch (OperationApplicationException e) { // Actions you want to take if the assert operation fails go here }
Pobieranie i modyfikowanie za pomocą intencji
Wysłanie intencji do aplikacji do obsługi kontaktów na urządzeniu umożliwia pośredni dostęp do dostawcy kontaktów. Intencja uruchamia interfejs aplikacji do obsługi kontaktów na urządzeniu, w którym użytkownicy mogą wykonywać czynności związane z kontaktami. Użytkownicy z tym poziomem dostępu mogą:
- Wybierz kontakt z listy i przekaż go do aplikacji, aby kontynuować pracę.
- edytować dane istniejącego kontaktu,
- wstawiać nowy kontakt bazowy na dowolnym koncie,
- Usuwanie danych kontaktów.
Jeśli użytkownik wstawia lub aktualizuje dane, możesz najpierw zebrać dane i wysłać je jako część intencji.
Gdy używasz intencji, aby uzyskać dostęp do dostawcy kontaktów za pomocą aplikacji do obsługi kontaktów na urządzeniu, nie musisz pisać własnego interfejsu ani kodu, aby uzyskać dostęp do dostawcy. Nie musisz też prosić o uprawnienia do odczytu lub zapisu u dostawcy. Aplikacja do obsługi kontaktów na urządzeniu może przekazać Ci uprawnienia do odczytu kontaktu, a ponieważ wprowadzasz zmiany w dostawcy za pomocą innej aplikacji, nie musisz mieć uprawnień do zapisu.
Ogólny proces wysyłania intencji w celu uzyskania dostępu do dostawcy jest szczegółowo opisany w sekcji „Dostęp do danych za pomocą intencji” w przewodniku
Podstawy mechanizmu dostarczania treści. Działanie, typ MIME i wartości danych, których używasz w przypadku dostępnych zadań, są podsumowane w tabeli 4, a wartości dodatkowe, których możesz używać z putExtra(), są wymienione w dokumentacji referencyjnej ContactsContract.Intents.Insert:
Tabela 4. Intencje dostawcy kontaktów.
| Zadanie | Działanie | Dane | Typ MIME | Uwagi |
|---|---|---|---|---|
| Wybieranie kontaktu z listy | ACTION_PICK |
Jedna z tych wartości:
|
Nieużywane |
Wyświetla listę kontaktów bazowych lub listę danych z kontaktu bazowego, w zależności od podanego typu identyfikatora URI treści.
Wywołaj metodę |
| Wstawianie nowego kontaktu bazowego | Insert.ACTION |
Nie dotyczy |
RawContacts.CONTENT_TYPE, typ MIME dla zbioru kontaktów bazowych.
|
Wyświetla ekran Dodaj kontakt w aplikacji do obsługi kontaktów na urządzeniu. Wyświetlą się wartości dodatkowe dodane do intencji. Jeśli zostanie wysłany z startActivityForResult(), identyfikator URI treści nowo dodanego kontaktu bazowego zostanie przekazany z powrotem do metody wywołania zwrotnego onActivityResult() aktywności w argumencie Intent w polu „data”. Aby uzyskać wartość, zadzwoń pod numer getData().
|
| Edytowanie kontaktu | ACTION_EDIT |
CONTENT_LOOKUP_URI dla kontaktu. Aktywność edytora pozwoli użytkownikowi edytować dowolne dane powiązane z tym kontaktem.
|
Contacts.CONTENT_ITEM_TYPE, 1 kontakt. |
Wyświetla ekran edytowania kontaktu w aplikacji do obsługi kontaktów. Wyświetlą się wartości dodatkowe dodane do intencji. Gdy użytkownik kliknie Gotowe, aby zapisać zmiany, Twoja aktywność wróci na pierwszy plan. |
| Wyświetlanie selektora, który może też dodawać dane | ACTION_INSERT_OR_EDIT |
Nie dotyczy |
CONTENT_ITEM_TYPE
|
Ta intencja zawsze wyświetla ekran wyboru aplikacji do kontaktów. Użytkownik może wybrać kontakt do edycji lub dodać nowy kontakt. W zależności od wyboru użytkownika pojawi się ekran edycji lub dodawania, a także dane dodatkowe przekazane w intencji. Jeśli aplikacja wyświetla dane kontaktowe, takie jak adres e-mail lub numer telefonu, użyj tej intencji, aby umożliwić użytkownikowi dodanie tych danych do istniejącego kontaktu.
kontakt,
Uwaga: nie musisz wysyłać wartości nazwy w dodatkach tej intencji, ponieważ użytkownik zawsze wybiera istniejącą nazwę lub dodaje nową. Co więcej, jeśli wyślesz nazwę, a użytkownik zdecyduje się na edycję, aplikacja do kontaktów wyświetli wysłaną przez Ciebie nazwę, zastępując poprzednią wartość. Jeśli użytkownik tego nie zauważy i zapisze zmiany, stara wartość zostanie utracona. |
Aplikacja do obsługi kontaktów na urządzeniu nie pozwala na usunięcie kontaktu bazowego ani żadnych jego danych za pomocą intencji. Aby usunąć kontakt bazowy, użyj ContentResolver.delete() lub ContentProviderOperation.newDelete().
Poniższy fragment kodu pokazuje, jak utworzyć i wysłać intencję, która wstawia nowy kontakt bazowy i dane:
Kotlin
// Gets values from the UI val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val company = companyName.text.toString() val jobtitle = jobTitle.text.toString() /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row val contactData = arrayListOf<ContentValues>() /* * Defines the raw contact row */ // Sets up the row as a ContentValues object val rawContactRow = ContentValues().apply { // Adds the account type and name to the row put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type) put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name) } // Adds the row to the array contactData.add(rawContactRow) /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object val phoneRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Adds the phone number and its type to the row put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) } // Adds the row to the array contactData.add(phoneRow) /* * Sets up the email data row */ // Sets up the row as a ContentValues object val emailRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Adds the email address and its type to the row put(ContactsContract.CommonDataKinds.Email.ADDRESS, email) } // Adds the row to the array contactData.add(emailRow) // Creates a new intent for sending to the device's contacts application val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply { // Sets the MIME type to the one expected by the insertion activity type = ContactsContract.RawContacts.CONTENT_TYPE // Sets the new contact name putExtra(ContactsContract.Intents.Insert.NAME, name) // Sets the new company and job title putExtra(ContactsContract.Intents.Insert.COMPANY, company) putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle) /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData) } // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent)
Java
// Gets values from the UI String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); String company = companyName.getText().toString(); String jobtitle = jobTitle.getText().toString(); // Creates a new intent for sending to the device's contacts application Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); // Sets the MIME type to the one expected by the insertion activity insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); // Sets the new contact name insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); // Sets the new company and job title insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); /* * Defines the raw contact row */ // Sets up the row as a ContentValues object ContentValues rawContactRow = new ContentValues(); // Adds the account type and name to the row rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()); rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Adds the row to the array contactData.add(rawContactRow); /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object ContentValues phoneRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) phoneRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ); // Adds the phone number and its type to the row phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); // Adds the row to the array contactData.add(phoneRow); /* * Sets up the email data row */ // Sets up the row as a ContentValues object ContentValues emailRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) emailRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ); // Adds the email address and its type to the row emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); // Adds the row to the array contactData.add(emailRow); /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent);
integralność danych,
Repozytorium kontaktów zawiera ważne i poufne dane, które powinny być prawidłowe i aktualne, dlatego dostawca kontaktów ma ściśle określone reguły dotyczące integralności danych. Twoim obowiązkiem jest przestrzeganie tych zasad podczas modyfikowania danych kontaktów. Oto najważniejsze zasady:
-
Zawsze dodawaj wiersz
ContactsContract.CommonDataKinds.StructuredNamedo każdego dodawanego wierszaContactsContract.RawContacts. - Wiersz
ContactsContract.RawContactsbez wierszaContactsContract.CommonDataKinds.StructuredNamew tabeliContactsContract.Datamoże powodować problemy podczas agregacji. -
Zawsze łącz nowe wiersze
ContactsContract.Dataz ich nadrzędnym wierszemContactsContract.RawContacts. -
Wiersz
ContactsContract.Data, który nie jest połączony zContactsContract.RawContacts, nie będzie widoczny w aplikacji do obsługi kontaktów na urządzeniu i może powodować problemy z adapterami synchronizacji. - Zmieniaj dane tylko w przypadku kontaktów bazowych, które należą do Ciebie.
- Pamiętaj, że dostawca kontaktów zwykle zarządza danymi z kilku różnych rodzajów kont i usług online. Musisz zadbać o to, aby aplikacja modyfikowała lub usuwała tylko dane z wierszy, które należą do Ciebie, oraz aby wstawiała tylko dane z rodzajem i nazwą konta, które kontrolujesz.
-
Zawsze używaj stałych zdefiniowanych w klasie
ContactsContracti jej podklasach w przypadku urzędów, identyfikatorów URI treści, ścieżek URI, nazw kolumn, typów MIME i wartościTYPE. - Używanie tych stałych pomaga uniknąć błędów. Jeśli któraś ze stałych jest wycofana, otrzymasz też powiadomienie z ostrzeżeniami kompilatora.
Wiersze danych niestandardowych
Tworząc i używając własnych niestandardowych typów MIME, możesz wstawiać, edytować, usuwać i pobierać własne wiersze danych w tabeli ContactsContract.Data. Wiersze mogą używać tylko kolumny zdefiniowanej w ContactsContract.DataColumns, ale możesz zmapować własne nazwy kolumn dla poszczególnych typów na domyślne nazwy kolumn. W aplikacji do obsługi kontaktów na urządzeniu dane z wierszy są wyświetlane, ale nie można ich edytować ani usuwać, a użytkownicy nie mogą dodawać dodatkowych danych. Aby umożliwić użytkownikom modyfikowanie niestandardowych wierszy danych, musisz udostępnić aktywność edytora we własnej aplikacji.
Aby wyświetlić dane niestandardowe, podaj plik contacts.xml zawierający element <ContactsAccountType> i co najmniej 1 element podrzędny <ContactsDataKind>. Szczegółowo opisujemy to w sekcji <ContactsDataKind> element.
Więcej informacji o niestandardowych typach MIME znajdziesz w przewodniku Tworzenie mechanizmu dostarczania treści.
Adaptery synchronizacji dostawcy kontaktów
Dostawca kontaktów został zaprojektowany specjalnie do obsługi synchronizacji danych kontaktów między urządzeniem a usługą online. Dzięki temu użytkownicy mogą pobierać istniejące dane na nowe urządzenie i przesyłać je na nowe konto. Synchronizacja zapewnia też użytkownikom dostęp do najnowszych danych, niezależnie od źródła dodanych informacji i zmian. Inną zaletą synchronizacji jest to, że dane kontaktów są dostępne nawet wtedy, gdy urządzenie nie jest połączone z siecią.
Chociaż synchronizację można wdrożyć na różne sposoby, system Android udostępnia platformę synchronizacji wtyczek, która automatyzuje te zadania:
- Sprawdzam dostępność sieci.
- planowanie i wykonywanie synchronizacji na podstawie preferencji użytkownika;
- ponowne uruchamianie zatrzymanych synchronizacji;
Aby korzystać z tej platformy, musisz dostarczyć wtyczkę adaptera synchronizacji. Każdy adapter synchronizacji jest unikalny dla usługi i mechanizmu dostarczania treści, ale może obsługiwać wiele nazw kont w tej samej usłudze. Platforma umożliwia też korzystanie z wielu adapterów synchronizacji w przypadku tej samej usługi i tego samego dostawcy.
Klasy i pliki adaptera synchronizacji
Adapter synchronizacji implementuje się jako podklasę AbstractThreadedSyncAdapter i instaluje jako część aplikacji na Androida. System dowiaduje się o adapterze synchronizacji z elementów w manifeście aplikacji i ze specjalnego pliku XML wskazanego przez manifest. Plik XML definiuje rodzaj konta usługi online i urząd dostawcy treści, które razem jednoznacznie identyfikują adapter. Adapter synchronizacji nie zostanie aktywowany, dopóki użytkownik nie doda konta odpowiedniego rodzaju i nie włączy synchronizacji dla mechanizmu dostarczania treści, z którym adapter synchronizacji ma się synchronizować. W tym momencie system zaczyna zarządzać adapterem, wywołując go w razie potrzeby, aby zsynchronizować mechanizm dostarczania treści z serwerem.
Uwaga: użycie rodzaju konta jako części identyfikatora adaptera synchronizacji umożliwia systemowi wykrywanie i grupowanie adapterów synchronizacji, które uzyskują dostęp do różnych usług z tej samej organizacji. Na przykład adaptery synchronizacji usług online Google mają ten sam rodzaj konta: com.google. Gdy użytkownicy dodają konto Google do swoich urządzeń, wszystkie zainstalowane adaptery synchronizacji usług Google są wyświetlane razem. Każdy z nich synchronizuje się z innym mechanizmem dostarczania treści na urządzeniu.
Większość usług wymaga od użytkowników potwierdzenia tożsamości przed uzyskaniem dostępu do danych, dlatego system Android oferuje platformę uwierzytelniania, która jest podobna do platformy adaptera synchronizacji i często jest używana w połączeniu z nią. Platforma uwierzytelniania używa modułów uwierzytelniających wtyczki, które są podklasami AbstractAccountAuthenticator. Uwierzytelniacz weryfikuje tożsamość użytkownika w tych krokach:
- zbiera nazwę użytkownika, hasło lub podobne informacje (dane logowania użytkownika);
- wysyła dane logowania do usługi,
- Sprawdza odpowiedź usługi.
Jeśli usługa zaakceptuje dane logowania, mechanizm uwierzytelniający może je zapisać do późniejszego użycia. Dzięki platformie uwierzytelniania wtyczek AccountManager może zapewniać dostęp do dowolnych tokenów uwierzytelniania, które obsługuje i udostępnia uwierzytelniacz, takich jak tokeny uwierzytelniania OAuth2.
Chociaż uwierzytelnianie nie jest wymagane, większość usług kontaktów z niego korzysta. Nie musisz jednak używać platformy uwierzytelniania Androida do uwierzytelniania.
Implementacja adaptera synchronizacji
Aby zaimplementować adapter synchronizacji dla dostawcy kontaktów, zacznij od utworzenia aplikacji na Androida, która zawiera:
-
Komponent
Service, który odpowiada na żądania systemu dotyczące powiązania z adapterem synchronizacji. -
Gdy system chce przeprowadzić synchronizację, wywołuje metodę
onBind()usługi, aby uzyskać obiektIBinderdla adaptera synchronizacji. Dzięki temu system może wykonywać wywołania metod adaptera między procesami. -
Właściwy adapter synchronizacji, zaimplementowany jako konkretna podklasa
AbstractThreadedSyncAdapter. -
Ta klasa odpowiada za pobieranie danych z serwera, przesyłanie danych z urządzenia i rozwiązywanie konfliktów. Główne zadanie adaptera jest wykonywane w metodzie
onPerformSync(). Ta klasa musi być utworzona jako singleton. -
Podklasa elementu
Application. -
Ta klasa działa jako fabryka dla obiektu singleton adaptera synchronizacji. Użyj metody
onCreate(), aby utworzyć instancję adaptera synchronizacji, i podaj statyczną metodę „getter”, aby zwrócić singleton do metodyonBind()usługi adaptera synchronizacji. -
Opcjonalnie: komponent
Service, który odpowiada na żądania uwierzytelnienia użytkownika wysyłane przez system. -
AccountManageruruchamia tę usługę, aby rozpocząć proces uwierzytelniania. MetodaonCreate()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ćIBinderdla uwierzytelniacza. Dzięki temu system może wykonywać wywołania metod uwierzytelniającego między procesami. -
Opcjonalnie: konkretna podklasa
AbstractAccountAuthenticator, która obsługuje żądania uwierzytelniania. - Ta klasa udostępnia metody wywoływane przez
AccountManagerw celu uwierzytelnienia danych logowania użytkownika na serwerze. Szczegóły procesu uwierzytelniania różnią się znacznie w zależności od używanej technologii serwera. Więcej informacji o uwierzytelnianiu znajdziesz w dokumentacji oprogramowania serwerowego. - Pliki XML, które definiują adapter synchronizacji i uwierzytelniacz w systemie.
-
Opisane wcześniej komponenty usługi adaptera synchronizacji i uwierzytelniania są zdefiniowane w elementach
<service>w pliku manifestu aplikacji. Te elementy zawierają elementy podrzędne<meta-data>, które dostarczają systemowi określone dane:-
Element
<meta-data>usługi adaptera synchronizacji wskazuje plik XMLres/xml/syncadapter.xml. Z kolei ten plik określa identyfikator URI usługi internetowej, która będzie synchronizowana z dostawcą kontaktów, oraz rodzaj konta usługi internetowej. -
Opcjonalnie: element
<meta-data>modułu uwierzytelniającego wskazuje plik XMLres/xml/authenticator.xml. Z kolei ten plik określa rodzaj konta obsługiwany przez ten moduł uwierzytelniający, a także zasoby interfejsu, które pojawiają się podczas procesu uwierzytelniania. Rodzaj konta określony w tym elemencie musi być taki sam jak rodzaj konta określony dla adaptera synchronizacji.
-
Element
Dane strumienia społecznościowego
Tabele android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos zarządzają danymi przychodzącymi z sieci społecznościowych. Możesz napisać adapter synchronizacji, który dodaje dane strumienia z Twojej sieci do tych tabel, lub odczytywać dane strumienia z tych tabel i wyświetlać je we własnej aplikacji. Możesz też wykonywać obie te czynności. Dzięki tym funkcjom Twoje usługi i aplikacje społecznościowe mogą zostać zintegrowane z funkcjami społecznościowymi Androida.
Tekst w sieciach społecznościowych
Elementy strumienia są zawsze powiązane z kontaktem bazowym. android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID prowadzi do wartości _ID kontaktu bazowego. Rodzaj konta i nazwa konta kontaktu bazowego są również przechowywane w wierszu elementu strumienia.
Przechowuj dane ze strumienia w tych kolumnach:
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
- Wymagany. Rodzaj konta użytkownika dla kontaktu bazowego powiązanego z tym elementem strumienia. Pamiętaj, aby ustawić tę wartość podczas wstawiania elementu strumienia.
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
- Wymagany. Nazwa konta użytkownika dla kontaktu bazowego powiązanego z tym elementem strumienia. Pamiętaj, aby ustawić tę wartość podczas wstawiania elementu strumienia.
- Kolumny identyfikatorów
-
Wymagany. Podczas wstawiania elementu strumienia musisz wstawić te kolumny identyfikatorów:
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: wartość android.provider.BaseColumns#_ID kontaktu, z którym jest powiązany ten element strumienia.
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: wartość android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY kontaktu, z którym jest powiązany ten element strumienia.
- android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: wartość android.provider.BaseColumns#_ID kontaktu bazowego, z którym jest powiązany ten element strumienia.
- android.provider.ContactsContract.StreamItemsColumns#COMMENTS
- Opcjonalnie. Przechowuje informacje podsumowujące, które możesz wyświetlić na początku elementu strumienia.
- android.provider.ContactsContract.StreamItemsColumns#TEXT
- Tekst elementu strumienia, czyli treść opublikowana przez źródło elementu lub opis działania, które wygenerowało element strumienia. Ta kolumna może zawierać dowolne formatowanie i obrazy zasobów, które mogą być renderowane przez
fromHtml(). Dostawca może obciąć lub skrócić długą treść, ale będzie starał się unikać uszkodzenia tagów. - android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
- Ciąg tekstowy zawierający czas wstawienia lub zaktualizowania elementu strumienia w formie milisekund od początku epoki. Aplikacje, które wstawiają lub aktualizują elementy strumienia, są odpowiedzialne za utrzymanie tej kolumny. Nie jest ona automatycznie utrzymywana przez dostawcę kontaktów.
Aby wyświetlić informacje umożliwiające identyfikację elementów strumienia, użyj android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL i android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE do utworzenia linków do zasobów w aplikacji.
Tabela android.provider.ContactsContract.StreamItems zawiera też kolumny android.provider.ContactsContract.StreamItemsColumns#SYNC1 do android.provider.ContactsContract.StreamItemsColumns#SYNC4, które są przeznaczone wyłącznie do użytku przez adaptery synchronizacji.
Zdjęcia ze strumienia społecznościowego
Tabela android.provider.ContactsContract.StreamItemPhotos przechowuje zdjęcia powiązane z elementem strumienia. Kolumna android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID tabeli zawiera linki do wartości w kolumnie _ID tabeli android.provider.ContactsContract.StreamItems. Odwołania do zdjęć są przechowywane w tabeli w tych kolumnach:
- kolumna android.provider.ContactsContract.StreamItemPhotos#PHOTO (BLOB).
- Reprezentacja binarna zdjęcia, którego rozmiar został zmieniony przez dostawcę na potrzeby przechowywania i wyświetlania. Ta kolumna jest dostępna ze względu na zgodność wsteczną z poprzednimi wersjami dostawcy kontaktów, które używały jej do przechowywania zdjęć. W obecnej wersji nie należy jednak używać tej kolumny do przechowywania zdjęć. Zamiast tego użyj android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID lub android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (oba są opisane w poniższych punktach), aby zapisać zdjęcia w pliku. Ta kolumna zawiera teraz miniaturę zdjęcia, które można odczytać.
- android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
-
Identyfikator numeryczny zdjęcia kontaktu bazowego. Dołącz tę wartość do stałej
DisplayPhoto.CONTENT_URI, aby uzyskać identyfikator URI treści wskazujący na pojedynczy plik zdjęcia, a następnie wywołajopenAssetFileDescriptor(), aby uzyskać uchwyt do pliku zdjęcia. - android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
-
Identyfikator URI treści wskazujący bezpośrednio na plik zdjęcia reprezentowanego przez ten wiersz.
Wywołaj
openAssetFileDescriptor()za pomocą tego identyfikatora URI, aby uzyskać uchwyt do pliku zdjęcia.
Korzystanie z tabel strumienia społecznościowego
Te tabele działają tak samo jak inne główne tabele w dostawcy kontaktów, z tymi wyjątkami:
- Te tabele wymagają dodatkowych uprawnień dostępu. Aby odczytywać z nich dane, aplikacja musi mieć uprawnienie android.Manifest.permission#READ_SOCIAL_STREAM. Aby je zmodyfikować, aplikacja musi mieć uprawnienie android.Manifest.permission#WRITE_SOCIAL_STREAM.
-
W przypadku tabeli android.provider.ContactsContract.StreamItems liczba wierszy przechowywanych dla każdego kontaktu bazowego jest ograniczona. Gdy zostanie osiągnięty, dostawca kontaktów zrobi miejsce na nowe wiersze elementów strumienia, automatycznie usuwając wiersze z najstarszymi wartościami android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP. Aby uzyskać limit, wyślij zapytanie do identyfikatora URI treści android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Możesz pozostawić wszystkie argumenty inne niż identyfikator URI treści ustawione na
null. Zapytanie zwraca kursor zawierający pojedynczy wiersz z pojedynczą kolumną android.provider.ContactsContract.StreamItems#MAX_ITEMS.
Klasa android.provider.ContactsContract.StreamItems.StreamItemPhotos definiuje podtabelę android.provider.ContactsContract.StreamItemPhotos zawierającą wiersze zdjęć dla pojedynczego elementu strumienia.
Interakcje ze strumieniem społecznościowym
Dane strumienia społecznościowego zarządzane przez dostawcę kontaktów w połączeniu z aplikacją do obsługi kontaktów na urządzeniu umożliwiają skuteczne połączenie systemu sieci społecznościowej z istniejącymi kontaktami. Dostępne są te funkcje:
- Synchronizując usługę sieci społecznościowej z dostawcą kontaktów za pomocą adaptera synchronizacji, możesz pobierać ostatnią aktywność kontaktów użytkownika i zapisywać ją w tabelach android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos do późniejszego wykorzystania.
- Oprócz regularnej synchronizacji możesz aktywować adapter synchronizacji, aby pobierał dodatkowe dane, gdy użytkownik wybierze kontakt do wyświetlenia. Dzięki temu adapter synchronizacji może pobierać zdjęcia w wysokiej rozdzielczości i najnowsze elementy strumienia dla kontaktu.
- Rejestrując powiadomienie w aplikacji do obsługi kontaktów na urządzeniu i u dostawcy kontaktów, możesz otrzymać intencję, gdy kontakt zostanie wyświetlony, i w tym momencie zaktualizować stan kontaktu z poziomu usługi. To podejście może być szybsze i zużywać mniej przepustowości niż pełna synchronizacja za pomocą adaptera synchronizacji.
- Użytkownicy mogą dodać kontakt do Twojej usługi sieci społecznościowej, gdy wyświetlają go w aplikacji do obsługi kontaktów na urządzeniu. Możesz to zrobić za pomocą funkcji „Zaproś kontakt”, którą włączasz, łącząc aktywność dodającą istniejący kontakt do Twojej sieci z plikiem XML, który dostarcza aplikacji do obsługi kontaktów na urządzeniu i dostawcy kontaktów szczegółowe informacje o Twojej aplikacji.
Regularna synchronizacja elementów strumienia z dostawcą kontaktów przebiega tak samo jak inne synchronizacje. Więcej informacji o synchronizacji znajdziesz w sekcji Adaptery synchronizacji dostawcy kontaktów. Rejestrowanie powiadomień i zapraszanie kontaktów opisano w następnych 2 sekcjach.
Rejestrowanie obsługi widoków sieci społecznościowych
Aby zarejestrować adapter synchronizacji i otrzymywać powiadomienia, gdy użytkownik wyświetli kontakt zarządzany przez ten adapter:
-
Utwórz plik o nazwie
contacts.xmlw katalogures/xml/projektu. Jeśli masz już ten plik, możesz pominąć ten krok. -
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. -
Aby zarejestrować usługę, która jest powiadamiana, gdy użytkownik otwiera stronę z informacjami o kontakcie w aplikacji do obsługi kontaktów na urządzeniu, dodaj atrybut
viewContactNotifyService="serviceclass"do elementu, gdzieserviceclassto pełna nazwa klasy usługi, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu. W przypadku usługi powiadomień użyj klasy, która rozszerzaIntentService, aby umożliwić usłudze odbieranie intencji. Dane w przychodzącej intencji zawierają identyfikator URI treści kontaktu bazowego, który kliknął użytkownik. Z poziomu usługi powiadomień możesz powiązać i wywołać adapter synchronizacji, aby zaktualizować dane kontaktu bazowego.
Aby zarejestrować aktywność, która ma zostać wywołana, gdy użytkownik kliknie element strumienia lub zdjęcie (albo jedno i drugie):
-
Utwórz plik o nazwie
contacts.xmlw katalogures/xml/projektu. Jeśli masz już ten plik, możesz pominąć ten krok. -
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. -
Aby zarejestrować jedną ze swoich aktywności do obsługi kliknięcia przez użytkownika elementu strumienia w aplikacji do obsługi kontaktów na urządzeniu, dodaj atrybut
viewStreamItemActivity="activityclass"do elementu, gdzieactivityclassto pełna nazwa klasy aktywności, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu. -
Aby zarejestrować jedną ze swoich aktywności do obsługi kliknięcia przez użytkownika zdjęcia strumienia w aplikacji do obsługi kontaktów na urządzeniu, dodaj atrybut
viewStreamItemPhotoActivity="activityclass"do elementu, gdzieactivityclassto pełna nazwa klasy aktywności, która powinna otrzymać intencję z aplikacji do obsługi kontaktów na urządzeniu.
Element <ContactsAccountType> został szczegółowo opisany w sekcji Element <ContactsAccountType>.
Przychodząca intencja zawiera identyfikator URI treści elementu lub zdjęcia, które kliknął użytkownik. Aby mieć osobne aktywności dla elementów tekstowych i zdjęć, użyj obu atrybutów w tym samym pliku.
interakcja z Twoją usługą sieci społecznościowej;
Użytkownicy nie muszą opuszczać aplikacji do obsługi kontaktów na urządzeniu, aby zaprosić kontakt do Twojego serwisu społecznościowego. Zamiast tego możesz sprawić, że aplikacja do obsługi kontaktów na urządzeniu wyśle intencję zaproszenia kontaktu do jednej z Twoich aktywności. Aby to skonfigurować:
-
Utwórz plik o nazwie
contacts.xmlw katalogures/xml/projektu. Jeśli masz już ten plik, możesz pominąć ten krok. -
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. -
Dodaj te atrybuty:
inviteContactActivity="activityclass"-
inviteContactActionLabel="@string/invite_action_label"
activityclassto pełna i jednoznaczna nazwa klasy aktywności, która powinna otrzymać intencję. Wartośćinvite_action_labelto ciąg tekstowy wyświetlany w menu Dodaj połączenie w aplikacji do obsługi kontaktów na urządzeniu.
Uwaga: ContactsSource to wycofana nazwa tagu ContactsAccountType.
Dokumentacja pliku contacts.xml
Plik contacts.xml zawiera elementy XML, które kontrolują interakcję adaptera synchronizacji i aplikacji z aplikacją do obsługi kontaktów i dostawcą kontaktów. Te elementy opisujemy w sekcjach poniżej.
Element <ContactsAccountType>
Element <ContactsAccountType> kontroluje interakcję aplikacji z aplikacją do obsługi kontaktów. Składnia:
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android" inviteContactActivity="activity_name" inviteContactActionLabel="invite_command_text" viewContactNotifyService="view_notify_service" viewGroupActivity="group_view_activity" viewGroupActionLabel="group_action_text" viewStreamItemActivity="viewstream_activity_name" viewStreamItemPhotoActivity="viewphotostream_activity_name">
contained in:
res/xml/contacts.xml
może zawierać:
<ContactsDataKind>
Opis:
Deklaruje komponenty Androida i etykiety interfejsu, które umożliwiają użytkownikom zapraszanie kontaktów do sieci społecznościowej, powiadamianie użytkowników o aktualizacji strumienia sieci społecznościowej itp.
Zauważ, że prefiks atrybutu android: nie jest konieczny w przypadku atrybutów <ContactsAccountType>.
Atrybuty:
inviteContactActivity- W pełni kwalifikowana nazwa klasy aktywności w aplikacji, którą chcesz aktywować, gdy użytkownik wybierze Dodaj połączenie w aplikacji do obsługi kontaktów na urządzeniu.
inviteContactActionLabel-
Ciąg tekstowy wyświetlany dla aktywności określonej w
inviteContactActivityw menu Dodaj połączenie. Możesz na przykład użyć ciągu „Obserwuj w mojej sieci”. Jako etykiety możesz użyć identyfikatora zasobu tekstowego. viewContactNotifyService- Pełna i jednoznaczna nazwa klasy usługi w aplikacji, która powinna otrzymywać powiadomienia, gdy użytkownik wyświetla kontakt. To powiadomienie jest wysyłane przez aplikację do obsługi kontaktów na urządzeniu. Pozwala ono aplikacji odłożyć operacje wymagające dużej ilości danych do czasu, gdy będą potrzebne. Na przykład aplikacja może zareagować na to powiadomienie, odczytując i wyświetlając zdjęcie kontaktu w wysokiej rozdzielczości oraz najnowsze elementy z jego strumienia społecznościowego. Ta funkcja została szczegółowo opisana w sekcji Interakcje w strumieniu społecznościowym.
viewGroupActivity- Pełna i jednoznaczna nazwa klasy aktywności w aplikacji, która może wyświetlać informacje o grupie. Gdy użytkownik kliknie etykietę grupy w aplikacji do obsługi kontaktów na urządzeniu, wyświetli się interfejs tej aktywności.
viewGroupActionLabel- Etykieta wyświetlana przez aplikację do obsługi kontaktów w przypadku elementu sterującego interfejsu, który umożliwia użytkownikowi przeglądanie grup w Twojej aplikacji.
W przypadku tego atrybutu dozwolony jest identyfikator zasobu tekstowego.
viewStreamItemActivity- W pełni kwalifikowana nazwa klasy aktywności w aplikacji, którą aplikacja do obsługi kontaktów na urządzeniu uruchamia, gdy użytkownik kliknie element strumienia dla kontaktu bazowego.
viewStreamItemPhotoActivity- Pełna nazwa klasy aktywności w aplikacji, którą aplikacja do obsługi kontaktów na urządzeniu uruchamia, gdy użytkownik kliknie zdjęcie w elemencie strumienia dla kontaktu bazowego.
Element <ContactsDataKind>
Element <ContactsDataKind> kontroluje wyświetlanie niestandardowych wierszy danych aplikacji w interfejsie aplikacji do obsługi kontaktów. Składnia:
<ContactsDataKind android:mimeType="MIMEtype" android:icon="icon_resources" android:summaryColumn="column_name" android:detailColumn="column_name">
contained in:
<ContactsAccountType>
Opis:
Użyj tego elementu, aby aplikacja do obsługi kontaktów wyświetlała zawartość niestandardowego wiersza danych jako część szczegółów kontaktu bazowego. Każdy element podrzędny <ContactsDataKind> elementu <ContactsAccountType> reprezentuje typ niestandardowego wiersza danych, który adapter synchronizacji dodaje do tabeli ContactsContract.Data. Dodaj po jednym elemencie <ContactsDataKind> dla każdego niestandardowego typu MIME, którego używasz. Nie musisz dodawać tego elementu, jeśli masz niestandardowy wiersz danych, dla którego nie chcesz wyświetlać danych.
Atrybuty:
android:mimeType-
Niestandardowy typ MIME zdefiniowany dla jednego z niestandardowych typów wierszy danych w tabeli
ContactsContract.Data. Na przykład wartośćvnd.android.cursor.item/vnd.example.locationstatusmoże być niestandardowym typem MIME dla wiersza danych, który rejestruje ostatnią znaną lokalizację kontaktu. android:icon- Obiekt rysowalny Androida, który aplikacja do obsługi kontaktów wyświetla obok Twoich danych. Użyj tego, aby poinformować użytkownika, że dane pochodzą z Twojej usługi.
android:summaryColumn-
Nazwa kolumny dla pierwszej z 2 wartości pobranych z wiersza danych. Wartość jest wyświetlana jako pierwszy wiersz wpisu dla tego wiersza danych. Pierwszy wiersz ma służyć jako podsumowanie danych, ale jest to opcjonalne. Zobacz też
android:detailColumn. android:detailColumn-
Nazwa kolumny dla drugiej z 2 wartości pobranych z wiersza danych. Wartość jest wyświetlana w drugim wierszu wpisu dla tego wiersza danych. Zobacz też
android:summaryColumn.
Dodatkowe funkcje dostawcy kontaktów
Oprócz głównych funkcji opisanych w poprzednich sekcjach dostawca kontaktów oferuje te przydatne funkcje do pracy z danymi kontaktów:
- Grupy kontaktów
- Funkcje edycji zdjęć
Grupy kontaktów
Dostawca kontaktów może opcjonalnie oznaczać kolekcje powiązanych kontaktów danymi grupy. Jeśli serwer powiązany z kontem użytkownika chce zachować grupy, adapter synchronizacji dla danego rodzaju konta powinien przesyłać dane grup między dostawcą kontaktów a serwerem. Gdy użytkownicy dodają nowy kontakt do serwera, a następnie umieszczają go w nowej grupie, adapter synchronizacji musi dodać nową grupę do tabeli ContactsContract.Groups. Grupy, do których należy kontakt bazowy, są przechowywane w tabeli ContactsContract.Data z użyciem typu MIME ContactsContract.CommonDataKinds.GroupMembership.
Jeśli projektujesz adapter synchronizacji, który będzie dodawać dane kontaktów bazowych z serwera do dostawcy kontaktów, i nie używasz grup, musisz poinformować dostawcę, aby udostępnił Twoje dane. W kodzie, który jest wykonywany, gdy użytkownik dodaje konto do urządzenia, zaktualizuj wiersz ContactsContract.Settings dodawany przez dostawcę kontaktów dla tego konta. W tym wierszu ustaw wartość w kolumnie Settings.UNGROUPED_VISIBLE na 1. Dzięki temu dostawca kontaktów zawsze będzie udostępniać dane kontaktów, nawet jeśli nie używasz grup.
Zdjęcia kontaktu
Tabela ContactsContract.Data przechowuje zdjęcia jako wiersze z typem MIME Photo.CONTENT_ITEM_TYPE. Kolumna CONTACT_ID wiersza jest połączona z kolumną _ID kontaktu bazowego, do którego należy.
Klasa ContactsContract.Contacts.Photo definiuje podtabelę ContactsContract.Contacts zawierającą informacje o głównym zdjęciu kontaktu, które jest głównym zdjęciem głównego kontaktu bazowego. Podobnie klasa ContactsContract.RawContacts.DisplayPhoto definiuje podtabelę klasy ContactsContract.RawContacts zawierającą informacje o głównym zdjęciu kontaktu bazowego.
Dokumentacja referencyjna dotycząca ContactsContract.Contacts.Photo i ContactsContract.RawContacts.DisplayPhoto zawiera przykłady pobierania informacji o zdjęciach. Nie ma klasy ułatwiającej pobieranie głównej miniatury kontaktu bazowego, ale możesz wysłać zapytanie do tabeli ContactsContract.Data, wybierając kolumny _ID, Photo.CONTENT_ITEM_TYPE i IS_PRIMARY kontaktu bazowego, aby znaleźć wiersz jego głównego zdjęcia.
Dane ze strumienia społecznościowego dotyczące danej osoby mogą też zawierać zdjęcia. Są one przechowywane w tabeli android.provider.ContactsContract.StreamItemPhotos, która została szczegółowo opisana w sekcji Zdjęcia w strumieniu społecznościowym.