Podstawowe informacje o dostawcy treści

Dostawca treści zarządza dostępem do centralnego repozytorium danych. Usługodawca jest częścią aplikacji na Androida, która często ma własny interfejs danych. Dostawcy treści są jednak głównie wykorzystywani aplikacji uzyskujących dostęp do dostawcy za pomocą obiektu klienckiego dostawcy. Dostawcy wspólnie i klientów dostawcy oferują spójny, standardowy interfejs do danych, który komunikację między procesami i bezpieczny dostęp do danych.

Zazwyczaj współpracujemy z dostawcami treści w jednym z dwóch scenariuszy: kod dostępu do istniejącego dostawcy treści w innej aplikacji lub tworzenie nowego dostawcy treści w aplikacji, który umożliwia udostępnianie danych innym aplikacjom.

Ta strona obejmuje podstawy współpracy z dostawcami treści. Aby dowiedzieć się więcej na temat wdrażania dostawców treści w Twoich własnych aplikacjach, zobacz Utwórz dostawcę treści.

W tym temacie opisano następujące zagadnienia:

  • Jak działają dostawcy treści
  • Interfejs API, z którego korzystasz do pobierania danych od dostawcy treści.
  • Interfejs API służący do wstawiania, aktualizowania i usuwania danych u dostawcy treści.
  • Inne funkcje interfejsu API, które ułatwiają pracę z dostawcami.

Omówienie

Dostawca treści prezentuje dane aplikacjom zewnętrznym w formie jednej lub kilku tabel, jak tabele w relacyjnej bazie danych. Wiersz reprezentuje instancję jakiegoś typu danych zbieranych przez dostawcę, a każda kolumna w wierszu odpowiada jednemu fragmentowi danych dla danej instancji.

Dostawca treści koordynuje dostęp do warstwy przechowywania danych w aplikacji dla wiele różnych interfejsów API i komponentów. Jak pokazano na rys. 1, są to następujące elementy:

  • Przyznawanie dostępu do danych aplikacji innym aplikacjom
  • Wysyłanie danych do widżetu
  • Zwracanie niestandardowych sugestii wyszukiwania dla Twojej aplikacji podczas wyszukiwania platforma wykorzystująca SearchRecentSuggestionsProvider
  • Synchronizowanie danych aplikacji z serwerem przy użyciu implementacji AbstractThreadedSyncAdapter
  • Wczytywanie danych w interfejsie za pomocą interfejsu CursorLoader
Relacja między dostawcą treści a innymi komponentami.

Rysunek 1. Relacja między dostawcą treści a innymi komponentami.

Dostęp do dostawcy

Aby uzyskać dostęp do danych dostawcy treści, użyj ContentResolver obiekt w aplikacji Context, aby komunikować się z dostawcą jako klient. Obiekt ContentResolver komunikuje się z obiektem dostawcy, instancji klasy, która implementuje ContentProvider.

Usługodawca odbiera żądania danych od klientów, wykonuje żądane działanie i zwraca wyników. Ten obiekt zawiera metody, które wywołują metody o identycznych nazwach w obiekcie dostawcy, wystąpienie jednej z konkretnych podklasy klasy ContentProvider. Metody ContentResolver zapewniają podstawowe „CRUD” (tworzenie, pobieranie, aktualizowanie i usuwanie) pamięci trwałej.

Typowym wzorcem uzyskiwania dostępu do aplikacji ContentProvider z poziomu interfejsu użytkownika jest CursorLoader, aby uruchomić zapytanie asynchroniczne w tle. Pole Activity lub Fragment w interfejsie wywołuje w interfejsie CursorLoader do zapytania, które z kolei otrzyma ContentProvider za pomocą: ContentResolver.

Dzięki temu interfejs użytkownika będzie nadal dostępny dla użytkownika podczas wykonywania zapytania. Ten obejmują interakcję kilku różnych obiektów, jak na ilustracji 2.

Interakcja między komponentem ContentProvider, innymi klasami i miejscem na dane.

Rysunek 2. Interakcja między usługą ContentProvider, innymi klasami i miejscem na dane.

Uwaga: aby uzyskać dostęp do dostawcy, aplikacja zwykle musi poprosić o określenie uprawnień w pliku manifestu. Ten wzorzec programowania opisuje się szczegółowo w Uprawnienia dostawcy treści.

Jednym z wbudowanych dostawców platformy Androida jest dostawca słownika użytkownika, przechowuje niestandardowe słowa, które użytkownik chce zachować. Tabela 1 ilustruje, dane mogą wyglądać tak:

Tabela 1. Przykładowa tabela słownika użytkownika.

słowne identyfikator aplikacji publikowania region _ID
mapreduce użytkownik1 100 pl_PL 1
precompiler użytkownik14 200 fr_fr 2
applet użytkownik2 225 fr_CA 3
const użytkownik1 255 pt_BR 4
int użytkownik5 100 pl_PL 5

