dostawca kontaktów

Dostawca kontaktów to zaawansowany i elastyczny komponent Androida, który zarządza centralnym repozytorium danych o osobach na urządzeniu. Dostawca kontaktów jest źródłem danych, które widzisz w aplikacji 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 tabeli ContactsContract.Data w wierszu ContactsContract.CommonDataKinds.StructuredName. Kontakt podstawowy ma tylko 1 wiersz tego typu w tabeli ContactsContract.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 to becky.sharp@dataservice.example.com, użytkownik musi najpierw dodać „typ” konta (com.example.dataservice) i „nazwę” konta (becky.smart@dataservice.example.com), zanim aplikacja będzie mogła dodać wiersze 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:

  1. Kontakt bazowy „Thomas Higginson” powiązany z emily.dickinson@gmail.com. Typ konta użytkownika to Google.
  2. 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.
  3. 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 wierszu ContactsContract.Data zawierającym ten numer w kolumnie IS_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.

Jak nazwy kolumn specyficzne dla typu są mapowane 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.

Główne tabele dostawcy kontaktów

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:

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.

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 przez AccountManager.
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.

Przepływ danych o osobach

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

Wymagane uprawnienia

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

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

Te uprawnienia nie obejmują danych profilu użytkownika. Profil użytkownika i wymagane uprawnienia 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_CONTACTSWRITE_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 CALLER_IS_SYNCADAPTER do używanego identyfikatora URI treści. Zapobiega to oznaczaniu wierszy jako zmienionych przez dostawcę. W przeciwnym razie zmiany w adapterze synchronizacji będą wyglądać na lokalne i zostaną wysłane na serwer, mimo że to on był źródłem zmiany.

„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:

  • Unikalny: każdy kontakt w koncie musi mieć własny identyfikator źródła. Jeśli tego nie zrobisz, spowodujesz problemy w aplikacji Kontakty. Zwróć uwagę, że 2 kontakty pierwotne tego samego typu konta mogą mieć ten sam identyfikator źródła. Na przykład kontakt „Thomas Higginson” na koncie emily.dickinson@gmail.com może mieć ten sam identyfikator źródła co kontakt „Thomas Higginson” na koncie emilyd@gmail.com.
  • Stabilne: identyfikatory źródła są stałą częścią danych usługi online dotyczących pierwotnego kontaktu. Jeśli na przykład użytkownik wyczyści Pamięć kontaktów w ustawieniach aplikacji i ponownie zsynchronizuje dane, przywrócone kontakty pierwotne powinny mieć te same identyfikatory źródła co wcześniej. Jeśli tego nie zrobisz, skróty przestaną działać.
ContactsContract.Groups GROUP_VISIBLE „0” – kontakty w tej grupie nie powinny być widoczne w interfejsach aplikacji na Androida. Ta kolumna 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.Entityo 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 ContentProviderResultapplyBatch(). 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 wiersza ContactsContract.Data.

Cała tablica wyników jest tworzona podczas pierwszego wywołania funkcji applyBatch() o rozmiarze równym rozmiarowi ArrayList obiektów ContentProviderOperation, które podasz. Wszystkie elementy w tablicy wyników są jednak ustawione na null, a jeśli spróbujesz wykonać odwołanie wsteczne do wyniku operacji, która nie została jeszcze zastosowana, funkcja withValueBackReference() zwróci błąd Exception.

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:

  1. Pobierz kolumnę VERSION surowego kontaktu wraz z innymi pobieranymi danymi.
  2. Utwórz obiekt ContentProviderOperation.Builder, którego można użyć do egzekwowania ograniczenia, za pomocą metody newAssertQuery(Uri). W przypadku identyfikatora URI treści użyj RawContacts.CONTENT_URI z dodanym do niego identyfikatorem _ID kontaktu.
  3. W przypadku obiektu ContentProviderOperation.Builder wywołaj withValue(), aby porównać kolumnę VERSION z numerem wersji, który został właśnie pobrany.
  4. W przypadku tego samego ContentProviderOperation.Builder wywołaj withExpectedCount(), aby mieć pewność, że to stwierdzenie testuje tylko jeden wiersz.
  5. Wywołaj funkcję build(), aby utworzyć obiekt ContentProviderOperation, a następnie dodaj ten obiekt jako pierwszy w obiekcie ArrayList, który przekazujesz do funkcji applyBatch().
  6. 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 startActivityForResult(), która zwraca identyfikator URI treści wybranego wiersza. Identyfikator URI ma postać identyfikatora URI treści tabeli z dodanym do niego LOOKUP_ID wiersza. Aplikacja Kontakty na urządzeniu przekazuje uprawnienia do odczytu i zapisu tego identyfikatora URI treści na czas trwania aktywności. Więcej informacji znajdziesz w przewodniku Podstawowe informacje o dostawcach treści.

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 wiersz ContactsContract.RawContacts.
Wiersz ContactsContract.RawContacts bez wiersza ContactsContract.CommonDataKinds.StructuredName w tabeli ContactsContract.Data może powodować problemy podczas agregacji.
Zawsze łącz nowe wiersze ContactsContract.Data z wierszem nadrzędnym ContactsContract.RawContacts.
Wiersz ContactsContract.Data, który nie jest połączony z ContactsContract.RawContacts, nie będzie widoczny w aplikacji Kontakty na urządzeniu i może 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ści TYPE.
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:

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

Jeśli usługa zaakceptuje dane logowania, 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 metody onBind() 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. Metoda onCreate() 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:
    <meta-data>
  • Element <meta-data> usługi adaptera synchronizacji wskazuje plik XML res/xml/syncadapter.xml. Z kolei ten plik określa identyfikator URI usługi internetowej, która będzie synchronizowana z dostawcą kontaktów, oraz typ konta usługi internetowej.
  • Opcjonalnie: element <meta-data> w przypadku narzędzia uwierzytelniającego wskazuje plik XML res/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.

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łaj openAssetFileDescriptor(), 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:

  1. Utwórz plik o nazwie contacts.xml w katalogu res/xml/ projektu. Jeśli masz już ten plik, możesz pominąć ten krok.
  2. W tym pliku dodaj element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jeśli ten element już istnieje, możesz pominąć ten krok.
  3. Aby zarejestrować usługę, która będzie otrzymywać powiadomienia, gdy użytkownik otworzy stronę szczegółów kontaktu w aplikacji Kontakty na urządzeniu, dodaj do elementu atrybut viewContactNotifyService="serviceclass", gdzie serviceclass to w pełni kwalifikowana nazwa klasy usługi, która powinna otrzymywać intencję z aplikacji Kontakty na urządzeniu. W przypadku usługi powiadamiania użyj klasy, która rozszerza IntentService, 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:

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

  1. Utwórz plik o nazwie contacts.xml w katalogu res/xml/ projektu. Jeśli masz już ten plik, możesz pominąć ten krok.
  2. W tym pliku dodaj element <ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">. Jeśli ten element już istnieje, możesz pominąć ten krok.
  3. Dodaj te atrybuty:
    • inviteContactActivity="activityclass"
    • inviteContactActionLabel="@string/invite_action_label"
    Wartość activityclass to pełna 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.PhotoContactsContract.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_TYPEIS_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.