Utwórz dostawcę treści

Dostawca treści zarządza dostępem do centralnego repozytorium danych. Wdrożenie dostawcy jako co najmniej jednej klasy w aplikacji na Androida wraz z elementami w plik manifestu. Jedna z Twoich klas implementuje podklasę ContentProvider – interfejs między Twoim dostawcą a usługą i innych aplikacjach.

Chociaż dostawcy treści są przeznaczone do udostępniania danych innym aplikacji, możesz mieć w aplikacji działania, które pozwolą użytkownikowi zapytań i modyfikowania danych zarządzanych przez dostawcę.

Ta strona zawiera opis podstawowego procesu tworzenia dostawcy treści i listy interfejsów API do wykorzystania.

Zanim zaczniesz tworzyć

Zanim zaczniesz tworzyć dostawcę, weź pod uwagę te kwestie:

  • Zdecyduj, czy potrzebujesz dostawcy treści. Musisz tworzyć treści, dostawcy, jeśli chcesz udostępnić co najmniej jedną z tych funkcji:
    • chcesz zaoferować złożone dane lub pliki innym aplikacjom;
    • Chcesz umożliwić użytkownikom kopiowanie złożonych danych z Twojej aplikacji do innych aplikacji.
    • Chcesz podać niestandardowe sugestie wyszukiwania za pomocą platformy wyszukiwania.
    • Chcesz udostępnić dane aplikacji widżetom.
    • Chcesz zaimplementować AbstractThreadedSyncAdapter, CursorAdapter lub CursorLoader zajęcia.

    Do korzystania z baz danych ani innych rodzajów danych nie potrzebujesz dostawcy. trwała pamięć masowa, jeśli użycie odbywa się w całości w ramach Twojej aplikacji. i nie potrzebujesz żadnej z wymienionych wcześniej funkcji. Zamiast tego możesz: korzystać z jednego z systemów pamięci opisanych w Informacje o miejscu na dane i pliki

  • Przeczytaj podstawowe informacje o dostawcach treści, aby dowiedzieć się więcej o dostawcach i ich działaniu.

Następnie wykonaj te czynności, aby utworzyć dostawcę:

  1. Projektowanie nieprzetworzonej pamięci masowej na dane. Dostawca treści udostępnia dane na 2 sposoby:
    Dane pliku
    Dane, które zwykle trafiają do plików, na przykład: zdjęcia, pliki dźwiękowe i filmy. Przechowuj pliki w prywatnym folderze aplikacji kosmosu. W odpowiedzi na żądanie pobrania pliku z innej aplikacji może zaoferować nick dla pliku.
    Uporządkowane dane
    Dane, które zwykle trafiają do bazy danych, tablicy lub podobnej struktury. Przechowuj dane w formacie zgodnym z tabelami wierszy i kolumn. wiersz A reprezentuje jednostkę, np. osobę lub przedmiot w asortymencie. Kolumna oznacza niektóre dane dotyczące podmiotu, np. imię i nazwisko osoby lub cenę produktu. Powszechnym sposobem tego typu są w bazie danych SQLite, ale można użyć dowolnego typu pamięci trwałej. Więcej informacji o typach pamięci masowej dostępnych w systemu Android, patrz Magazyn danych projektu.
  2. zdefiniować konkretną implementację klasy ContentProvider oraz jego wymaganych metod. Ta klasa łączy Twoje dane z pozostałymi danymi System Android. Więcej informacji o tych zajęciach: Zaimplementuj sekcję klasy ContentProvider.
  3. Zdefiniuj ciąg urzędowy dostawcy, identyfikatory URI treści i nazwy kolumn. Jeśli chcesz aplikacji dostawcy do obsługi intencji, definiowania działań intencji, dodawania danych dodatkowych i flag. Zdefiniuj także uprawnienia wymagane dla aplikacji, które mają aby uzyskać dostęp do swoich danych. Rozważ zdefiniowanie wszystkich tych wartości jako stałych w funkcji oddzielnej klasy umowy. Później możesz udostępnić tę klasę innym programistom. Więcej więcej informacji o identyfikatorach URI treści znajdziesz w Sekcja Identyfikatory URI treści projektu. Więcej informacji o intencjach znajdziesz tutaj: Intencje i dostęp do danych.
  4. Dodaj inne opcjonalne elementy, takie jak przykładowe dane lub implementacja z AbstractThreadedSyncAdapter, które mogą synchronizować dane między dostawcy i danych w chmurze.

Przechowywanie danych projektu

Dostawca treści to interfejs do danych zapisanych w uporządkowanym formacie. Zanim utworzysz wybrać sposób przechowywania danych. Dane można przechowywać w dowolnym a potem zaprojektować interfejs tak, aby w razie potrzeby odczytywać i zapisywać dane.