W tabeli 1 każdy wiersz reprezentuje wystąpienie słowa niebędącego które można znaleźć w słowniku standardowym. Każda kolumna reprezentuje fragment danych dla tego słowa, taki jak i regionie, w którym został po raz pierwszy napotkany. Nagłówki kolumn to nazwy kolumn przechowywane w z usługodawcą. Aby na przykład odwołać się do regionu wiersza, możesz użyć kolumny locale. Dla: tego dostawcy, kolumna _ID służy jako kolumna klucza podstawowego, przez dostawcę.

Aby uzyskać listę słów i ich języków od dostawcy słownika użytkownika, Dzwonisz do: ContentResolver.query(). Metoda query() wywołuje metodę ContentProvider.query() zdefiniowaną przez Dostawca słownika użytkownika. Poniższe wiersze kodu pokazują ContentResolver.query() – połączenie:

Kotlin

// Queries the UserDictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

Java

// Queries the UserDictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

Tabela 2 pokazuje, jak argumenty do query(Uri,projection,selection,selectionArgs,sortOrder) pasuje do instrukcji SQL SELECT:

Tabela 2. Porównanie wartości query() z zapytaniem SQL.

query() argument SELECT słowo kluczowe/parametr Uwagi
Uri FROM table_name Uri jest mapowany na tabelę u dostawcy o nazwie table_name.
projection col,col,col,... projection to tablica kolumn uwzględniona w każdym wierszu – pobrano.
selection WHERE col = value selection określa kryteria wyboru wierszy.
selectionArgs Brak dokładnego odpowiednika. Argumenty wyboru zastępują obiekty zastępcze ? w elemencie klauzuli wyboru.
sortOrder ORDER BY col,col,... sortOrder określa kolejność, w jakiej wiersze pojawiają się w zwróconym wyniku Cursor

Identyfikatory URI treści

Identyfikator URI treści to identyfikator URI identyfikujący dane u dostawcy. Identyfikatory URI treści zawierają symboliczną nazwę całego dostawcy (jego organu), a także element nazwa wskazująca tabelę – ścieżka. Gdy dzwonisz metody klienta, aby uzyskać dostęp do tabeli u dostawcy, identyfikator URI treści tabeli jest jednym z argumentów.

W poprzednich wierszach kodu stała CONTENT_URI zawiera identyfikator URI treści: tabeli Words dostawcy słownika użytkownika. ContentResolver analizuje autorytet identyfikatora URI i używa go do rozwiązania dostawcy przez porównując urzędy z tabelą systemową znanych dostawców. ContentResolver może następnie wysłać argumenty zapytania na właściwą wartość dostawcy usług.

ContentProvider używa części ścieżki identyfikatora URI treści, aby wybrać tabeli. Dostawca ma zwykle ścieżkę dla każdej udostępnianej tabeli.

Pełny identyfikator URI tabeli Words w poprzednich wierszach kodu to:

content://user_dictionary/words
  • Ciąg znaków content:// to schemat, który jest zawsze obecny i identyfikuje go jako identyfikator URI treści.
  • Ciąg tekstowy user_dictionary to uprawnienia dostawcy.
  • Ciąg znaków words to ścieżka tabeli.

Wielu dostawców umożliwia dostęp do pojedynczego wiersza tabeli dzięki dodaniu wartości identyfikatora na końcu identyfikatora URI. Aby na przykład pobrać wiersz, w którym _ID to 4 ze strony dostawcy słownika użytkownika, możesz użyć tego identyfikatora URI treści:

Kotlin

val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)

Java

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

Wartości identyfikatorów są często używane, gdy pobierasz zbiór wierszy, a następnie chcesz zaktualizować lub usunąć lub jednym z nich.

Uwaga: klasy Uri i Uri.Builder zawierają wygodne metody tworzenia poprawnie sformułowanych obiektów URI na podstawie ciągów znaków. Klasa ContentUris zawiera wygodne metody dołączania wartości identyfikatorów do identyfikator URI. Poprzedni fragment kodu używa withAppendedId(), aby dodać identyfikator do identyfikatora URI treści dostawcy słownika użytkownika.

Pobieranie danych od dostawcy

Ta sekcja opisuje, jak pobrać dane od dostawcy za pomocą dostawcy słownika użytkowników .

Dla jasności: fragmenty kodu w tej sekcji ContentResolver.query() w wątku UI. W ale wykonywać asynchroniczne zapytania w osobnym wątku. Dostępne opcje użyj klasy CursorLoader, która została opisana bardziej szczegółowo w Przewodnika. Pamiętaj też, że wiersze kodu są tylko fragmentami. Nie wyświetlają pełnego, aplikacji.

