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 Kontakty na urządzeniu. Możesz też uzyskać dostęp do tych danych we własnej aplikacji i przesyłać dane między urządzeniem a usługami online. Usługodawca 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. Dlatego interfejs API dostawcy zawiera obszerny zestaw klas i interfejsów kontraktów, które ułatwiają zarówno pobieranie, jak i modyfikowanie danych.
W tym przewodniku znajdziesz informacje o tych kwestiach:
- Podstawowa struktura dostawcy.
- Jak pobrać dane od dostawcy.
- Jak modyfikować dane u dostawcy.
- Jak napisać adapter synchronizacji do synchronizowania danych z serwera z dostawcą kontaktów.
W tym przewodniku przyjęto założenie, że znasz podstawy dostawców treści na Androidzie. Więcej informacji o dostawcach treści na Androida znajdziesz w przewodniku Podstawy dostawców treści.
Organizacja dostawcy kontaktów
Dostawca kontaktów to komponent dostawcy treści Androida. Przechowuje 3 rodzaje 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 klas kontraktów. Klasy definiują stałe dla identyfikatorów URI treści, nazw kolumn i wartości kolumn używanych przez tabele:
-
ContactsContract.Contacts
tabela - Wiersze reprezentujące różne osoby na podstawie agregacji wierszy z danymi kontaktowymi.
-
ContactsContract.RawContacts
tabela - Wiersze zawierające podsumowanie danych osoby, które są specyficzne dla konta i typu użytkownika.
-
ContactsContract.Data
tabela - Wiersze zawierające szczegóły dotyczące kontaktu podstawowego, takie jak adresy e-mail lub numery telefonów.
Pozostałe tabele reprezentowane przez klasy kontraktów w ContactsContract
to tabele pomocnicze, których dostawca kontaktów używa do zarządzania swoimi operacjami lub obsługi określonych funkcji w aplikacjach do obsługi kontaktów lub połączeń telefonicznych na urządzeniu.
Kontakty bazowe
Kontakt podstawowy reprezentuje dane osoby pochodzące z jednego typu konta i nazwy konta. Ponieważ dostawca kontaktów umożliwia korzystanie z więcej niż jednej usługi online jako źródła danych o osobie, pozwala na przechowywanie wielu surowych kontaktów dotyczących tej samej osoby. Wiele kontaktów podstawowych umożliwia też użytkownikowi łączenie danych osoby z więcej niż jednego konta tego samego typu.
Większość danych dotyczących kontaktu podstawowego nie jest przechowywana w tabeli ContactsContract.RawContacts
. Zamiast tego jest on przechowywany w co najmniej 1 wierszu w tabeli ContactsContract.Data
. Każdy wiersz danych ma kolumnę Data.RAW_CONTACT_ID
, która zawiera wartość RawContacts._ID
wiersza nadrzędnego ContactsContract.RawContacts
.
Ważne kolumny kontaktu
Ważne kolumny w ContactsContract.RawContacts
tabeli są wymienione w tabeli 1. Zapoznaj się z uwagami pod tabelą:
Tabela 1. Ważne kolumny kontaktu pierwotnego.
Nazwa kolumny | Użyj | Uwagi |
---|---|---|
ACCOUNT_NAME
|
Nazwa konta dla typu konta, które jest źródłem tego surowego kontaktu.
Na przykład nazwa konta Google to jeden 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 typu konta. Nie musi to być adres e-mail. |
ACCOUNT_TYPE
|
Typ konta, z którego pochodzi ten surowy kontakt. Na przykład typ konta Google to com.google . Zawsze określaj typ konta za pomocą identyfikatora domeny, która należy do Ciebie lub którą kontrolujesz. Dzięki temu Twój rodzaj konta będzie unikalny.
|
Typ konta, który oferuje dane kontaktów, ma zwykle powiązany adapter synchronizacji, który synchronizuje się z dostawcą kontaktów. |
DELETED
|
Flaga „usunięto” dla kontaktu pierwotnego. | Ta flaga umożliwia dostawcy kontaktów przechowywanie wiersza wewnętrznie, dopóki adaptery synchronizacji nie będą mogły usunąć go ze swoich serwerów, a następnie z repozytorium. |
Uwagi
Oto ważne informacje o tabeli ContactsContract.RawContacts
:
-
Nazwa kontaktu podstawowego nie jest przechowywana w jego wierszu w tabeli
ContactsContract.RawContacts
. Zamiast tego jest przechowywana w tabeliContactsContract.Data
w wierszuContactsContract.CommonDataKinds.StructuredName
. Kontakt podstawowy ma tylko 1 wiersz tego typu w tabeliContactsContract.Data
. -
Ostrzeżenie: aby użyć danych własnego konta w wierszu kontaktu w formacie surowym, musisz najpierw zarejestrować je w
AccountManager
. Aby to zrobić, poproś użytkowników o dodanie typu konta i nazwy konta do listy kont. Jeśli tego nie zrobisz, dostawca kontaktów automatycznie usunie wiersz z kontaktem pierwotnym.Jeśli na przykład chcesz, aby aplikacja przechowywała dane kontaktów na potrzeby usługi internetowej w domenie
com.example.dataservice
, a konto użytkownika w tej 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 z danymi kontaktów. Możesz wyjaśnić to wymaganie użytkownikowi w dokumentacji lub poprosić go o dodanie typu i nazwy albo obu tych elementów. Rodzaje i nazwy kont opisujemy bardziej szczegółowo w następnej sekcji.
Źródła danych o kontaktach
Aby zrozumieć, jak działają kontakty podstawowe, weź pod uwagę użytkownika „Emily Dickinson”, który ma na swoim 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ł synchronizację kontaktów na wszystkich 3 kontach w ustawieniach Kont.
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 „Tomasza Higginsona”, co automatycznie dodaje go do kontaktów. Obserwuje też na Twitterze użytkownika „colonel_tom” (identyfikator Twittera Thomasa Higginsona).
W wyniku tego działania dostawca kontaktów tworzy 3 kontakty pierwotne:
-
Kontakt bazowy „Thomas Higginson” powiązany z
emily.dickinson@gmail.com
. Typ konta użytkownika to Google. -
Drugi kontakt w formacie surowym dla „Thomas Higginson” powiązany z
emilyd@gmail.com
. Typ konta użytkownika to też Google. Istnieje drugi kontakt w formacie RAW, mimo że nazwa jest identyczna z poprzednią, ponieważ osoba została dodana na innym koncie użytkownika. - Trzeci kontakt surowy dla „Thomas Higginson” powiązany z „belle_of_amherst”. Typ konta użytkownika to Twitter.
Dane
Jak wspomnieliśmy wcześniej, dane dotyczące kontaktu pierwotnego są przechowywane w wierszu ContactsContract.Data
, który jest połączony z wartością _ID
kontaktu pierwotnego. Dzięki temu jeden kontakt może mieć wiele wystąpień tego samego typu danych, np. adresów e-mail lub numerów telefonów. Jeśli na przykład kontakt „Tomasz Higginson” w emilyd@gmail.com
(wiersz kontaktu w formie surowej dla Tomasza Higginson 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 zapisuje 2 wiersze adresów e-mail i łączy je z kontaktem w formie surowej.
Zwróć uwagę, że w tej tabeli są przechowywane różne typy danych. Wiersze z nazwą wyświetlaną, numerem telefonu, adresem e-mail, adresem pocztowym, zdjęciem i szczegółami witryny znajdują się w tabeli ContactsContract.Data
. Aby ułatwić zarządzanie tymi danymi, tabela ContactsContract.Data
zawiera kolumny z nazwami opisowymi i inne z nazwami ogólnymi. Zawartość kolumny z nazwą opisową ma to 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
Przykłady opisowych nazw kolumn:
-
RAW_CONTACT_ID
- Wartość kolumny
_ID
w przypadku tego kontaktu. -
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, 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 pierwotnego, kolumna
IS_PRIMARY
oznacza wiersz danych, który zawiera podstawowe dane dla danego typu. Jeśli na przykład użytkownik przytrzyma numer telefonu kontaktu i wybierze Ustaw domyślny, w wierszuContactsContract.Data
zawierającym ten numer w kolumnieIS_PRIMARY
zostanie ustawiona wartość różna od zera.
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 nazwy kolumn ogólnych zawsze działają niezależnie od typu danych, które zawiera wiersz.
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 e-mail ta kolumna zawiera rzeczywisty adres e-mail.
Zgodnie z konwencją kolumna DATA15
jest zarezerwowana do przechowywania danych typu Binary Large Object (BLOB), takich jak miniatury zdjęć.
Nazwy kolumn specyficzne dla typu
Aby ułatwić pracę z kolumnami dla określonego typu wiersza, dostawca kontaktów udostępnia też stałe nazwy kolumn specyficzne dla danego typu, zdefiniowane w podklasach ContactsContract.CommonDataKinds
. Stałe po prostu 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 specyficzne dla typu 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.
Ostrzeżenie: nie dodawaj własnych danych niestandardowych do tabeli ContactsContract.Data
za pomocą wiersza, który ma jeden z wstępnie zdefiniowanych typów MIME dostawcy. W takim przypadku 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 w przypadku wiersza używasz własnego typu MIME, możesz zdefiniować własne nazwy kolumn specyficzne dla danego typu i używać kolumn w dowolny sposób.
Ilustracja 2 pokazuje, jak kolumny opisowe i kolumny danych pojawiają się w ContactsContract.Data
wierszu oraz jak nazwy kolumn specyficzne dla typu „nakładają się” na ogólne nazwy kolumn.

Rysunek 2. Nazwy kolumn specyficzne dla typu i nazwy kolumn ogólne.
Klasy nazw kolumn specyficzne dla typu
W tabeli 2 znajdziesz najczęściej używane klasy nazw kolumn specyficzne dla danego typu:
Tabela 2. Klasy nazw kolumn specyficzne dla typu
Klasa mapowania | Typ danych | Uwagi |
---|---|---|
ContactsContract.CommonDataKinds.StructuredName |
Dane nazwy surowego kontaktu powiązanego z tym wierszem danych. | Surowy kontakt ma tylko jeden z tych wierszy. |
ContactsContract.CommonDataKinds.Photo |
Główne zdjęcie kontaktu nieprzetworzonego powiązanego z tym wierszem danych. | Surowy kontakt ma tylko jeden z tych wierszy. |
ContactsContract.CommonDataKinds.Email |
Adres e-mail kontaktu podstawowego powiązanego z tym wierszem danych. | Jeden kontakt może mieć wiele adresów e-mail. |
ContactsContract.CommonDataKinds.StructuredPostal |
Adres pocztowy kontaktu podstawowego powiązanego z tym wierszem danych. | Jeden kontakt może mieć wiele adresów pocztowych. |
ContactsContract.CommonDataKinds.GroupMembership |
Identyfikator, który łączy kontakt z jedną z grup w usłudze Kontaktów. | Grupy są opcjonalną funkcją typu konta i nazwy konta. Szczegółowe informacje znajdziesz w sekcji Grupy kontaktów. |
kontakty,
Dostawca kontaktów łączy wiersze surowych 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 o danej osobie. Dostawca kontaktów zarządza tworzeniem nowych wierszy kontaktów i agregowaniem surowych kontaktów z istniejącym wierszem kontaktu. 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ą znaku insert()
, otrzymasz wyjątek UnsupportedOperationException
. Jeśli spróbujesz zaktualizować kolumnę, która jest oznaczona jako „tylko do odczytu”, aktualizacja zostanie zignorowana.
Dostawca kontaktów tworzy nowy kontakt w odpowiedzi na dodanie nowego kontaktu podstawowego, który nie pasuje do żadnego z dotychczasowych kontaktów. Dostawca robi to również wtedy, gdy dane istniejącego kontaktu podstawowego ulegną zmianie w taki sposób, że nie będą już pasować do kontaktu, do którego był wcześniej dołączony. Jeśli aplikacja lub adapter synchronizacji utworzy nowy kontakt podstawowy, który pasuje do istniejącego kontaktu, nowy kontakt podstawowy zostanie połączony z tym kontaktem.
Dostawca kontaktów łączy wiersz kontaktu z wierszami kontaktu pierwotnego za pomocą kolumny _ID
w tabeli Contacts
. Kolumna CONTACT_ID
w tabeli kontaktów surowychContactsContract.RawContacts
zawiera _ID
wartości wiersza kontaktów powiązanego z każdym wierszem kontaktów surowych.
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żywać 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
.
Na rysunku 3 pokazano, jak 3 główne tabele są ze sobą powiązane.

Rysunek 3. Relacje między tabelami Kontakty, Surowe kontakty i Szczegóły.
Ostrzeżenie: jeśli opublikujesz aplikację w Sklepie Google Play lub jeśli aplikacja jest na urządzeniu z Androidem 10 (API na poziomie 29) lub nowszym, pamiętaj, że ograniczony zestaw pól danych i metod kontaktów jest przestarzały.
W podanych warunkach system okresowo usuwa 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 powyższych pól danych również są przestarzałe:
Ponadto 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 należą one do 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 Email, Phone i Callable)
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 realizować określone przypadki użycia, korzystając z prywatnych dostawców treści lub innych danych przechowywanych w aplikacji bądź 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 na urządzeniu z Androidem 4.1 (poziom interfejsu API 16) lub nowszym to polecenie ADB:
adb shell content delete \ --uri content://com.android.contacts/contacts/delete_usage
Dane z adapterów synchronizacji
Użytkownicy wprowadzają dane kontaktów bezpośrednio na urządzeniu, ale dane są też przesyłane 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
do zarządzania danymi.
W Androidzie usługa internetowa, z którą współpracuje adapter synchronizacji, jest identyfikowana przez typ konta. Każdy adapter synchronizacji działa z jednym typem konta, ale może obsługiwać wiele nazw kont tego typu. Rodzaje kont i ich nazwy zostały krótko opisane w sekcji Źródła nieprzetworzonych danych kontaktów. Poniższe definicje zawierają więcej szczegółów i opisują, jak typ i nazwa konta są powiązane z adapterami i usługami synchronizacji.
- Rodzaj konta
-
Identyfikuje usługę, w której użytkownik ma zapisane dane. W większości przypadków użytkownik musi uwierzytelnić się w usłudze. Na przykład Kontakty Google to typ konta oznaczony kodem
google.com
. Ta wartość odpowiada typowi konta używanego przezAccountManager
. - Nazwa konta
- Określa konkretne konto lub login do konta danego typu. Konta kontaktów Google są takie same jak konta Google, które mają adres e-mail jako nazwę konta. Inne usługi mogą używać nazwy użytkownika składającej się z jednego słowa lub identyfikatora numerycznego.
Typy kont nie muszą być unikalne. Użytkownik może skonfigurować wiele kont Kontaktów Google i pobrać dane do dostawcy kontaktów. Może to nastąpić, jeśli użytkownik ma jeden zestaw kontaktów osobistych dla nazwy konta osobistego, a drugi zestaw dla konta służbowego. Nazwy kont są zwykle niepowtarzalne. Razem identyfikują one 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. Szczegółowo opisujemy to w sekcji Adaptery synchronizacji dostawcy kontaktów.
Ilustracja 4 pokazuje, jak dostawca kontaktów wpisuje się w przepływ danych o osobach. W polu „Adaptery synchronizacji” każdy adapter jest oznaczony etykietą z typem 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 do odczytu co najmniej 1 tabeli
-
READ_CONTACTS
, określony wAndroidManifest.xml
z elementem<uses-permission>
jako<uses-permission android:name="android.permission.READ_CONTACTS">
. - uprawnienia do zapisu w co najmniej 1 tabeli;
-
WRITE_CONTACTS
, określony wAndroidManifest.xml
z elementem<uses-permission>
jako<uses-permission android:name="android.permission.WRITE_CONTACTS">
.
Te uprawnienia nie obejmują danych profilu użytkownika. Profil użytkownika i wymagane uprawnienia są omówione w sekcji Profil użytkownika.
Pamiętaj, że dane kontaktowe użytkownika są danymi osobowymi i wrażliwymi. Użytkownicy martwią się o swoją prywatność, więc nie chcą, aby aplikacje zbierały informacje o nich ani o ich kontaktach. Jeśli nie będzie jasne, dlaczego potrzebujesz dostępu do danych kontaktów użytkownika, może on przyznać 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 w profilu jest połączony z wierszem kontaktów pierwotnych w każdym systemie, który używa profilu.
Każdy wiersz surowych kontaktów w 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
potrzebnych 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 dane wrażliwe. Uprawnienie android.Manifest.permission#READ_PROFILE umożliwia dostęp do danych osobowych 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 funkcję 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 surowych kontaktów lub danych profilu. Na przykład ten fragment kodu pobiera 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 wiele wierszy kontaktów i chcesz sprawdzić, czy jeden z nich jest profilem użytkownika, przetestuj kolumnę IS_USER_PROFILE
wiersza. W tej kolumnie jest ustawiona 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 repozytorium są przechowywane w różnych miejscach, m.in. w wierszach tabel Raw Contacts, Data i Contacts, w tabeli ContactsContract.Settings
i w tabeli ContactsContract.SyncState
. W tabeli poniżej znajdziesz wpływ poszczególnych elementów metadanych:
Tabela 3. Metadane u dostawcy kontaktów
Tabela | Kolumna | Wartości | Znaczenie |
---|---|---|---|
ContactsContract.RawContacts |
DIRTY |
„0” – nie zmieniono od ostatniej synchronizacji. |
Oznacza kontakty pierwotne, 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 surowych kontaktów lub danych, powinny zawsze dołączać ciąg znaków |
„1” – zmieniono od czasu ostatniej synchronizacji, wymaga synchronizacji z serwerem. | |||
ContactsContract.RawContacts |
VERSION |
Numer wersji tego wiersza. | Dostawca kontaktów automatycznie zwiększa tę wartość za każdym razem, gdy wiersz lub powiązane z nim dane ulegną zmianie. |
ContactsContract.Data |
DATA_VERSION |
Numer wersji tego wiersza. | Dostawca kontaktów automatycznie zwiększa tę wartość za każdym razem, gdy zmienia się wiersz danych. |
ContactsContract.RawContacts |
SOURCE_ID |
Ciąg znaków, który jednoznacznie identyfikuje ten surowy kontakt na koncie, na którym został utworzony. |
Gdy adapter synchronizacji tworzy nowy kontakt podstawowy, w tej kolumnie powinien być ustawiony unikalny identyfikator kontaktu podstawowego na serwerze. Gdy aplikacja na Androida tworzy nowy kontakt podstawowy, powinna pozostawić tę kolumnę pustą. Sygnalizuje to adapterowi synchronizacji, że powinien utworzyć nowy kontakt w 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 w przypadku synchronizacji:
|
ContactsContract.Groups |
GROUP_VISIBLE |
„0” – kontakty w tej grupie nie powinny być widoczne w interfejsach aplikacji na Androida. | Ta kolumna służy do zapewnienia zgodności z serwerami, które umożliwiają użytkownikowi ukrywanie kontaktów w określonych grupach. |
„1” – kontakty w tej grupie mogą być widoczne w interfejsach aplikacji. | |||
ContactsContract.Settings |
UNGROUPED_VISIBLE |
„0” – w przypadku tego konta i typu 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 podstawowych nie należy do grupy (przynależność kontaktu podstawowego do grupy jest wskazywana przez co najmniej 1 wiersz ContactsContract.CommonDataKinds.GroupMembership w tabeli ContactsContract.Data ).
Ustawiając tę flagę w wierszu tabeli ContactsContract.Settings dla typu konta i konta, możesz wymusić widoczność 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 typu 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. | W tej tabeli możesz trwale przechowywać na urządzeniu stan synchronizacji i inne dane z nią związane. |
Dostęp do dostawcy kontaktów
W tej sekcji opisujemy wytyczne dotyczące dostępu do danych od dostawcy kontaktów, skupiając się na tych kwestiach:
- Zapytania o elementy.
- Modyfikacja zbiorcza.
- Pobieranie i modyfikowanie za pomocą intencji.
- integralność danych,
Wprowadzanie zmian za pomocą adaptera synchronizacji zostało szczegółowo opisane w sekcji Adaptery synchronizacji dostawcy kontaktów.
Wykonywanie zapytań o encje
Tabele dostawcy kontaktów są uporządkowane hierarchicznie, dlatego często przydatne jest pobieranie wiersza i wszystkich powiązanych z nim wierszy „podrzędnych”. Jeśli na przykład chcesz wyświetlić wszystkie informacje o danej osobie, możesz pobrać wszystkie wiersze ContactsContract.RawContacts
dla jednego wiersza ContactsContract.Contacts
lub wszystkie wiersze ContactsContract.CommonDataKinds.Email
dla jednego 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 podobna do tabeli składającej się z wybranych kolumn z tabeli nadrzędnej i tabeli podrzędnej.
Gdy wysyłasz zapytanie o encję, podajesz projekcję i kryteria wyszukiwania na podstawie kolumn dostępnych w encji. Wynikiem jest Cursor
, która zawiera po 1 wierszu na każdy pobrany wiersz tabeli podrzędnej. Jeśli na przykład wysyłasz zapytanie ContactsContract.Contacts.Entity
o nazwę kontaktuContactsContract.CommonDataKinds.Email
i wszystkie wiersze Cursor
zawierające wszystkie kontakty pierwotne o tej nazwie, otrzymujesz ContactsContract.CommonDataKinds.Email
zawierający po jednym wierszu dla każdego wiersza ContactsContract.CommonDataKinds.Email
.
Encje upraszczają zapytania. Korzystając z encji, możesz pobrać wszystkie dane kontaktów dla kontaktu lub surowego kontaktu 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. Poza tym dostawca kontaktów przetwarza zapytanie dotyczące jednostki 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 typu, otrzymasz błąd Exception
.
Poniższy fragment kodu pokazuje, jak pobrać wszystkie wiersze kontaktów z informacjami o kontakcie. Fragment kodu jest częścią większej aplikacji, która ma 2 aktywności: „main” i „detail”. Główna aktywność
wyświetla listę wierszy kontaktów. Gdy użytkownik wybierze jeden z nich, aktywność wyśle jego identyfikator do aktywności szczegółowej. Aktywność szczegółowa używa elementu ContactsContract.Contacts.Entity
, aby wyświetlić wszystkie wiersze danych ze wszystkich surowych kontaktów 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 funkcja LoaderManager
wywołuje funkcję zwrotną onLoadFinished()
. Jednym z argumentów wejściowych tej metody jest Cursor
z wynikami zapytania. W swojej aplikacji możesz pobrać dane z tego Cursor
, aby je wyświetlić lub dalej z nimi pracować.
Zmiana zbiorcza
W miarę możliwości wstawiaj, aktualizuj i usuwaj dane w usłudze dostawcy kontaktów w „trybie wsadowym”, tworząc ArrayList
obiektów ContentProviderOperation
i wywołując applyBatch()
. Dostawca kontaktów wykonuje wszystkie operacje w ramach pojedynczej transakcji applyBatch()
, więc zmiany nigdy nie opuszczą repozytorium kontaktów w stanie niespójności. Modyfikacja zbiorcza ułatwia też jednoczesne wstawianie surowego kontaktu i jego szczegółowych danych.
Uwaga: jeśli chcesz zmodyfikować pojedynczy surowy kontakt, rozważ wysłanie intencji do aplikacji kontaktów na urządzeniu zamiast obsługiwać modyfikację w swojej aplikacji. Więcej informacji znajdziesz w sekcji Pobieranie i modyfikowanie za pomocą intencji.
Punkty zysku
Modyfikacja zbiorcza zawierająca dużą liczbę operacji może blokować inne procesy, co negatywnie wpływa na ogólne wrażenia użytkownika. Aby uporządkować wszystkie modyfikacje, które chcesz wprowadzić, na jak najmniejszej liczbie oddzielnych list, a jednocześnie zapobiec blokowaniu systemu, ustaw punkty zwrotu dla co najmniej 1 operacji.
Punkt rentowności 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 ponownie się uruchomi, będzie kontynuować wykonywanie kolejnej operacji w ArrayList
i rozpocznie nową transakcję.
Punkty rentowności powodują więcej niż 1 transakcję na połączenie z applyBatch()
. Z tego powodu w przypadku ostatniej operacji na zbiorze powiązanych wierszy należy ustawić punkt wstrzymania.
Na przykład punkt zwrotny należy ustawić dla ostatniej operacji w zbiorze, która dodaje wiersze surowych kontaktów i powiązane z nimi wiersze danych, lub dla ostatniej operacji w zbiorze wierszy powiązanych z jednym kontaktem.
Punkty zysku to również jednostka operacji niepodzielnej. Wszystkie dostępy między dwoma punktami zwrotu zakończą się powodzeniem lub niepowodzeniem jako pojedyncza jednostka. Jeśli nie ustawisz żadnych punktów wstrzymania, najmniejszą operacją niepodzielną będzie cały pakiet 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.
Modyfikowanie odwołań wstecznych
Podczas wstawiania nowego wiersza kontaktu pierwotnego i powiązanych z nim wierszy danych jako zestawu obiektów ContentProviderOperation
musisz połączyć wiersze danych z wierszem kontaktu pierwotnego, wstawiając wartość _ID
kontaktu pierwotnego jako wartość RAW_CONTACT_ID
. Ta wartość nie jest jednak dostępna podczas tworzenia ContentProviderOperation
wiersza danych, ponieważ nie zastosowano jeszcze ContentProviderOperation
wiersza surowych danych kontaktowych. Aby obejść ten problem, klasa ContentProviderOperation.Builder
ma metodę withValueBackReference()
.
Ta metoda umożliwia wstawianie lub modyfikowanie kolumny z wynikiem poprzedniej operacji.
Metoda withValueBackReference()
ma 2 argumenty:
-
key
- Klucz w parze klucz-wartość. Wartością tego argumentu powinna być nazwa kolumny w tabeli, którą modyfikujesz.
-
previousResult
- Indeks rozpoczynający się od 0 wartości w tablicy obiektów
ContentProviderResult
zapplyBatch()
. W miarę stosowania operacji wsadowych wynik każdej operacji jest przechowywany w tablicy wyników pośrednich. WartośćpreviousResult
to indeks jednego z tych wyników, który jest pobierany i przechowywany z wartościąkey
. Dzięki temu możesz wstawić nowy rekord kontaktu w formacie pierwotnym i uzyskać jego wartość_ID
, a następnie utworzyć „odwołanie zwrotne” do wartości podczas dodawania wierszaContactsContract.Data
.Cała tablica wyników jest tworzona podczas pierwszego wywołania funkcji
applyBatch()
o rozmiarze równym rozmiarowiArrayList
obiektówContentProviderOperation
, które podasz. Wszystkie elementy w tablicy wyników są jednak ustawione nanull
, a jeśli spróbujesz wykonać odwołanie wsteczne do wyniku operacji, która nie została jeszcze zastosowana, funkcjawithValueBackReference()
zwróci błądException
.
Poniższe fragmenty kodu pokazują, jak wstawić nowy kontakt i dane w partii. Zawierają one kod, który ustala punkt zwrotu i używa odwołania wstecznego.
Pierwszy fragment kodu pobiera dane kontaktów z interfejsu. Na tym etapie użytkownik wybrał już konto, do którego ma zostać dodany nowy kontakt w formacie surowym.
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 pierwotnego 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 konstruktora operacji używa
withValueBackReference()
do pobierania
RAW_CONTACT_ID
. Punkty odniesienia
odwołują się do obiektu ContentProviderResult
z pierwszej operacji,
która dodaje wiersz kontaktu w formacie surowym i zwraca jego nową wartość _ID
. W rezultacie każdy wiersz danych jest automatycznie łączony za pomocą swojego 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 uzyskiwania przychodów:
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 pokazuje wywołanie funkcji applyBatch()
, która wstawia nowe wiersze z danymi i surowymi danymi kontaktowymi.
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ą też wdrożenie optymistycznej kontroli współbieżności, czyli metody stosowania transakcji modyfikacji bez konieczności blokowania bazowego repozytorium. Aby skorzystać z tej metody, zastosuj transakcję, a potem sprawdź, czy w tym samym czasie nie wprowadzono innych modyfikacji. Jeśli zauważysz niespójną modyfikację, możesz wycofać transakcję i spróbować ponownie.
Optymistyczna kontrola współbieżności jest przydatna w przypadku urządzeń mobilnych, z których korzysta tylko jeden użytkownik naraz, a jednoczesny dostęp do repozytorium danych jest rzadki. Blokowanie nie jest używane, więc nie marnujesz czasu na ustawianie blokad ani czekanie, aż inne transakcje zwolnią swoje blokady.
Aby użyć optymistycznej kontroli współbieżności podczas aktualizowania pojedynczego wierszaContactsContract.RawContacts
, wykonaj te czynności:
-
Pobierz kolumnę
VERSION
surowego kontaktu wraz z innymi pobieranymi danymi. -
Utwórz obiekt
ContentProviderOperation.Builder
, którego można użyć do egzekwowania ograniczenia, za pomocą metodynewAssertQuery(Uri)
. W przypadku identyfikatora URI treści użyjRawContacts.CONTENT_URI
z dodanym do niego identyfikatorem_ID
kontaktu. -
W przypadku obiektu
ContentProviderOperation.Builder
wywołajwithValue()
, aby porównać kolumnęVERSION
z numerem wersji, który został właśnie pobrany. -
W przypadku tego samego
ContentProviderOperation.Builder
wywołajwithExpectedCount()
, aby mieć pewność, że to stwierdzenie testuje tylko jeden wiersz. -
Wywołaj funkcję
build()
, aby utworzyć obiektContentProviderOperation
, a następnie dodaj ten obiekt jako pierwszy w obiekcieArrayList
, który przekazujesz do funkcjiapplyBatch()
. - Zastosuj transakcję wsadową.
Jeśli wiersz kontaktu pierwotnego zostanie zaktualizowany przez inną operację między czasem odczytu wiersza a czasem próby jego zmodyfikowania, „assert” ContentProviderOperation
nie powiedzie się i cała partia operacji zostanie wycofana. Możesz wtedy ponowić próbę wysłania partii lub wykonać inne działanie.
Fragment kodu poniżej pokazuje, jak utworzyć „assert”ContentProviderOperation
po wysłaniu zapytania o jeden kontakt w formacie surowym 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
Wysyłanie intencji do aplikacji kontaktów na urządzeniu umożliwia pośredni dostęp do dostawcy kontaktów. Intencja uruchamia interfejs aplikacji Kontakty na urządzeniu, w którym użytkownicy mogą wykonywać działania związane z kontaktami. Dzięki temu typowi dostępu użytkownicy mogą:
- Wybierz kontakt z listy i przekaż go do aplikacji, aby kontynuować pracę.
- Edytuj dane istniejącego kontaktu.
- Wstawiać nowy kontakt podstawowy na dowolnym z kont użytkownika.
- Usuń kontakt lub dane kontaktów.
Jeśli użytkownik wstawia lub aktualizuje dane, możesz najpierw je zebrać i wysłać w ramach 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 dostępu do dostawcy. Nie musisz też prosić o uprawnienia do odczytu ani zapisu danych 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 uzyskania dostępu do dostawcy jest szczegółowo opisany w przewodniku
Podstawy dostawcy treści w sekcji „Dostęp do danych za pomocą intencji”. Działanie, typ MIME i wartości danych używane 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:
|
Bez lampy |
Wyświetla listę kontaktów podstawowych lub listę danych z kontaktu podstawowego w zależności od podanego typu identyfikatora URI treści.
Wywołaj
|
Wstawianie nowego kontaktu podstawowego | Insert.ACTION |
Nie dotyczy |
RawContacts.CONTENT_TYPE Typ MIME dla zestawu kontaktów pierwotnych.
|
Wyświetla ekran Dodaj kontakt aplikacji Kontakty na urządzeniu. Wyświetlane są wartości dodatkowe dodane do intencji. Jeśli jest wysyłany z startActivityForResult() , identyfikator URI treści nowo dodanego kontaktu w formacie surowym jest przekazywany 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 umożliwi użytkownikowi edytowanie wszystkich danych powiązanych z tym kontaktem.
|
Contacts.CONTENT_ITEM_TYPE , pojedynczy kontakt. |
Wyświetla ekran Edytuj kontakt w aplikacji Kontakty. Wyświetlane są wartości dodatkowe, które dodasz do intencji. Gdy użytkownik kliknie Gotowe, aby zapisać zmiany, Twoja aktywność wróci na pierwszy plan. |
Wyświetl selektor, który może też dodawać dane. | ACTION_INSERT_OR_EDIT |
Nie dotyczy |
CONTENT_ITEM_TYPE
|
Ta intencja zawsze wyświetla ekran wyboru aplikacji Kontakty. 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, a dane dodatkowe przekazane w intencji zostaną wyświetlone. Jeśli Twoja aplikacja wyświetla dane kontaktowe, takie jak adres e-mail lub numer telefonu, użyj tego zamiaru, aby umożliwić użytkownikowi dodanie danych do istniejącego kontaktu.
kontakt,
Uwaga: nie musisz wysyłać wartości nazwy w dodatkach tego zamiaru, ponieważ użytkownik zawsze wybiera istniejącą nazwę lub dodaje nową. Jeśli wyślesz imię i nazwisko, a użytkownik zdecyduje się je edytować, aplikacja Kontakty wyświetli wysłane imię i nazwisko, zastępując poprzednią wartość. Jeśli użytkownik nie zauważy tego i zapisze zmiany, stara wartość zostanie utracona. |
Aplikacja Kontakty na urządzeniu nie pozwala na usunięcie pierwotnego kontaktu ani żadnych jego danych. Aby usunąć kontakt surowy, użyj polecenia ContentResolver.delete()
lub ContentProviderOperation.newDelete()
.
Poniższy fragment kodu pokazuje, jak utworzyć i wysłać intencję, która wstawia nowy kontakt i dane w formacie surowym:
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 użytkownicy chcą mieć aktualne i poprawne. Dlatego dostawca kontaktów ma dobrze zdefiniowane reguły dotyczące integralności danych. Twoim obowiązkiem jest przestrzeganie tych zasad podczas modyfikowania danych kontaktów. Ważne zasady:
-
Zawsze dodawaj wiersz
ContactsContract.CommonDataKinds.StructuredName
za każdy dodany wierszContactsContract.RawContacts
. -
Wiersz
ContactsContract.RawContacts
bez wierszaContactsContract.CommonDataKinds.StructuredName
w tabeliContactsContract.Data
może powodować problemy podczas agregacji. -
Zawsze łącz nowe wiersze
ContactsContract.Data
z wierszem nadrzędnymContactsContract.RawContacts
. -
Wiersz
ContactsContract.Data
, który nie jest połączony zContactsContract.RawContacts
, nie będzie widoczny w aplikacji Kontakty na urządzeniu i może powodować problemy z adapterami synchronizacji. - Zmieniaj dane tylko tych kontaktów, których jesteś właścicielem.
- Pamiętaj, że dostawca kontaktów zwykle zarządza danymi z kilku różnych typów kont lub usług online. Musisz zadbać o to, aby aplikacja modyfikowała lub usuwała dane tylko w przypadku wierszy należących do Ciebie oraz aby wstawiała dane tylko z typem i nazwą konta, nad którymi masz kontrolę.
-
Zawsze używaj stałych zdefiniowanych w klasie
ContactsContract
i jej podklasach w przypadku organó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ż ostrzeżenia 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 specyficzne dla typu na domyślne nazwy kolumn. W aplikacji 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 wierszy danych niestandardowych, musisz udostępnić w swojej aplikacji aktywność edytora.
Aby wyświetlić dane niestandardowe, prześlij plik contacts.xml
zawierający element <ContactsAccountType>
i co najmniej 1 element podrzędny <ContactsDataKind>
. Szczegółowe informacje znajdziesz 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 jest przeznaczony do obsługi synchronizacji danych kontaktów między urządzeniem a usługą online. Umożliwia to użytkownikom pobieranie istniejących danych na nowe urządzenie i przesyłanie ich na nowe konto. Synchronizacja zapewnia też użytkownikom dostęp do najnowszych danych niezależnie od źródła dodatków i zmian. Kolejną zaletą synchronizacji jest to, że dane kontaktów są dostępne nawet wtedy, gdy urządzenie nie jest połączone z siecią.
Synchronizację można wdrożyć na wiele sposobów, ale system Android udostępnia wtyczkową platformę synchronizacji, która automatyzuje te zadania:
- Sprawdzam dostępność sieci.
- Planowanie i przeprowadzanie synchronizacji na podstawie preferencji użytkownika.
- ponowne uruchamianie synchronizacji, które zostały zatrzymane;
Aby korzystać z tego frameworka, musisz dostarczyć wtyczkę adaptera synchronizacji. Każdy adapter synchronizacji jest unikalny dla usługi i dostawcy treści, ale może obsługiwać wiele nazw kont dla tej samej usługi. Platforma umożliwia też korzystanie z wielu adapterów synchronizacji dla tej samej usługi i dostawcy.
Synchronizowanie klas i plików adaptera
Adapter synchronizacji implementujesz jako podklasę AbstractThreadedSyncAdapter
i instalujesz go w ramach aplikacji na Androida. System dowiaduje się o adapterze synchronizacji z elementów w manifeście aplikacji i ze specjalnego pliku XML, do którego odwołuje się manifest. Plik XML określa typ konta usługi online i uprawnienia dostawcy treści, które razem jednoznacznie identyfikują adapter. Adapter synchronizacji nie staje się aktywny, dopóki użytkownik nie doda konta dla typu konta adaptera synchronizacji i nie włączy synchronizacji dostawcy treści, z którym adapter synchronizacji synchronizuje dane. W tym momencie system zaczyna zarządzać adapterem, wywołując go w razie potrzeby w celu synchronizacji między dostawcą treści a serwerem.
Uwaga: użycie typu konta jako części identyfikacji adaptera synchronizacji umożliwia systemowi wykrywanie i grupowanie adapterów synchronizacji, które uzyskują dostęp do różnych usług 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 do swoich urządzeń, wszystkie zainstalowane adaptery synchronizacji usług Google są wyświetlane razem. Każdy z nich synchronizuje dane z innym dostawcą 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 podobną do platformy adaptera synchronizacji, która jest często używana w połączeniu z nią. Platforma uwierzytelniania korzysta z wtyczek uwierzytelniających, które są podklasami klasy AbstractAccountAuthenticator
. Aplikacja uwierzytelniająca weryfikuje tożsamość użytkownika w tych krokach:
- zbiera imię i nazwisko 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, uwierzytelnianie może je zapisać do późniejszego wykorzystania. Dzięki strukturze wtyczek uwierzytelniających usługa AccountManager
może zapewniać dostęp do wszystkich tokenów uwierzytelniających obsługiwanych i udostępnianych przez uwierzytelnianie, np. tokenów uwierzytelniających OAuth2.
Uwierzytelnianie nie jest wymagane, ale większość usług kontaktów go używa. Nie musisz jednak używać platformy uwierzytelniania Androida do uwierzytelniania.
Implementacja adaptera synchronizacji
Aby wdrożyć 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 uruchomić synchronizację, wywołuje metodę usługi
onBind()
, aby uzyskaćIBinder
dla adaptera synchronizacji. Umożliwia to systemowi wykonywanie wywołań międzyprocesowych do metod adaptera. -
Rzeczywisty adapter synchronizacji zaimplementowany jako konkretna podklasa klasy
AbstractThreadedSyncAdapter
. -
Ta klasa odpowiada za pobieranie danych z serwera, przesyłanie danych z urządzenia i rozwiązywanie konfliktów. Główna praca adaptera jest wykonywana w metodzie
onPerformSync()
. Ta klasa musi być instancją pojedynczą. -
Podklasa
Application
. -
Ta klasa działa jako fabryka pojedynczego adaptera synchronizacji. Użyj metody
onCreate()
, aby utworzyć instancję adaptera synchronizacji, i udostępnij statyczną metodę „getter”, która zwraca singleton do metodyonBind()
usługi adaptera synchronizacji. -
Opcjonalnie: komponent
Service
, który odpowiada na żądania uwierzytelnienia użytkownika wysyłane przez system. -
AccountManager
rozpoczyna tę usługę, aby rozpocząć proces uwierzytelniania. MetodaonCreate()
usługi tworzy instancję obiektu uwierzytelniania. Gdy system chce uwierzytelnić konto użytkownika w adapterze synchronizacji aplikacji, wywołuje metodęonBind()
usługi, aby uzyskaćIBinder
dla uwierzytelniania. Umożliwia to systemowi wykonywanie wywołań międzyprocesowych do metod uwierzytelniania. -
Opcjonalne: konkretna podklasa
AbstractAccountAuthenticator
, która obsługuje żądania uwierzytelniania. -
Ta klasa udostępnia metody, które
AccountManager
wywołuje 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 moduł uwierzytelniania w systemie.
-
Opisane wcześniej komponenty adaptera synchronizacji i usługi uwierzytelniania są zdefiniowane w elementach
<service>
w pliku manifestu aplikacji. Te elementy zawierają elementy podrzędne, 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 typ konta usługi internetowej. -
Opcjonalnie: element
<meta-data>
w przypadku narzędzia uwierzytelniającego wskazuje plik XMLres/xml/authenticator.xml
. Z kolei ten plik określa typ konta obsługiwany przez ten moduł uwierzytelniania, 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 w przypadku adaptera synchronizacji.
<meta-data>
-
Element
Dane ze 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 strumieniowe z Twojej sieci do tych tabel, lub odczytywać dane strumieniowe z tych tabel i wyświetlać je we własnej aplikacji, albo robić obie te rzeczy. Dzięki tym funkcjom usługi i aplikacje społecznościowe mogą być zintegrowane z funkcjami społecznościowymi Androida.
Tekst strumienia społecznościowego
Elementy strumienia są zawsze powiązane z kontaktem pierwotnym. Pole
android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID jest połączone z wartością _ID
w przypadku kontaktu pierwotnego. W wierszu elementu strumienia są też przechowywane typ i nazwa konta pierwotnego kontaktu.
Dane ze strumienia przechowuj w tych kolumnach:
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
- Wymagany. Typ konta użytkownika dla kontaktu podstawowego 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 w przypadku kontaktu podstawowego 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 pierwotnego, z którym jest powiązany ten element strumienia.
- android.provider.ContactsContract.StreamItemsColumns#COMMENTS
- Opcjonalnie. Zawiera podsumowanie informacji, 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 osadzone obrazy zasobów, które mogą być renderowane przez
fromHtml()
. Dostawca może obcinać lub skracać długie treści, ale będzie się starać nie przerywać tagów. - android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
- Ciąg tekstowy zawierający czas wstawienia lub zaktualizowania elementu strumienia w formacie milisekund od początku epoki. Aplikacje, które wstawiają lub aktualizują elementy strumienia, są odpowiedzialne za utrzymywanie tej kolumny. Nie jest ona automatycznie utrzymywana przez dostawcę kontaktów.
Aby wyświetlać informacje identyfikacyjne o elementach strumienia, użyj pól android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL i android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE, aby połączyć się z zasobami w aplikacji.
Tabela android.provider.ContactsContract.StreamItems zawiera też kolumny android.provider.ContactsContract.StreamItemsColumns#SYNC1–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 w tabeli jest połączona z wartościami 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).
- Binarna reprezentacja zdjęcia, której 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 bieżącej 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 te parametry są opisane w dalszej części), aby przechowywać zdjęcia w pliku. Ta kolumna zawiera teraz miniaturę zdjęcia, którą można odczytać.
- android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
-
Identyfikator liczbowy zdjęcia kontaktu. Dołącz tę wartość do stałej
DisplayPhoto.CONTENT_URI
, aby uzyskać identyfikator URI treści wskazujący pojedynczy plik ze zdjęciem, a następnie wywołajopenAssetFileDescriptor()
, aby uzyskać uchwyt do pliku ze zdjęciem. - android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
-
Identyfikator URI treści wskazujący bezpośrednio plik zdjęcia reprezentowanego przez ten wiersz.
Zadzwoń na
openAssetFileDescriptor()
, używając tego identyfikatora URI, aby uzyskać dostęp do pliku ze zdjęciem.
Korzystanie z tabel strumienia społecznościowego
Tabele te działają tak samo jak inne główne tabele w usłudze dostawcy kontaktów, z tą różnicą, że:
- 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ć, aplikacja musi mieć uprawnienie android.Manifest.permission#WRITE_SOCIAL_STREAM.
-
W przypadku tabeli android.provider.ContactsContract.StreamItems liczba wierszy
przechowywanych dla każdego kontaktu jest ograniczona. Gdy ten limit 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 obiekt Cursor zawierający jeden wiersz z jedną 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ą kontaktów na urządzeniu stanowią skuteczny sposób na połączenie systemu sieci społecznościowej z istniejącymi kontaktami. Dostępne są te funkcje:
- Synchronizując usługę społecznościową z dostawcą kontaktów za pomocą adaptera synchronizacji, możesz pobierać najnowszą aktywność kontaktów użytkownika i przechowywać ją w tabelach android.provider.ContactsContract.StreamItems i android.provider.ContactsContract.StreamItemPhotos do późniejszego wykorzystania.
- Oprócz regularnej synchronizacji możesz uruchomić adapter synchronizacji, aby pobrać 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 danego kontaktu.
- Rejestrując powiadomienie w aplikacji kontaktów na urządzeniu i w usłudze dostawcy kontaktów, możesz otrzymywać intencję, gdy kontakt jest wyświetlany, i w tym momencie aktualizować stan kontaktu w swojej usłudze. To podejście może być szybsze i wykorzystywać mniejszą przepustowość niż pełna synchronizacja za pomocą adaptera synchronizacji.
- Użytkownicy mogą dodać kontakt do usługi społecznościowej, gdy wyświetlają go w aplikacji Kontakty na urządzeniu. Możesz to zrobić za pomocą funkcji „Zaproś kontakt”, którą włączasz, wykonując działanie polegające na dodaniu istniejącego kontaktu do Twojej sieci, oraz za pomocą pliku XML, który zawiera szczegóły Twojej aplikacji dla aplikacji do obsługi kontaktów na urządzeniu i dla dostawcy kontaktów.
Regularna synchronizacja elementów strumienia z dostawcą kontaktów jest taka sama jak inne synchronizacje. Więcej informacji o synchronizacji znajdziesz w sekcji Synchronizacja dostawcy kontaktów. Rejestrowanie powiadomień i zapraszanie kontaktów omówimy w następnych 2 sekcjach.
Rejestrowanie się 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 ten adapter:
-
Utwórz plik o nazwie
contacts.xml
w 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 będzie otrzymywać powiadomienia, gdy użytkownik otworzy stronę szczegółów kontaktu w aplikacji Kontakty na urządzeniu, dodaj do elementu atrybut
viewContactNotifyService="serviceclass"
, gdzieserviceclass
to w pełni kwalifikowana nazwa klasy usługi, która powinna otrzymywać intencję z aplikacji Kontakty na urządzeniu. W przypadku usługi powiadamiania użyj klasy, która rozszerzaIntentService
, aby umożliwić usłudze odbieranie intencji. Dane w przychodzącym zamiarze zawierają identyfikator URI treści surowego kontaktu, który kliknął użytkownik. Z usługi powiadamiania możesz powiązać, a następnie wywołać adapter synchronizacji, aby zaktualizować dane kontaktu podstawowego.
Aby zarejestrować aktywność, która ma być wywoływana, gdy użytkownik kliknie element strumienia lub zdjęcie albo oba te elementy:
-
Utwórz plik o nazwie
contacts.xml
w 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ą z aktywności do obsługi kliknięcia elementu strumienia w aplikacji Kontakty na urządzeniu, dodaj do elementu atrybut
viewStreamItemActivity="activityclass"
, gdzieactivityclass
to w pełni kwalifikowana nazwa klasy aktywności, która powinna odbierać intencję z aplikacji Kontakty na urządzeniu. -
Aby zarejestrować jedną z aktywności do obsługi kliknięcia przez użytkownika zdjęcia w aplikacji kontaktów na urządzeniu, dodaj do elementu atrybut
viewStreamItemPhotoActivity="activityclass"
, gdzieactivityclass
to w pełni kwalifikowana nazwa klasy aktywności, która powinna otrzymać intencję z aplikacji kontaktów na urządzeniu.
Element <ContactsAccountType>
jest szczegółowo opisany w sekcji Element<ContactsAccountType>.
Przychodzący zamiar zawiera identyfikator URI treści elementu lub zdjęcia, które użytkownik kliknął. Aby mieć osobne działania dla elementów tekstowych i zdjęć, użyj obu atrybutów w tym samym pliku.
Korzystanie z usługi społecznościowej
Użytkownicy nie muszą opuszczać aplikacji Kontakty na urządzeniu, aby zaprosić kontakt do Twojej witryny społecznościowej. Zamiast tego możesz poprosić aplikację Kontakty na urządzeniu o wysłanie intencji zaproszenia kontaktu do jednej z Twoich aktywności. Aby to skonfigurować:
-
Utwórz plik o nazwie
contacts.xml
w 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"
activityclass
to pełna 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 Kontakty na urządzeniu.
Uwaga: ContactsSource
to nieużywana nazwa tagu dla 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 są opisane w kolejnych sekcjach.
Element <ContactsAccountType>
Element <ContactsAccountType>
kontroluje interakcję aplikacji z aplikacją do obsługi kontaktów. Ma on 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">
zawarte w:
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 jednego z ich strumieni w sieci społecznościowej itp.
Zwróć uwagę, że w przypadku atrybutów elementu <ContactsAccountType>
nie jest wymagany prefiks atrybutu android:
.
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
inviteContactActivity
w menu Dodaj połączenie. Możesz na przykład użyć ciągu znaków „Obserwuj w mojej sieci”. W przypadku tej etykiety możesz użyć identyfikatora zasobu ciągu znaków. viewContactNotifyService
- W Twojej aplikacji w pełni kwalifikowana nazwa klasy usługi, która powinna otrzymywać powiadomienia, gdy użytkownik wyświetla kontakt. To powiadomienie jest wysyłane przez aplikację Kontakty na urządzeniu. Umożliwia ono odłożenie przez aplikację operacji wymagających dużej ilości danych do czasu, gdy będą potrzebne. Na przykład aplikacja może odpowiedzieć na to powiadomienie, odczytując i wyświetlając zdjęcie kontaktu w wysokiej rozdzielczości oraz najnowsze wpisy z jego profilu w mediach społecznościowych. Więcej informacji o tej funkcji znajdziesz 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 grupie. Gdy użytkownik kliknie etykietę grupy w aplikacji kontaktów na urządzeniu, wyświetli się interfejs tej aktywności.
viewGroupActionLabel
-
Etykieta, którą aplikacja Kontakty wyświetla w przypadku elementu interfejsu umożliwiającego
użytkownikowi przeglądanie grup w aplikacji.
W przypadku tego atrybutu dozwolony jest identyfikator zasobu w postaci ciągu znaków.
viewStreamItemActivity
- Pełna nazwa klasy aktywności w aplikacji, którą aplikacja do obsługi kontaktów na urządzeniu uruchamia, gdy użytkownik kliknie element strumienia dotyczący kontaktu podstawowego.
viewStreamItemPhotoActivity
- Pełna nazwa klasy działania w aplikacji, którą aplikacja kontaktów na urządzeniu uruchamia, gdy użytkownik kliknie zdjęcie w elemencie strumienia dla kontaktu podstawowego.
Element <ContactsDataKind>
Element <ContactsDataKind>
kontroluje wyświetlanie w interfejsie aplikacji do obsługi kontaktów niestandardowych wierszy danych aplikacji. Ma on taką składnię:
<ContactsDataKind android:mimeType="MIMEtype" android:icon="icon_resources" android:summaryColumn="column_name" android:detailColumn="column_name">
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 kontaktu pierwotnego. Każdy element podrzędny <ContactsDataKind>
elementu <ContactsAccountType>
reprezentuje typ wiersza danych niestandardowych, który adapter synchronizacji dodaje do tabeli ContactsContract.Data
. Dodaj jeden element
<ContactsDataKind>
dla każdego używanego niestandardowego typu MIME. Nie musisz dodawać tego elementu, jeśli masz niestandardowy wiersz danych, dla którego nie chcesz wyświetlać danych.
Atrybuty:
android:mimeType
-
Niestandardowy typ MIME zdefiniowany dla jednego z niestandardowych typów wierszy danych w tabeli
ContactsContract.Data
. Na przykład wartośćvnd.android.cursor.item/vnd.example.locationstatus
może być niestandardowym typem MIME w przypadku wiersza danych, który rejestruje ostatnią znaną lokalizację kontaktu. android:icon
- Zasób rysowalny Androida, który aplikacja Kontakty wyświetla obok Twoich danych. Użyj tego, aby poinformować użytkownika, że dane pochodzą z Twojej usługi.
android:summaryColumn
- Nazwa kolumny pierwszej z 2 wartości pobranych z wiersza danych. Wartość jest wyświetlana w pierwszym wierszu wpisu dla tego wiersza danych. Pierwsza linia ma służyć jako podsumowanie danych, ale jest opcjonalna. Zobacz też android:detailColumn.
android:detailColumn
-
Nazwa kolumny 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 typu konta powinien przesyłać dane grup między dostawcą kontaktów a serwerem. Gdy użytkownicy dodają nowy kontakt na serwerze, 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, są przechowywane w tabeli ContactsContract.Data
przy użyciu typu MIME ContactsContract.CommonDataKinds.GroupMembership
.
Jeśli projektujesz adapter synchronizacji, który będzie dodawać dane kontaktów z serwera do dostawcy kontaktów, a nie używasz grup, musisz poinformować dostawcę, aby Twoje dane były widoczne. W kodzie wykonywanym, 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. W takim przypadku dostawca kontaktów zawsze będzie udostępniać dane kontaktów, nawet jeśli nie używasz grup.
Zdjęcia kontaktów
Tabela ContactsContract.Data
przechowuje zdjęcia w wierszach z typem MIME Photo.CONTENT_ITEM_TYPE
. Kolumna CONTACT_ID
w wierszu jest połączona z kolumną _ID
w surowym kontakcie, do którego należy.
Klasa ContactsContract.Contacts.Photo
definiuje podtabelę ContactsContract.Contacts
zawierającą informacje o zdjęciu głównego kontaktu, czyli głównym zdjęciu podstawowego kontaktu. Podobnie klasa ContactsContract.RawContacts.DisplayPhoto
definiuje podtabelę ContactsContract.RawContacts
zawierającą informacje o zdjęciu głównym nieprzetworzonego kontaktu.
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 podstawowego, ale możesz wysłać zapytanie do tabeli ContactsContract.Data
, wybierając kolumny _ID
, Photo.CONTENT_ITEM_TYPE
i IS_PRIMARY
kontaktu podstawowego, aby znaleźć wiersz głównego zdjęcia kontaktu podstawowego.
Dane z sieci społecznościowych dotyczące danej osoby mogą też obejmować zdjęcia. Są one przechowywane w tabeli android.provider.ContactsContract.StreamItemPhotos, która jest szczegółowo opisana w sekcji Zdjęcia w strumieniu społecznościowym.