Oto niektóre technologie przechowywania danych dostępne na Androidzie:

  • Jeśli pracujesz z uporządkowanymi danymi, rozważ użycie relacyjnej bazy danych takiej jak SQLite lub nierelacyjny magazyn danych klucz-wartość, taki jak LevelDB. Jeśli pracujesz z nieuporządkowanymi danymi, takimi jak pliki audio, obrazy czy filmy, jako pliki. Możesz łączyć i dopasowywać kilka różnych typów pamięci masowej oraz udostępniać je korzystając z usług jednego dostawcy treści.
  • System Android może korzystać z biblioteki trwałości sal, która zapewnia dostęp do interfejsu API bazy danych SQLite udostępnianego przez dostawców Androida do przechowywania danych tabel. Aby utworzyć bazę danych za pomocą tego Library, utwórz instancję podklasy RoomDatabase, jak opisano w Zapisywanie danych w lokalnej bazie danych przy użyciu Pokoju.

    Nie musisz używać bazy danych do wdrażania repozytorium. Usługodawca wyglądają na zewnątrz jako zbiór tabel, podobnie jak w relacyjnej bazie danych, ale jest to nie jest wymagane do wewnętrznego wdrożenia dostawcy.

  • Android ma różne interfejsy API do przechowywania danych w postaci plików. Aby dowiedzieć się więcej o miejscu na pliki, przeczytaj Informacje o miejscu na dane i pliki Jeśli zaprojektowanie dostawcy danych dotyczących mediów, takich jak muzyka czy filmy, wybierz dostawcę, który łączy dane w tabeli i pliki.
  • W rzadkich przypadkach wdrożenie więcej niż jednego dostawcy treści może być korzystne. do jednej aplikacji. Możesz na przykład udostępnić niektóre dane widżetowi za pomocą: jednego dostawcy treści, a inny zestaw danych udostępniać aplikacji.
  • Do pracy z danymi sieciowymi używaj zajęć w funkcjach java.net i android.net Możesz też synchronizować dane sieciowe z danymi lokalnymi np. w bazie danych, a potem oferują dane w postaci tabel lub plików.

Uwaga: jeśli w repozytorium wprowadzisz zmianę, która nie jest zgodne wstecznie, musisz oznaczyć repozytorium nową wersją numer. Musisz też zwiększyć numer wersji aplikacji, która implementuje nowego dostawcę treści. Wprowadzenie tej zmiany zapobiega zmienia wersję, powodując awarię systemu przy próbie ponownego zainstalowania która ma niezgodnego dostawcę treści.

Uwagi na temat projektowania danych

Oto kilka wskazówek dotyczących projektowania struktury danych dostawcy:

  • Dane tabeli muszą zawsze mieć „klucz podstawowy” utrzymywanej przez dostawcę kolumny. jako unikalną wartość liczbową dla każdego wiersza. Za pomocą tej wartości możesz połączyć wiersz z powiązanymi wierszy w innych tabelach (używając go jako „klucza obcego”). Możesz użyć dowolnej nazwy w tej kolumnie najlepiej użyć kolumny BaseColumns._ID. ponieważ łączenie wyników zapytania dostawcy z polem ListView wymaga, by jedna z pobranych kolumn miała nazwę _ID
  • Jeśli chcesz udostępnić obrazy bitmap lub inne bardzo duże fragmenty danych w postaci plików, danych w pliku, a następnie podać je pośrednio, zamiast przechowywać bezpośrednio tabeli. Jeśli to zrobisz, musisz powiadomić użytkowników swojego dostawcy, że muszą użyć tagu ContentResolver, aby uzyskać dostęp do danych.
  • Typ danych binarnych dużych obiektów (BLOB) umożliwia przechowywanie danych o różnych rozmiarach lub o zróżnicowanej strukturze. W kolumnie BLOB można na przykład przechowywać bufor protokołu lub Struktura JSON.

    Za pomocą obiektu BLOB możesz też wdrożyć tabelę niezależną od schematu. W tego typu tabeli, definiujesz kolumnę klucza podstawowego, kolumny typu MIME bardziej ogólnych kolumn jako BLOB. Znaczenie danych w kolumnach BLOB jest wskazane przez wartość w kolumnie Typ MIME. Dzięki temu możesz przechowywać różne typy wierszy w do tej samej tabeli. „Dane” dostawcy kontaktów tabela ContactsContract.Data to przykład interfejsu niezależnego od schematu tabeli.

Identyfikatory URI treści projektu