Aby pobrać dane od dostawcy, wykonaj te podstawowe czynności:

  1. Poproś o uprawnienia do odczytu dla dostawcy.
  2. Określ kod, który wysyła zapytanie do dostawcy.

Prośba o uprawnienia do odczytu

Aby pobierać dane od dostawcy, aplikacja musi mieć uprawnienia do odczytu dla dostawcy usług. Nie możesz o nie prosić w czasie działania aplikacji. Zamiast tego musisz określić, musisz mieć te uprawnienia w pliku manifestu za pomocą <uses-permission> i dokładną nazwę uprawnienia zdefiniowaną przez dostawcy usług.

Określając ten element w pliku manifestu, żądasz tego uprawnienia aplikacji. Gdy użytkownicy instalują Twoją aplikację, domyślnie przyznają Ci tę prośbę.

Aby znaleźć dokładną nazwę uprawnienia dostępu do odczytu dla używanego dostawcy jako nazwy innych uprawnień dostępu używanych przez dostawcę, poszukaj w sekcji dokumentacji.

Rola uprawnień dostępu do dostawców została szczegółowo opisana w Uprawnienia dostawcy treści.

Dostawca słownika użytkownika definiuje uprawnienie android.permission.READ_USER_DICTIONARY w pliku manifestu, więc aplikacja, która chce odczytywać dane od dostawcy, musi poprosić o to uprawnienie.

Utwórz zapytanie

Następnym krokiem w procesie pobierania danych od dostawcy jest utworzenie zapytania. Ten fragment kodu definiuje pewne zmienne umożliwiające dostęp do dostawcy słownika użytkownika:

Kotlin

// A "projection" defines the columns that are returned for each row
private val mProjection: Array<String> = arrayOf(
        UserDictionary.Words._ID,    // Contract class constant for the _ID column name
        UserDictionary.Words.WORD,   // Contract class constant for the word column name
        UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
)

// Defines a string to contain the selection clause
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

Java

// A "projection" defines the columns that are returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// Initializes an array to contain selection arguments
String[] selectionArgs = {""};

Z następnego fragmentu dowiesz się, jak używać ContentResolver.query(), korzystając ze słownika użytkownika Dostawca. Zapytanie klienta dostawcy jest podobne do zapytania SQL i zawiera zestaw kolumn do zwrócenia, zestaw kryteriów wyboru oraz kolejność sortowania.

Zbiór kolumn zwracanych przez zapytanie jest nazywany odwzorowaniem. zmienna ma wartość mProjection.

Wyrażenie określające wiersze do pobrania jest podzielone na klauzulę wyboru i argumentów wyboru. Klauzula wyboru to połączenie wyrażeń logicznych i logicznych, nazwy kolumn i wartości. Zmienna to mSelectionClause. Jeśli określisz parametr wymienny parametr ? zamiast wartości, metoda zapytania pobiera wartość z tablicy argumentów wyboru, która jest zmienną mSelectionArgs.

Jeśli w następnym fragmencie użytkownik nie wpisze słowa, zostanie ustawiona klauzula wyboru null, a zapytanie zwróci wszystkie słowa dostawcy. Jeśli użytkownik wpisze słowo, klauzula wyboru jest ustawiona na UserDictionary.Words.WORD + " = ?" i pierwszy element tablicy z argumentami wyboru jest ustawiony na słowo wpisane przez użytkownika.

Kotlin

/*
 * This declares a String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI, // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null or the word the user entered
        selectionArgs,                    // Either empty or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You might want to call android.util.Log.e() to log this error.
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search is unsuccessful. This isn't
         * necessarily an error. You might want to offer the user the option to insert a new
         * row, or re-type the search term.
         */
    }
    else -> {
        // Insert code here to do something with the results
    }
}

Java

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] selectionArgs = {""};

// Gets a word from the UI
searchString = searchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(searchString)) {
    // Setting the selection clause to null returns all words
    selectionClause = null;
    selectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments
    selectionArgs[0] = searchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI, // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null or the word the user entered
    selectionArgs,                    // Either empty or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You can
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily
     * an error. You can offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

To zapytanie jest podobne do tej instrukcji SQL:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

W tej instrukcji SQL zamiast stałych klas kontraktu używane są rzeczywiste nazwy kolumn.

Ochrona przed złośliwymi danymi wejściowymi

Jeśli dane zarządzane przez dostawcę treści znajdują się w bazie danych SQL, w tym danych zewnętrznych niezaufanych do nieprzetworzonych instrukcji SQL może prowadzić do wstrzyknięcia kodu SQL.

Rozważ następującą klauzulę wyboru:

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

Java

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause = "var = " + userInput;

Jeśli to zrobisz, umożliwisz użytkownikowi łączenie złośliwego kodu SQL z instrukcją SQL. Użytkownik może na przykład wpisać „nothing; USUŃ TABELĘ *; dla kolumny mUserInput, która daje wynik klauzuli wyboru var = nothing; DROP TABLE *;.

Ponieważ klauzula wyboru jest traktowana jako instrukcja SQL, może to spowodować usunięcie wszystkich tabel w bazowej bazie danych SQLite, chyba że dostawca jest skonfigurowany do przechwytywania Próby wstrzykiwania SQL.

Aby uniknąć tego problemu, użyj klauzuli wyboru, w której parametr ? jest zmienny i osobnej tablicy argumentów wyboru. Dzięki temu dane wejściowe użytkownika jest bezpośrednio powiązana z zapytaniem, a nie interpretowana jako część instrukcji SQL. Dane wejściowe użytkownika nie są traktowane jako SQL, więc nie mogą wstrzykiwać złośliwego kodu SQL. Zamiast użycia konkatenacji, aby uwzględnić dane wejściowe użytkownika, użyj tej klauzuli wyboru:

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

Java

// Constructs a selection clause with a replaceable parameter
String selectionClause =  "var = ?";

Skonfiguruj tablicę argumentów wyboru w ten sposób:

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

Java

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

Umieść wartość w tablicy argumentów wyboru w ten sposób:

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

Java

// Sets the selection argument to the user's input
selectionArgs[0] = userInput;

Klauzula wyboru, w której parametr ? jest wymiennym parametrem i tablica tablica z argumentami wyboru to preferowany sposób określania wyboru, nawet jeśli dostawca w oparciu o bazę danych SQL.

Wyświetl wyniki zapytania

Metoda klienta ContentResolver.query() zawsze zwraca element Cursor zawierający kolumny określone przez funkcję zapytania prognozy dla wierszy spełniających kryteria wyboru zapytania. O Obiekt Cursor zapewnia losowy dostęp z możliwością odczytu do wierszy i kolumn zawiera.

Korzystając z metod Cursor, możesz iterować wiersze w określać typ danych w poszczególnych kolumnach, pobierać je z kolumn i sprawdzać inne właściwości wyników.

Niektóre implementacje Cursor automatycznie aktualizowanie obiektu w przypadku zmiany danych dostawcy, aktywowanie metod w obiekcie obserwatora gdy zmieni się Cursor lub oba.

Uwaga: dostawca może ograniczyć dostęp do kolumn na podstawie charakteru kolumny który tworzy zapytanie. Na przykład Dostawca kontaktów ogranicza dostęp do niektórych kolumn, aby adaptery synchronizacji, aby nie zwracały ich do aktywności lub usługi.

Jeśli żadne wiersze nie pasują do kryteriów wyboru, dostawca zwraca obiekt Cursor, dla którego Cursor.getCount() to 0 – czyli pusty kursor.

Jeśli wystąpi błąd wewnętrzny, wyniki zapytania zależą od konkretnego dostawcy. Może zwróci wartość null lub może zwrócić Exception.

Cursor jest listą wierszy, więc dobry sposób na wyświetlenie zawartość elementu Cursor to połączenie jej z elementem ListView za pomocą SimpleCursorAdapter.

Poniższy fragment stanowi kontynuację kodu z poprzedniego. Tworzy SimpleCursorAdapter obiekt zawierający obiekt Cursor pobrane przez zapytanie i ustawia ten obiekt jako adapter dla obiektu ListView

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        UserDictionary.Words.WORD,      // Contract class constant containing the word column name
        UserDictionary.Words.LOCALE     // Contract class constant containing the locale column name
)

// Defines a list of View IDs that receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

// Creates a new SimpleCursorAdapter
cursorAdapter = SimpleCursorAdapter(
        applicationContext,             // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        wordListColumns,                // A string array of column names in the cursor
        wordListItems,                  // An integer array of view IDs in the row layout
        0                               // Flags (usually none are needed)
)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

Java

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    wordListColumns,                       // A string array of column names in the cursor
    wordListItems,                         // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

Uwaga: aby wesprzeć ListView z Cursor, kursor musi zawierać kolumnę o nazwie _ID. Z tego powodu wyświetlone wcześniej zapytanie pobiera kolumnę _ID o wartości Words, mimo że ListView jej nie wyświetla. To ograniczenie wyjaśnia też, dlaczego większość dostawców ma kolumnę _ID dla każdego z ich tabel.

Pobieranie danych z wyników zapytania

Oprócz wyświetlania wyników zapytania możesz ich używać do innych zadań. Dla: możesz na przykład pobrać pisownię od dostawcy słownika użytkownika, a następnie wyszukać je z innymi usługami. Aby to zrobić, powtarzaj kolejne wiersze w tabeli Cursor, jak pokazano w tym przykładzie:

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers might throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column
        newWord = getString(index)

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
}