Identyfikator URI treści to identyfikator URI identyfikujący dane u dostawcy. Identyfikatory URI treści obejmują symboliczną nazwę całego dostawcy (jego organu) oraz nazwa tabeli lub pliku (ścieżka). Opcjonalna część identyfikatora wskazuje w pojedynczym wierszu tabeli. Wszystkie metody dostępu do danych ContentProvider zawiera identyfikator URI treści jako argument. Dzięki temu możesz: określić tabelę, wiersz lub plik, do których chcesz uzyskać dostęp;

Informacje o identyfikatorach URI treści znajdziesz tutaj: Podstawowe informacje o dostawcach treści

Projektowanie organów

Dostawca ma zwykle jeden urząd, który pełni funkcję jego wewnętrznej nazwy Androida. Do unikanie konfliktów z innymi dostawcami, używanie własności domeny internetowej (odwrotnie) i za podstawę działania tego urzędu. Ponieważ dotyczy to również Androida nazwy pakietów, możesz zdefiniować urząd dostawcy jako rozszerzenie nazwy z pakietu zawierającego dostawcę.

Jeśli na przykład nazwa pakietu na Androida to com.example.<appname>, udostępnij swojemu dostawcy urząd: com.example.<appname>.provider.

Projektowanie struktury ścieżki

Programiści zwykle tworzą identyfikatory URI treści na podstawie źródeł, dołączając ścieżki kierujące do poszczególnych tabel. Jeśli np. masz 2 tabele: table1 i table2, możesz połączyć je z autoryzacją z poprzedniego przykładu, aby uzyskać identyfikatory URI treści com.example.<appname>.provider/table1 i com.example.<appname>.provider/table2 Ścieżki nie są może być ograniczona do jednego segmentu i nie musi istnieć tabela dla każdego poziomu ścieżki.

Obsługa identyfikatorów URI treści

Zgodnie z tradycją dostawcy oferują dostęp do pojedynczego wiersza w tabeli, akceptując identyfikator URI treści z wartością identyfikatora wiersza na końcu identyfikatora URI. Dostawcy zgodnie z konwencją dopasowują identyfikatora do kolumny _ID tabeli i wykonać żądany dostęp za pomocą pasującego wiersza.

Ta konwencja ułatwia spójne projektowanie aplikacji uzyskujących dostęp do dostawcy. Aplikacja wykonuje zapytanie względem dostawcy i wyświetla wynikowy Cursor w ListView przy użyciu CursorAdapter. Definicja obiektu CursorAdapter wymaga jednej z kolumn w Cursor na _ID

Następnie wybiera w interfejsie jeden z wyświetlonych wierszy, aby zobaczyć lub zmodyfikować i skalowalnych danych. Aplikacja uzyskuje odpowiedni wiersz z tabeli Cursor reprezentującej ListView, pobiera wartość _ID dla tego wiersza i dodaje ją do identyfikator URI treści i wysyła żądanie dostępu do dostawcy. Dostawca może wtedy wykonać te czynności: zapytanie lub modyfikacja do wiersza wybranego przez użytkownika.

Wzorce identyfikatora URI treści

Aby ułatwić Ci wybór czynności do wykonania w związku z identyfikatorem URI treści przychodzącej, interfejs Provider API zawiera klasa wygodę UriMatcher, która mapuje wzorce URI treści na jest liczbą całkowitą. W instrukcji switch można użyć wartości całkowitych, wybiera żądane działanie dla identyfikatora URI treści lub identyfikatorów URI pasujących do określonego wzorca.

Wzorzec identyfikatora URI treści pasuje do identyfikatorów URI treści przy użyciu symboli wieloznacznych:

  • * pasuje do dowolnego prawidłowego ciągu znaków o dowolnej długości.
  • # pasuje do ciągu znaków liczbowych o dowolnej długości.

Jako przykład do projektowania i kodowania obsługi identyfikatorów URI treści rozważmy dostawcę z urząd com.example.app.provider, który rozpoznaje następujące identyfikatory URI treści wskazujące tabele:

  • content://com.example.app.provider/table1: tabela o nazwie table1.
  • content://com.example.app.provider/table2/dataset1: tabela o nazwie dataset1
  • content://com.example.app.provider/table2/dataset2: tabela o nazwie dataset2
  • content://com.example.app.provider/table3: tabela o nazwie table3.

Dostawca rozpoznaje też te identyfikatory URI treści, jeśli do nich dołączany jest identyfikator wiersza, np. content://com.example.app.provider/table3/1 dla wiersza wskazywanego przez 1 w: table3.

Oto możliwe wzorce identyfikatora URI treści:

content://com.example.app.provider/*
Pasuje do dowolnego identyfikatora URI treści u dostawcy.
content://com.example.app.provider/table2/*
Pasuje do identyfikatora URI treści dla tabel dataset1 i dataset2, ale nie pasuje do identyfikatorów URI treści dla table1 lub table3.
content://com.example.app.provider/table3/#
Pasuje do identyfikatora URI treści dla pojedynczych wierszy w funkcji table3, jak content://com.example.app.provider/table3/6 w wierszu identyfikowanym przez 6