Java

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers might throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception
}

Implementacje typu Cursor zawierają kilka instrukcji „get” dla pobierania różnych typów danych z obiektu. Na przykład poprzedni fragment kodu używa getString(). Mają też Metoda getType(), która zwraca wartość wskazującą typ danych kolumny.

Zwolnij zasoby wyników zapytania

Cursor obiektu musi być są zamykane, jeśli nie są już potrzebne, dzięki czemu powiązane z nimi zasoby zostaną zwolnione szybciej. Można to zrobić, wywołując close() lub za pomocą funkcji instrukcja try-with-resources w języku programowania Java lub use() w języku programowania Kotlin.

Uprawnienia dostawcy treści

Aplikacja dostawcy może określać uprawnienia, które muszą mieć inne aplikacje uzyskać dostęp do danych dostawcy. Dzięki tym uprawnieniom użytkownik może dowiedzieć się, jakie dane aplikacja próbuje uzyskać dostęp. W zależności od wymagań dostawcy inne aplikacje poprosić o odpowiednie uprawnienia w celu uzyskania dostępu do dostawcy. Użytkownicy widzą żądane uprawnień podczas instalowania aplikacji.

Jeśli aplikacja dostawcy nie określa żadnych uprawnień, to inne aplikacje nie mają dostępu do danych dostawcy, chyba że dostawca zostanie wyeksportowany. Dodatkowo komponenty w aplikacji dostawcy zawsze mają pełne uprawnienia do odczytu i zapisu, niezależnie od określonych uprawnień.

Dostawca słownika użytkownika wymaga parametru android.permission.READ_USER_DICTIONARY, aby pobrać z niego dane. Dostawca ma oddzielne konto android.permission.WRITE_USER_DICTIONARY uprawnień do wstawiania, aktualizowania lub usuwania danych.

Aby uzyskać uprawnienia dostępu do dostawcy, aplikacja prosi go o <uses-permission> w swoim pliku manifestu. Gdy Menedżer pakietów Androida zainstaluje aplikację, użytkownik musi zatwierdzić wszystkie uprawnienia, o które prosi aplikacja. Jeśli użytkownik je zatwierdzi, Menedżer pakietów kontynuuje instalację. Jeśli użytkownik ich nie zatwierdzi, Menedżer pakietów spowoduje zatrzymanie instalacji.

Następująca próbka <uses-permission> element żąda uprawnień z możliwością odczytu do dostawcy słownika użytkownika:

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

Wpływ uprawnień na dostęp dostawcy został szczegółowo wyjaśniony w Wskazówki dotyczące bezpieczeństwa

Wstawianie, aktualizowanie i usuwanie danych

W ten sam sposób, w jaki pobierasz dane od dostawcy, korzystasz też z interakcji między klienta dostawcy i ContentProvider dostawcy w celu modyfikowania danych. Wywołujesz metodę ContentResolver za pomocą argumentów przekazywanych do odpowiednią metodę ContentProvider. Usługodawca i dostawca automatycznie obsługuje zabezpieczenia i komunikację między procesami.

Wstaw dane

Aby wstawić dane do dostawcy, wywołaj funkcję ContentResolver.insert() . Ta metoda wstawia nowy wiersz do dostawcy i zwraca dla niego identyfikator URI treści. Ten fragment kodu pokazuje, jak wstawić nowe słowo w polu dostawcy słownika użytkownika:

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri
...
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value".
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
        newValues                           // The values to insert
)

Java

// Defines a new Uri object that receives the result of the insertion
Uri newUri;
...
// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value".
 */
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
    newValues                           // The values to insert
);

Dane z nowego wiersza trafiają do pojedynczego obiektu ContentValues, który przypomina kursor jednowierszowy. Kolumny w tym obiekcie nie muszą zawierać tego samego typu danych, a jeśli w ogóle nie chcesz określać wartości, do: null za pomocą: ContentValues.putNull().

Poprzedni fragment kodu nie dodaje kolumny _ID, ponieważ ta kolumna jest zachowywana automatycznie. Dostawca przypisuje unikalną wartość _ID do każdego wiersza, który – dodano. Dostawcy zwykle używają tej wartości jako klucza podstawowego tabeli.

Identyfikator URI treści zwrócony w zasadzie newUri identyfikuje nowo dodany wiersz z oznaczeniem w tym formacie:

content://user_dictionary/words/<id_value>

<id_value> to zawartość nowego wiersza: _ID. Większość dostawców może automatycznie wykryć tę formę identyfikatora URI treści, a następnie wykonać żądane operacji na tym konkretnym wierszu.

Aby uzyskać wartość _ID ze zwróconych danych Uri, wywołaj ContentUris.parseId()