Poniższy fragment kodu pokazuje, jak działają metody dostępne w pliku UriMatcher. Ten kod obsługuje identyfikatory URI całej tabeli inaczej niż identyfikatory URI jednego wiersza, korzystając ze wzorca identyfikatora URI treści content://<authority>/<path> na tabele i content://<authority>/<path>/<id> dla pojedynczych wierszy.

Metoda addURI() mapuje i ścieżkę do wartości całkowitej. Metoda match() zwraca wartość całkowitą dla identyfikatora URI. Instrukcja switch wybiera pomiędzy wysyłaniem zapytań dotyczących całej tabeli a wysyłaniem zapytań dotyczących pojedynczego rekordu.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

Inna klasa, ContentUris, zapewnia wygodne metody pracy z częścią id identyfikatorów URI treści. Zajęcia Uri i Uri.Builder zawierają wygodne metody analizy istniejących Uri obiektów i tworzenie nowych.

Implementowanie klasy ContentProvider

Dostępem zarządza instancja ContentProvider do uporządkowanego zbioru danych dzięki obsłudze żądań z innych aplikacji. Wszystkie formularze dostępu ostatecznie wywoła parametr ContentResolver, który następnie wywołuje konkretny ContentProvider.

Wymagane metody

Klasa abstrakcyjna ContentProvider definiuje 6 metod abstrakcyjnych, które: które wdrożysz w ramach swojego konkretnego podklasy. Wszystkie te metody oprócz Funkcja onCreate() jest wywoływana przez aplikację kliencką który próbuje uzyskać dostęp do dostawcy treści.

query()
Pobierz dane od dostawcy. Użyj argumentów do wybrania tabeli, aby w zapytaniu, wiersze i kolumny do zwrócenia, a także kolejność sortowania wyniku. Zwraca dane jako obiekt Cursor.
insert()
Wstaw nowy wiersz w wierszu dostawcy. Użyj argumentów, aby wybrać tabeli docelowej i uzyskać wartości kolumny do użycia. Zwraca identyfikator URI treści dla nowo wstawiony wiersz.
update()
Zaktualizuj istniejące wiersze u dostawcy. Użyj argumentów, aby wybrać tabelę i wiersze do zaktualizowania kolumny i pobrania zaktualizowanych wartości. Zwraca liczbę zaktualizowanych wierszy.
delete()
Usuń wiersze od dostawcy. Użyj argumentów do wybrania tabeli i wierszy do usuwania. Zwraca liczbę usuniętych wierszy.
getType()
Zwraca typ MIME odpowiadający identyfikatorowi URI treści. Metodę tę opisano bardziej szczegółowo szczegóły w sekcji Implementowanie typów MIME dostawcy treści.
onCreate()
Zainicjuj dostawcę. System Android wywołuje tę metodę natychmiast po niej. tworzy dostawcę. Dostawca zostanie utworzony dopiero ContentResolver obiekt próbuje uzyskać do niego dostęp.

Te metody mają taki sam podpis jak metody o identycznej nazwie ContentResolver metody.

Podczas wdrażania tych metod musisz uwzględnić:

  • Wszystkie te metody oprócz onCreate() mogą być wywoływane przez wiele wątków jednocześnie, muszą więc być bezpieczne dla wątku. Aby się uczyć o wielu wątkach znajdziesz Omówienie procesów i wątków.
  • Unikaj długich operacji w obszarze onCreate(). Odrocz zadania inicjowania do momentu, gdy będą faktycznie potrzebne. Sekcja dotycząca implementowania metody onCreate(). omawiamy tę kwestię bardziej szczegółowo.
  • Mimo że musisz wdrożyć te metody, Twój kod nie musi robić nic oprócz zwraca oczekiwany typ danych. Można na przykład uniemożliwić innym aplikacjom do wstawiania danych do niektórych tabel przez ignorowanie wywołania funkcji insert() i powracanie 0.

Zaimplementuj metodę query()

Metoda ContentProvider.query() musi zwracać obiekt Cursor lub, jeśli niepowodzenie, zostanie zwrócony Exception. Jeśli używasz bazy danych SQLite jako danych możesz zwrócić Cursor zwrócone przez jeden z Metody query() klasy SQLiteDatabase.

Jeśli zapytanie nie pasuje do żadnego wiersza, zwraca wartość Cursor instancję, której metoda getCount() zwraca 0. Zwróć wartość null tylko wtedy, gdy podczas procesu zapytania wystąpił błąd wewnętrzny.

Jeśli do przechowywania danych nie używasz bazy danych SQLite, użyj jednej z konkretnych podklas z Cursor. Na przykład klasa MatrixCursor implementuje kursor, w którym każdy wiersz jest tablicą instancji Object. Na tych zajęciach użyj wiersza addRow(), aby dodać nowy wiersz.

System Android musi mieć możliwość komunikacji z Exception mogą przekraczać granice procesów. Android może to zrobić w przypadku tych przydatnych wyjątków obsługi błędów zapytań:

Wdrażanie metody insert()

Metoda insert() dodaje element nowy wiersz do odpowiedniej tabeli, korzystając z wartości z kolumny ContentValues . Jeśli nazwy kolumny nie ma w argumencie ContentValues, zostanie możesz podać dla niego wartość domyślną w kodzie dostawcy lub w bazie danych schemat.

Ta metoda zwraca identyfikator URI treści w nowym wierszu. Aby to zrobić, dołącz nowy element klucza podstawowego wiersza, zwykle wartości _ID, do identyfikatora URI treści tabeli przy użyciu operatora withAppendedId()

Zaimplementuj metodę delete()

Metoda delete() nie musi usuwać wierszy ze swojej pamięci. Jeśli korzystasz z adaptera synchronizacji z dostawcą, rozważ oznaczenie usuniętego wiersza ze słowem „delete” zamiast całkowicie usuwać wiersz. Adapter synchronizacji może sprawdzić, czy są usunięte wiersze, i usunąć je z serwera, zanim skasujesz je od dostawcy.

Wdrażanie metody update()

Metoda update() przyjmuje ten sam argument ContentValues, który jest używany przez funkcję insert() oraz te same argumenty selection i selectionArgs używane przez delete() i ContentProvider.query() Może to umożliwić ponowne użycie kodu między tymi metodami.

Wdrażanie metody onCreate()

System Android wywołuje funkcję onCreate() po uruchomieniu dostawcy. Inicjuj tylko szybko do wykonywania zadań w tej metodzie oraz opóźniania utworzenia bazy danych i wczytywania danych do momentu, otrzyma żądanie tych danych. Jeśli wykonujesz długie zadania na onCreate(), zwolnisz start-upu. To z kolei spowalnia odpowiedź między dostawcą aplikacji.

Te 2 fragmenty kodu pokazują interakcję między ContentProvider.onCreate() i Room.databaseBuilder(). Pierwszy pokazuje implementację ContentProvider.onCreate(), gdzie zostanie utworzony obiekt bazy danych, a uchwyty dla obiektów dostępu do danych –

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Wdrażanie typów MIME komponentu ContentProvider

Klasa ContentProvider ma 2 metody zwracania typów MIME:

getType()
Jedna z wymaganych metod, które implementujesz w przypadku dowolnego dostawcy.
getStreamTypes()
Metoda, którą należy wdrożyć, jeśli dostawca oferuje pliki.

Typy MIME tabel

Metoda getType() zwraca błąd String w formacie MIME, który opisuje typ danych zwracanych przez treść Argument identyfikatora URI. Argument Uri może być wzorem, a nie określonym identyfikatorem URI. W takim przypadku zwracany jest typ danych powiązany z identyfikatorami URI treści pasującymi do zapytania. wzorcem.

W przypadku typowych typów danych, takich jak tekst, HTML czy JPEG, getType() zwraca wartość standardową Typ MIME tych danych. Pełna lista tych standardowych typów jest dostępna w Typy mediów MIME IANA witryny.

W przypadku identyfikatorów URI treści wskazujących wiersz lub wiersze danych w tabeli getType() za możliwość zwrotu typ MIME w specyficznym dla dostawcy formacie MIME Androida:

  • Część typu: vnd
  • Część podtypu:
    • Jeśli wzorzec identyfikatora URI dotyczy pojedynczego wiersza: android.cursor.item/
    • Jeśli wzorzec identyfikatora URI dotyczy więcej niż jednego wiersza: android.cursor.dir/
  • Część u dostawcy: vnd.<name>.<type>

    Ty dostarczasz <name> i <type>. Wartość <name> jest unikalna globalnie, a wartość <type> jest unikalna dla odpowiedniego identyfikatora URI wzorcem. Jeśli chodzi o <name>, warto podać nazwę firmy lub część nazwy pakietu aplikacji na Androida. Dobry wybór dla <type> to ciąg znaków identyfikujący tabelę powiązaną z Identyfikator URI.

Jeśli na przykład ustanowienie organu com.example.app.provider, aby udostępnić tabelę o nazwie table1, typ MIME dla wielu wierszy w tabeli table1 to:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Typ MIME pojedynczego wiersza wartości table1 to:

vnd.android.cursor.item/vnd.com.example.provider.table1

Typy MIME plików

Jeśli dostawca oferuje pliki, getStreamTypes() Metoda zwraca tablicę String typów MIME dla plików dostawcy. może zwrócić określony identyfikator URI treści. Filtruj oferowane typy MIME według typu MIME , dzięki czemu zwracane będą tylko typy MIME, które klient chce obsługiwać.

Weźmy na przykład dostawcę, który oferuje zdjęcia w formie plików w formacie JPG, PNG i GIF. Jeśli aplikacja wywołuje ContentResolver.getStreamTypes() z ciągiem filtra image/*, dla elementu, który jest „obrazem”, metoda ContentProvider.getStreamTypes() zwraca tablicę:

{ "image/jpeg", "image/png", "image/gif"}

Jeśli aplikacja potrzebuje tylko plików JPG, może zadzwonić ContentResolver.getStreamTypes() z ciągiem filtra *\/jpeg, getStreamTypes() zwraca:

{"image/jpeg"}

Jeśli Twój dostawca nie oferuje żadnego z typów MIME wymaganych w ciągu filtra, getStreamTypes() zwraca null.

Wdróż klasę umowy

Klasa umowy to klasa public final, która zawiera stałe definicje obiektu Identyfikatory URI, nazwy kolumn, typy MIME i inne metadane dotyczące dostawcy. Klasa zawiera umowę między dostawcą a innymi aplikacjami, upewniając się, że dostawca można uzyskać poprawnie nawet wtedy, gdy zmienią się rzeczywiste wartości identyfikatorów URI, nazwy kolumn, i tak dalej.

Klasa kontraktowa pomaga również programistom, ponieważ zwykle ma mnemotechniczne nazwy stałych, więc programiści mają mniejsze szanse na używanie przez programistów nieprawidłowych wartości dla nazw kolumn i identyfikatorów URI. Jest to , może zawierać dokumentację Javadoc. Zintegrowane środowiska programistyczne, takie jak Android Studio może automatycznie uzupełniać nazwy stałe z klasy umowy i wyświetlać zasoby Javadoc dla: stałe.

Deweloperzy nie mają dostępu do pliku klasy umowy z poziomu Twojej aplikacji, ale mogą statycznie skompilować go do swojej aplikacji na podstawie dostarczonego przez Ciebie pliku JAR.

Klasa ContactsContract i jej zagnieżdżone klasy to przykłady: klas kontraktowych.

Wdrażanie uprawnień dostawcy treści

Uprawnienia i dostęp do wszystkich aspektów systemu Android zostały szczegółowo opisane w Wskazówki dotyczące bezpieczeństwa Zobacz też Omówienie miejsca na dane i plików opisuje zabezpieczenia i uprawnienia obowiązujące w przypadku różnych typów pamięci masowej. Oto najważniejsze kwestie:

  • Domyślnie pliki danych przechowywane w pamięci wewnętrznej urządzenia są prywatne aplikacji i dostawcy.
  • SQLiteDatabase utworzone przez Ciebie bazy danych są prywatne w aplikacji i dostawcy.
  • Domyślnie pliki danych zapisywane w pamięci zewnętrznej są publiczne oraz czytelne na całym świecie. Nie możesz korzystać z usług dostawcy treści, aby ograniczyć dostęp do plików w pamięci zewnętrznej, ponieważ inne aplikacje mogą używać innych wywołań interfejsu API do ich odczytu i zapisu.
  • Metoda wymaga otwierania lub tworzenia plików lub baz danych SQLite w wewnętrznej pamięci urządzenia może zapewnić wszystkim innym aplikacjom dostęp zarówno do odczytu, jak i do zapisu. Jeśli użyj wewnętrznego pliku lub bazy danych jako repozytorium dostawcy i dodasz go „czytelny na całym świecie” czy „możliwe do zapisu na całym świecie” uprawnienia ustawione dla dostawcy w jego plik manifestu nie chroni Twoich danych. Domyślny dostęp do plików i baz danych w pamięć wewnętrzna jest „prywatna”, nie zmieniaj tego w repozytorium dostawcy.

Jeśli chcesz kontrolować dostęp do swoich danych za pomocą uprawnień dostawcy treści: przechowywać dane w plikach wewnętrznych, bazach danych SQLite lub w chmurze, na serwerze zdalnym oraz zachowaj prywatność plików i baz danych w aplikacji.

Wdróż uprawnienia

Domyślnie wszystkie aplikacje mogą odczytywać dane dostawcy lub do niego zapisywać, nawet jeśli dane bazowe są prywatne, ponieważ domyślnie Twój dostawca nie ma ustawionych uprawnień. Aby to zmienić: ustaw uprawnienia dostawcy w pliku manifestu za pomocą atrybutów lub elementów podrzędnych elementów elementu <provider>. Możesz ustawić uprawnienia dotyczące całego dostawcy, do określonych tabel, do konkretnych rekordów lub do wszystkich trzech.

Uprawnienia dla dostawcy określasz za pomocą jednego lub kilku <permission> elementów w pliku manifestu. Aby unikalne dla Twojego dostawcy, użyj określania zakresu w stylu Java dla android:name. Możesz na przykład nazwać uprawnienie do odczytu com.example.app.provider.permission.READ_PROVIDER

Poniższa lista opisuje zakres uprawnień dostawcy, zaczynając od uprawnień, które dotyczą całego dostawcy, a potem są coraz bardziej szczegółowe. Uprawnienia o większej szczegółowości mają pierwszeństwo przed uprawnieniami o szerszym zakresie.

Pojedyncze uprawnienie na poziomie dostawcy do odczytu i zapisu
Jedno uprawnienie, które kontroluje dostęp z uprawnieniami do odczytu i zapisu do całego dostawcy, określony z atrybutem android:permission atrybutu <provider>.
Oddzielne uprawnienia na poziomie dostawcy do odczytu i zapisu
Uprawnienia do odczytu i zapisu dla całego dostawcy. Ty je określasz android:readPermission i android:writePermission atrybutów <provider>. Mają one pierwszeństwo przed uprawnieniami wymaganymi przez android:permission
Uprawnienia na poziomie ścieżki
Uprawnienia do odczytu, zapisu i odczytu/zapisu identyfikatora URI treści u dostawcy. Ty określasz dla każdego identyfikatora URI, który chcesz kontrolować, za pomocą <path-permission> element podrzędny tagu <provider>. Dla każdego podanego identyfikatora URI treści możesz podać uprawnienia do odczytu i zapisu, do odczytu, zapisu lub wszystkie 3 opcje. Sekcja Odczyt i uprawnienia do zapisu mają pierwszeństwo przed uprawnieniami do odczytu/zapisu. Oprócz tego na poziomie ścieżki mają pierwszeństwo przed uprawnieniami na poziomie dostawcy.
Uprawnienia tymczasowe
Poziom uprawnień przyznający tymczasowy dostęp do aplikacji, nawet jeśli to aplikacja nie ma uprawnień, które są zwykle wymagane. Tymczasowy funkcja dostępu zmniejsza liczbę uprawnień, o które aplikacja prosi i jej pliku manifestu. Gdy włączysz uprawnienia tymczasowe, jedyne aplikacje, które potrzebują tych uprawnień, stałe uprawnienia dostępu to te, które mają stały dostęp do wszystkich danych.

Weź pod uwagę na przykład wymagane uprawnienia, jeśli wdrażasz dostawcę poczty e-mail i aplikację, chcesz zezwolić zewnętrznej aplikacji przeglądarki obrazów na wyświetlanie załączników zdjęć z dostawcy usług. Aby przyznać przeglądarce obrazów niezbędny dostęp bez konieczności uzyskania zezwoleń, możesz ustawić tymczasowe uprawnienia dotyczące identyfikatorów URI zawartości dla zdjęć.

Zaprojektuj aplikację do poczty e-mail tak że gdy użytkownik chce wyświetlić zdjęcie, aplikacja wysyła intencję zawierającą identyfikator URI zawartości zdjęcia i flagi uprawnień do przeglądarki obrazów. Przeglądarka obrazów może i wysłać do dostawcy poczty e-mail prośbę o pobranie zdjęcia, mimo że przeglądający mają zwykłe uprawnienia do odczytu u Twojego dostawcy.

Aby włączyć uprawnienia tymczasowe, ustaw android:grantUriPermissions atrybutu <provider> lub dodaj co najmniej 1 element <grant-uri-permission> elementów podrzędnych do <provider>. Zadzwoń do nas Context.revokeUriPermission() za każdym razem, gdy usuniesz obsługę identyfikatora URI treści powiązanego z tymczasowym uprawnieniem z Twojego konta dostawcy usług.

Wartość atrybutu określa, w jakim stopniu informacje od dostawcy są dostępne. Jeśli atrybut ma wartość "true", system przyznaje tymczasowo wszystkie uprawnienia dostawcy, zastępując wszystkie wymagane uprawnienia. zgodnie z uprawnieniami na poziomie dostawcy lub ścieżki.

Jeśli ta flaga jest ustawiona na "false", dodaj <grant-uri-permission> elementów podrzędnych do <provider>. Każdy element podrzędny określa identyfikator URI treści lub Identyfikatory URI, którym przyznano dostęp tymczasowy.

Aby można było przekazać aplikacji tymczasowy dostęp, intencja musi zawierać flaga FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION albo obie. Te są ustawiane za pomocą metody setFlags().

Jeśli atrybut android:grantUriPermissions nie istnieje, przyjmuje się, że jest to atrybut "false"

<provider> element

Podobnie jak komponenty Activity i Service, podklasa klasy ContentProvider jest zdefiniowane w pliku manifestu aplikacji za pomocą <provider>. System Android pobiera następujące informacje z element:

Podmiot autoryzujący (android:authorities)
Nazwy symboliczne, które identyfikują całego dostawcę w systemie. Ten został opisany bardziej szczegółowo w Sekcja Identyfikatory URI treści projektu.
Nazwa klasy dostawcy (android:name)
Klasa, która implementuje ContentProvider. Te zajęcia są omówiono to szczegółowo w Zaimplementuj sekcję klasy ContentProvider.
Uprawnienia
Atrybuty określające uprawnienia, które inne aplikacje muszą mieć, aby uzyskać dostęp do usługi dane dostawcy:

Uprawnienia i odpowiadające im atrybuty zostały opisane w dalszej części artykułu w sekcji Implementowanie uprawnień dostawcy treści.

Atrybuty uruchamiania i sterowania
Te atrybuty określają, jak i kiedy system Android uruchamia dostawcę, charakterystyka procesu dostawcy i inne ustawienia środowiska wykonawczego:
  • android:enabled: flaga umożliwiająca systemowi uruchomienie dostawcy
  • android:exported: flaga umożliwiająca innym aplikacjom korzystanie z tego dostawcy
  • android:initOrder: kolejność uruchamiania dostawcy, w porównaniu z innymi dostawcami uczestniczącymi w tym samym procesie
  • android:multiProcess: flaga umożliwiająca systemowi uruchomienie dostawcy w tym samym procesie co klient wywołujący
  • android:process: nazwa procesu, w którym działa dostawca
  • android:syncable: flaga wskazująca, że dane dostawcy mają zostać synchronizacja z danymi na serwerze

Pełna dokumentacja tych atrybutów znajduje się w przewodniku <provider> element.

Atrybuty informacyjne
Opcjonalna ikona i etykieta dostawcy:
  • android:icon: rysowalny zasób zawierający ikonę dostawcy. Ikona jest wyświetlana obok etykiety dostawcy na liście aplikacji w Ustawienia > Aplikacje > Wszystkie
  • android:label: etykieta informacyjna opisująca dostawcę, jego lub oba te problemy. Etykieta jest widoczna na liście aplikacji w Ustawienia > Aplikacje > Wszystkie

Pełna dokumentacja tych atrybutów znajduje się w przewodniku <provider> element.

Uwaga: jeśli kierujesz aplikację na Androida 11 lub nowszego, zapoznaj się z artykułem dokumentacja dotycząca widoczności pakietów w celach związanych z konfiguracją.

Intencje i dostęp do danych

Aplikacje mogą uzyskiwać dostęp do dostawcy treści pośrednio za pomocą Intent. Aplikacja nie wywołuje żadnej z metod obiektu ContentResolver lub ContentProvider Zamiast tego wysyła intencję, która rozpoczyna działanie, co często jest częścią aplikacji dostawcy. Aktywność w miejscu docelowym odpowiada za: pobieranie i wyświetlanie danych w interfejsie użytkownika.

W zależności od działania intencji aktywność w miejscu docelowym może też prosić użytkownika o wprowadzenie zmian w danych dostawcy. Intencja może też zawierać słowa „extras” dane wyświetlane w ramach aktywności docelowej w interfejsie. Użytkownik może potem zmienić te dane, zanim użyje ich do zmodyfikowania u dostawcy.

Dostęp do intencji pomaga w integralności danych. To, z usług którego dostawcy korzystasz, może zależeć wstawianie, aktualizowanie i usuwanie danych zgodnie ze ściśle zdefiniowaną logiką biznesową. Jeśli w tej sytuacji, zezwolenie innym aplikacjom na bezpośrednie modyfikowanie danych może nieprawidłowych danych.

Jeśli chcesz, aby deweloperzy korzystali z dostępu do intencji, dokładnie udokumentuj to działanie. Wyjaśnij, dlaczego dostęp do intencji za pomocą interfejsu aplikacji jest lepszy niż próba modyfikować dane za pomocą kodu.

Obsługa intencji przychodzącej, która chce zmodyfikować dane dostawcy, niczym się nie różni obsługi innych intencji. Więcej informacji o używaniu intencji znajdziesz w artykule Filtry intencji i zamiarów.

Dodatkowe powiązane informacje znajdziesz tutaj: Omówienie dostawcy kalendarza