Zaktualizuj dane

Aby zaktualizować wiersz, użyj obiektu ContentValues ze zaktualizowanym tak samo jak w przypadku kryteriów wstawiania i wyboru, tak jak w przypadku zapytania. Metoda klienta, której używasz, to ContentResolver.update() Wystarczy dodać do obiektu ContentValues dla kolumn, które aktualizujesz. Jeśli Jeśli chcesz wyczyścić zawartość kolumny, ustaw wartość null.

Ten fragment kodu zmienia wszystkie wiersze, których język to "en", na ma ustawiony język null. Zwracana wartość to liczba zaktualizowanych wierszy.

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0
...
rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        updateValues,                      // The columns to update
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    updateValues,                      // The columns to update
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

Napraw dane wejściowe użytkownika podczas rozmowy ContentResolver.update() Aby dowiedzieć się więcej o: przeczytaj sekcję Ochrona przed złośliwymi danymi.

Usuń dane

Usuwanie wierszy przypomina pobieranie ich danych. Określasz kryteria wyboru wierszy do usunięcia, a metoda klienta zwróci liczbę usuniętych wierszy. Ten fragment kodu usuwa wiersze, których identyfikator aplikacji pasuje do "user". Metoda zwraca wartość liczbę usuniętych wierszy.

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0
...
// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;
...
// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

Napraw dane wejściowe użytkownika podczas rozmowy ContentResolver.delete() Aby dowiedzieć się więcej o: przeczytaj sekcję Ochrona przed złośliwymi danymi wejściowymi.

Typy danych dostawcy

Dostawcy treści mogą oferować wiele różnych typów danych. Dostawca słownika użytkownika ma tylko tekstu, ale dostawcy mogą też oferować te formaty:

  • Liczba całkowita
  • długa liczba całkowita (długi)
  • liczba zmiennoprzecinkowa
  • długa zmiennoprzecinkowa (liczba zmiennoprzecinkowa)

Innym często używanym przez dostawców typem danych jest duży obiekt binarny (BLOB) zaimplementowany jako Tablica bajtów o rozmiarze 64 KB. Dostępne typy danych znajdziesz w Cursor klasa „get” .

Typ danych dla każdej kolumny dostawcy jest zwykle wymieniony w jego dokumentacji. Typy danych dostawcy słownika użytkownika zostały wymienione w dokumentacji referencyjnej dla swojej klasy umowy: UserDictionary.Words. Klasy kontraktu to opisane w sekcji Klasy umów. Typ danych możesz też określić, wywołując funkcję Cursor.getType().

Dostawcy obsługują też informacje o typie danych MIME dla każdego zdefiniowanego przez siebie identyfikatora URI treści. Dostępne opcje korzysta z informacji o typie MIME, aby sprawdzić, czy aplikacja może obsłużyć dane lub wybrać typ obsługi na podstawie typu MIME. Zwykle potrzebujesz Typ MIME w przypadku współpracy z dostawcą, który zawiera złożone struktury danych lub pliki.

Na przykład ContactsContract.Data narzędzia Dostawca kontaktów używa typów MIME do oznaczania typów danych kontaktów przechowywanych w każdym . Aby uzyskać typ MIME odpowiadający identyfikatorowi URI treści, wywołaj ContentResolver.getType()

Sekcja Informacje o typach MIME zawiera opis zarówno standardowych, jak i niestandardowych typów MIME.

Alternatywne formy dostępu dostawcy

Przy tworzeniu aplikacji ważne są 3 alternatywne formy dostępu dostawcy:

Dostęp wsadowy i modyfikowanie za pomocą intencji zostały opisane w dalszej części tego artykułu.

Dostęp grupowy

Dostęp wsadowy do dostawcy jest przydatny przy wstawianiem dużej liczby wierszy, np. wiersze w wielu tabelach w tym samym wywołaniu metody i ogólnie do wykonywania zbioru funkcji to transakcja między granicami procesów, czyli operacja niepodzielna.

Aby uzyskać dostęp do dostawcy w trybie wsadowym, utwórz tablicę obiektów ContentProviderOperation, a następnie wysłać je do dostawcy treści, ContentResolver.applyBatch() Musisz zdać autorytet dostawcy treści, a nie konkretnego identyfikatora URI treści.

Dzięki temu każdy obiekt ContentProviderOperation w tablicy będzie działać względem innej tabeli. Wywołanie ContentResolver.applyBatch() zwraca tablicę wyników.

Opis klasy umowy ContactsContract.RawContacts zawiera fragment kodu, który pokazuje wsadowe wstawianie reklam.

Dostęp do danych za pomocą intencji

Intencje mogą zapewniać pośredni dostęp do dostawcy treści. Możesz zezwolić użytkownikowi na dostęp nawet jeśli aplikacja nie ma uprawnień dostępu ze względu na: pobrania intencji wynikowej z aplikacji, która ma uprawnienia, lub przez aktywację to aplikacja, która ma uprawnienia i pozwala użytkownikowi na wykonywanie w niej pracy.

Uzyskiwanie dostępu za pomocą uprawnień tymczasowych

Możesz uzyskać dostęp do danych dostawcy treści, nawet jeśli nie masz odpowiednich uprawnień przez wysyłanie intencji do aplikacji, która ma uprawnienia odbierając intencję wynikową zawierającą uprawnienia do identyfikatora URI. Są to uprawnienia dotyczące określonego identyfikatora URI treści, ważne do momentu, gdy działanie odbiera skończą pracę. Aplikacja, która ma trwałe uprawnienia, przyznaje tymczasowo przez ustawienie flagi w intencji wyniku:

Uwaga: te flagi nie zapewniają dostawcy ogólnych uprawnień do odczytu ani zapisu. którego uprawnienia są zawarte w identyfikatorze URI treści. Dostęp dotyczy tylko identyfikatora URI.

Wysyłając identyfikatory URI treści do innej aplikacji, podaj przynajmniej jeden z tych elementów flagami. Flagi zapewniają poniższe możliwości każdej aplikacji, która otrzymuje są kierowane na Androida 11 (poziom interfejsu API 30) lub nowszego:

  • odczyt i zapis danych reprezentowanych przez identyfikator URI treści; w zależności od flagi w intencji.
  • Przejmij pakiet widocznośćw aplikacji zawierającej dostawcę treści pasującego do Uprawnienia dla identyfikatora URI. Aplikacja wysyłająca intencję i aplikacja, która zawiera dostawcę treści, mogą to być 2 różne aplikacje.

Dostawca definiuje uprawnienia URI dla identyfikatorów URI treści w swoim manifeście za pomocą android:grantUriPermissions atrybutu <provider> , a także <grant-uri-permission> element potomny tagu <provider> . Mechanizm uprawnień do identyfikatora URI został szczegółowo opisany w Przewodnik po uprawnieniach na Androidzie.

Możesz na przykład pobrać dane kontaktu z usługi Dostawca kontaktów, nawet jeśli masz uprawnienie READ_CONTACTS. Możesz zrobić w aplikacji wysyłającej powitanie elektroniczne do kontaktu w dniu urodzin. Zamiast prosi o READ_CONTACTS, co daje Ci dostęp do wszystkich kontaktów użytkownika i wszystkich jego informacji, pozwalają mu kontrolować, kontaktów używanych przez aplikację. Aby to zrobić, wykonaj te czynności:

  1. W aplikacji wyślij intencję zawierającą działanie ACTION_PICK i „kontakty” Typ MIME CONTENT_ITEM_TYPE, przy użyciu metody metoda startActivityForResult().
  2. Ponieważ intencja ta pasuje do filtra intencji dla „Wybór” aplikacji Osoby aktywność wyświetla się na pierwszym planie.
  3. W działaniu zaznaczania użytkownik wybiera kontakt , aby zaktualizować. W takim przypadku aktywność wyboru wywołuje setResult(resultcode, intent) skonfigurować zamiar odwrócenia aplikacji. Intencja zawiera identyfikator URI treści wybranego kontaktu i „dodatkowych elementów”. flagi FLAG_GRANT_READ_URI_PERMISSION Te flagi przyznają identyfikator URI aplikacji otrzymują uprawnienia do odczytu danych kontaktu wskazywanego przez identyfikator URI treści. Aktywność wyboru wywołuje następnie finish(), aby aby zwrócić element sterujący do aplikacji.
  4. Aktywność wraca na pierwszy plan, a system wywołuje metodę onActivityResult() . Ta metoda otrzymuje intencję wynikową utworzoną przez aktywność związaną z wyborem w aplikacji Osoby.
  5. Za pomocą identyfikatora URI treści z intencji wyniku możesz odczytać dane kontaktu od dostawcy kontaktów, chociaż nie wysłano prośby o stałe uprawnienia do odczytu z dostawcą w pliku manifestu. Możesz uzyskać informacje o urodzinach kontaktu. lub adres e-mail, a następnie wyślij e-powitanie.

Użyj innej aplikacji

Innym sposobem zezwolenia użytkownikowi na modyfikowanie danych, do których nie masz uprawnień dostępu, jest aktywować aplikację, która ma uprawnienia, i pozwolić użytkownikowi wykonać w niej pracę.

Na przykład aplikacja Kalendarz akceptuje intencję ACTION_INSERT, która pozwala aktywować interfejsu wstawiania aplikacji. Można przekazywać elementy dodatkowe dane w tej intencji, do których aplikacja który wykorzystuje do wstępnego wypełnienia interfejsu. Wydarzenia cykliczne mają złożoną składnię, dlatego preferowane jest wstawienie wydarzeń do dostawcy kalendarza to aktywowanie aplikacji Kalendarz za pomocą ACTION_INSERT, a następnie zezwól użytkownikowi na wstawienie w niej zdarzenia.

Wyświetlanie danych za pomocą aplikacji pomocniczej

Jeśli aplikacja ma uprawnienia dostępu, możesz nadal używać do wyświetlania danych w innej aplikacji. Na przykład aplikacja Kalendarz akceptuje intencja ACTION_VIEW, która wyświetla konkretną datę lub zdarzenie. Dzięki temu możesz wyświetlać informacje z kalendarza bez konieczności tworzenia własnego interfejsu użytkownika. Więcej informacji o tej funkcji znajdziesz w Omówienie dostawcy kalendarza

Aplikacja, do której wysyłasz intencję, nie musi być aplikacją powiązane z dostawcą. Możesz na przykład pobrać kontakt z Skontaktuj się z dostawcą, a następnie wyślij intencję ACTION_VIEW zawierający identyfikator URI treści obrazu kontaktu w przeglądarce obrazów.

Klasy kontraktowe

Klasa umowy definiuje stałe, które ułatwiają aplikacjom korzystanie z identyfikatorów URI treści, kolumny nazwy, działania intencji i inne cechy dostawcy treści. Klasy kontraktu nie są automatycznie dołączane do dostawcy. Programista dostawcy musi je zdefiniować, a następnie udostępniać je innym programistom. Wielu dostawców oprogramowania zainstalowanego na Androidzie platforma ma odpowiednie klasy kontraktu w pakiecie android.provider.

Na przykład dostawca słownika użytkownika ma klasę umowy Pole UserDictionary zawiera stałe identyfikatory URI treści i nazwy kolumn. identyfikator URI treści w tabeli Words jest zdefiniowany w stałej wartości UserDictionary.Words.CONTENT_URI Klasa UserDictionary.Words zawiera też stałe nazwy kolumny, wykorzystane w przykładowych fragmentach w tym przewodniku. Na przykład rzutowanie zapytania może być zdefiniowane w następujący sposób:

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

Java

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

Inną klasą umowy w przypadku dostawcy kontaktów jest ContactsContract. Dokumentacja referencyjna dotycząca tych zajęć zawiera przykładowe fragmenty kodu. Jedną z podklasy (ContactsContract.Intents.Insert) to kontrakt , która zawiera stałe dla intencji i danych intencji.

Informacje o typach MIME

Dostawcy treści mogą zwracać standardowe typy mediów MIME, ciągi niestandardowe typu MIME lub oba te typy.

Typy MIME:

type/subtype

Na przykład znany typ MIME text/html ma typ text i podtyp html. Jeśli dostawca zwraca ten typ dla identyfikatora URI, oznacza to, że zwraca tekst zawierający tagi HTML.

Niestandardowe typy MIME, nazywane też typami MIME poszczególnych dostawców, mają więcej zespolonych wartości type i subtype. W przypadku wielu wierszy wartość typu jest zawsze taka:

vnd.android.cursor.dir

W przypadku pojedynczego wiersza wartość typu jest zawsze następująca:

vnd.android.cursor.item

Identyfikator subtype zależy od dostawcy. Wbudowani dostawcy Androida zwykle mają prosty podtyp. Jeśli na przykład aplikacja Kontakty tworzy wiersz dla numeru telefonu, ustawia w wierszu następujący typ MIME:

vnd.android.cursor.item/phone_v2

Wartość podtypu to phone_v2.

Inni deweloperzy mogą tworzyć własne wzorce podtypów na podstawie nazw urzędów i tabel. Załóżmy na przykład, że dostawca udostępnia rozkłady jazdy pociągów. Uprawnienia dostawcy to com.example.trains i zawiera on tabele Line1, Line2 i Line3. W odpowiedzi na ten identyfikator URI treści dla wiersza 1 tabeli:

content://com.example.trains/Line1

dostawca zwraca ten typ MIME:

vnd.android.cursor.dir/vnd.example.line1

W odpowiedzi na ten identyfikator URI treści w wierszu 5 w tabeli Line2:

content://com.example.trains/Line2/5

dostawca zwraca ten typ MIME:

vnd.android.cursor.item/vnd.example.line2

Większość dostawców treści definiuje stałe klasy umowy dla używanych typów MIME. Klasa umowy dostawcy kontaktów: ContactsContract.RawContacts, Na przykład definiuje stałą CONTENT_ITEM_TYPE dla typu MIME: pojedynczy wiersz nieprzetworzonego kontaktu.

Identyfikatory URI treści w pojedynczych wierszach są opisane w Identyfikatory URI treści.