Contentanbieter – Grundlagen

Ein Contentanbieter verwaltet den Zugriff auf ein zentrales Datenverzeichnis. Ein Anbieter ist Teil einer Android-Anwendung, die oft eine eigene UI zum Arbeiten mit den Daten bereitstellt. Sie werden jedoch hauptsächlich von anderen Anwendungen verwendet, die über ein Clientobjekt des Anbieters auf den Anbieter zugreifen. Gemeinsam bieten Anbieter und Anbieterclients eine einheitliche, Standardschnittstelle für Daten, die auch die Kommunikation zwischen Prozessen und den sicheren Datenzugriff übernimmt.

Normalerweise arbeiten Sie mit Contentanbietern in einem von zwei Szenarien zusammen: Implementieren von Code für den Zugriff auf einen vorhandenen Contentanbieter in einer anderen Anwendung oder Erstellen eines neuen Contentanbieters in Ihrer App, um Daten für andere Anwendungen freizugeben.

Auf dieser Seite werden die Grundlagen der Zusammenarbeit mit vorhandenen Contentanbietern erläutert. Informationen zur Implementierung von Contentanbietern in Ihren eigenen Anwendungen finden Sie unter Contentanbieter erstellen.

In diesem Thema wird Folgendes beschrieben:

  • Funktionsweise von Contentanbietern
  • Die API, mit der Sie Daten von einem Contentanbieter abrufen.
  • Die API, mit der Sie Daten bei einem Contentanbieter einfügen, aktualisieren oder löschen.
  • Weitere API-Funktionen, die die Zusammenarbeit mit Anbietern erleichtern.

Übersicht

Ein Contentanbieter stellt Daten externen Anwendungen als eine oder mehrere Tabellen bereit, die den Tabellen in einer relationalen Datenbank ähneln. Eine Zeile stellt eine Instanz eines Datentyps dar, der vom Anbieter erfasst wird, und jede Spalte in der Zeile steht für ein einzelnes Datenelement, das für eine Instanz erfasst wird.

Ein Contentanbieter koordiniert den Zugriff auf die Datenspeicherebene in Ihrer Anwendung für eine Reihe verschiedener APIs und Komponenten. Wie in Abbildung 1 dargestellt, umfassen diese Folgendes:

  • Zugriff auf Ihre Anwendungsdaten für andere Apps freigeben
  • Daten an ein Widget senden
  • Benutzerdefinierte Suchvorschläge für Ihre Anwendung über das Such-Framework mit SearchRecentSuggestionsProvider zurückgeben
  • Anwendungsdaten mithilfe einer Implementierung von AbstractThreadedSyncAdapter mit Ihrem Server synchronisieren
  • Daten in Ihre UI mit einem CursorLoader laden
Beziehung zwischen Contentanbieter und anderen Komponenten.

Abbildung 1: Beziehung zwischen einem Contentanbieter und anderen Komponenten.

Auf einen Anbieter zugreifen

Wenn Sie auf Daten in einem Contentanbieter zugreifen möchten, verwenden Sie das Objekt ContentResolver in der Context Ihrer Anwendung, um mit dem Anbieter als Client zu kommunizieren. Das Objekt ContentResolver kommuniziert mit dem Anbieterobjekt, einer Instanz einer Klasse, die ContentProvider implementiert.

Das Anbieterobjekt empfängt Datenanfragen von Clients, führt die angeforderte Aktion aus und gibt die Ergebnisse zurück. Dieses Objekt verfügt über Methoden, die identisch benannte Methoden im Provider-Objekt aufrufen, einer Instanz einer der konkreten Unterklassen von ContentProvider. Die Methoden ContentResolver bieten die grundlegenden CRUD-Funktionen zum Erstellen, Abrufen, Aktualisieren und Löschen von nichtflüchtigen Speichern.

Ein gängiges Muster für den Zugriff auf ContentProvider über die UI verwendet CursorLoader, um eine asynchrone Abfrage im Hintergrund auszuführen. Das Activity oder Fragment in Ihrer UI ruft eine CursorLoader für die Abfrage auf, die wiederum die ContentProvider mithilfe der ContentResolver abruft.

So steht die Benutzeroberfläche dem Nutzer weiterhin zur Verfügung, während die Abfrage ausgeführt wird. Dieses Muster beinhaltet die Interaktion verschiedener Objekte sowie den zugrunde liegenden Speichermechanismus, wie in Abbildung 2 dargestellt.

Interaktion zwischen ContentProvider, anderen Klassen und Speicher.

Abbildung 2: Interaktion zwischen ContentProvider, anderen Klassen und Speicher.

Hinweis:Für den Zugriff auf einen Anbieter muss Ihre App in der Regel bestimmte Berechtigungen in der Manifestdatei anfordern. Dieses Entwicklungsmuster wird im Abschnitt Berechtigungen für Contentanbieter ausführlicher beschrieben.

Einer der integrierten Anbieter der Android-Plattform ist der User Dictionary Provider, der die nicht standardisierten Wörter speichert, die der Nutzer behalten möchte. Tabelle 1 zeigt, wie die Daten in der Tabelle dieses Anbieters aussehen könnten:

Tabelle 1:Beispieltabelle mit einem Nutzerwörterbuch.

Wortspiele App-ID Beiträgen Sprache _ID
mapreduce nutzer1 100 de_DE 1
precompiler Nutzer 14 200 fr_FR 2
applet Nutzer 2 225 fr_CA 3
const nutzer1 255 pt_BR 4
int Nutzer 5 100 de_DE 5

In Tabelle 1 stellt jede Zeile eine Instanz eines Wortes dar, das in einem Standardwörterbuch nicht gefunden wird. Jede Spalte repräsentiert ein Datenelement für das Wort, z. B. die Sprache, in der es zuerst gefunden wurde. Die Spaltenüberschriften sind Spaltennamen, die im Anbieter gespeichert sind. Um auf die Sprache einer Zeile zu verweisen, verweisen Sie also beispielsweise auf die Spalte locale. Bei diesem Anbieter dient die Spalte _ID als Primärschlüsselspalte, die der Anbieter automatisch verwaltet.

Wenn du eine Liste der Wörter und ihrer Sprachen vom Nutzerwörterbuchanbieter abrufen möchtest, rufe ContentResolver.query() auf. Die Methode query() ruft die Methode ContentProvider.query() auf, die vom Nutzerwörterbuchanbieter definiert wurde. Die folgenden Codezeilen zeigen einen ContentResolver.query()-Aufruf:

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

Tabelle 2 zeigt, wie die Argumente für query(Uri,projection,selection,selectionArgs,sortOrder) mit einer SQL-SELECT-Anweisung übereinstimmen:

Tabelle 2: query() im Vergleich zur SQL-Abfrage.

query()-Argument Keyword/Parameter SELECT Hinweise
Uri FROM table_name Uri ist der Tabelle im Anbieter mit dem Namen table_name zugeordnet.
projection col,col,col,... projection ist ein Spaltenarray, das für jede abgerufene Zeile enthalten ist.
selection WHERE col = value selection gibt die Kriterien für die Zeilenauswahl an.
selectionArgs Keine genaue Entsprechung. Auswahlargumente ersetzen ?-Platzhalter in der Auswahlklausel.
sortOrder ORDER BY col,col,... sortOrder gibt die Reihenfolge an, in der Zeilen in der zurückgegebenen Cursor angezeigt werden.

Inhalts-URIs

Ein Inhalts-URI ist ein URI, der Daten bei einem Anbieter identifiziert. Inhalts-URIs umfassen den symbolischen Namen des gesamten Anbieters – seine Zertifizierungsstelle – und einen Namen, der auf eine Tabelle verweist, einen Pfad. Wenn Sie eine Clientmethode aufrufen, um auf eine Tabelle bei einem Anbieter zuzugreifen, ist der Inhalts-URI für die Tabelle eines der Argumente.

In den vorangehenden Codezeilen enthält die Konstante CONTENT_URI den Inhalts-URI der Tabelle Words des Nutzerwörterbuchanbieters. Das Objekt ContentResolver parst die Autorisierung des URI und verwendet sie, um den Anbieter zu auflösen. Dazu wird die Autorisierung mit einer Systemtabelle bekannter Anbieter verglichen. Der ContentResolver kann dann die Abfrageargumente an den richtigen Anbieter weiterleiten.

ContentProvider verwendet den Pfadteil des Inhalts-URI, um die Tabelle auszuwählen, auf die zugegriffen werden soll. Ein Anbieter hat normalerweise einen Pfad für jede bereitgestellte Tabelle.

In den vorherigen Codezeilen lautet der vollständige URI für die Tabelle Words:

content://user_dictionary/words
  • Der String content:// ist das Schema, das immer vorhanden ist und dieses als Inhalts-URI identifiziert.
  • Der String user_dictionary ist die Autorisierung des Anbieters.
  • Der String words ist der Pfad der Tabelle.

Bei vielen Anbietern können Sie auf eine einzelne Zeile in einer Tabelle zugreifen, indem Sie einen ID-Wert am Ende des URI anhängen. Wenn Sie beispielsweise eine Zeile mit dem _ID-Wert 4 aus dem Nutzerwörterbuchanbieter abrufen möchten, können Sie den folgenden Inhalts-URI verwenden:

Kotlin

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

Java

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

ID-Werte werden häufig verwendet, wenn Sie einen Satz von Zeilen abrufen und dann eine davon aktualisieren oder löschen möchten.

Hinweis:Die Klassen Uri und Uri.Builder enthalten praktische Methoden zum Erstellen richtig formatierter URI-Objekte aus Strings. Die Klasse ContentUris enthält praktische Methoden zum Anhängen von ID-Werten an einen URI. Im vorherigen Snippet wird withAppendedId() verwendet, um eine ID an den Inhalts-URI des Nutzerwörterbuchanbieters anzufügen.

Daten vom Anbieter abrufen

In diesem Abschnitt wird beschrieben, wie du Daten von einem Anbieter abrufst. Als Beispiel wird der „User Dictionary Provider“ verwendet.

Zum besseren Verständnis rufen die Code-Snippets in diesem Abschnitt ContentResolver.query() im UI-Thread auf. Im eigentlichen Code werden Abfragen jedoch asynchron in einem separaten Thread ausgeführt. Sie können die Klasse CursorLoader verwenden, die in der Anleitung zu Ladeprogrammen ausführlicher beschrieben wird. Außerdem handelt es sich bei den Codezeilen nur um Snippets. Es wird keine vollständige Anwendung angezeigt.

So rufen Sie Daten von einem Anbieter ab:

  1. Fordern Sie eine Lesezugriff-Berechtigung für den Anbieter an.
  2. Definieren Sie den Code, der eine Anfrage an den Anbieter sendet.

Lesezugriff anfordern

Zum Abrufen von Daten von einem Anbieter benötigt Ihre Anwendung eine Lesezugriff-Berechtigung für den Anbieter. Sie können diese Berechtigung nicht zur Laufzeit anfordern. Stattdessen müssen Sie diese Berechtigung in Ihrem Manifest mit dem Element <uses-permission> und dem genauen vom Anbieter definierten Berechtigungsnamen angeben.

Wenn Sie dieses Element in Ihrem Manifest angeben, fordern Sie diese Berechtigung für Ihre App an. Wenn Nutzer Ihre Anwendung installieren, erteilen sie diese Anfrage implizit.

Den genauen Namen der Lesezugriffsberechtigung für den von Ihnen verwendeten Anbieter sowie die Namen anderer vom Anbieter verwendeten Zugriffsberechtigungen finden Sie in der Dokumentation des Anbieters.

Die Rolle von Berechtigungen beim Zugriff auf Anbieter wird im Abschnitt Contentanbieterberechtigungen ausführlicher beschrieben.

Der User Dictionary Provider definiert die Berechtigung android.permission.READ_USER_DICTIONARY in seiner Manifestdatei. Daher muss eine App, die Daten vom Anbieter lesen möchte, diese Berechtigung anfordern.

Abfrage erstellen

Der nächste Schritt beim Abrufen von Daten von einem Anbieter besteht darin, eine Abfrage zu erstellen. Im folgenden Snippet werden einige Variablen für den Zugriff auf den User Dictionary Provider definiert:

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 = {""};

Das nächste Snippet zeigt die Verwendung von ContentResolver.query() am Beispiel des User Dictionary Provider. Eine Anbieter-Client-Abfrage ähnelt einer SQL-Abfrage und enthält eine Reihe von zurückzugebenden Spalten, eine Reihe von Auswahlkriterien und eine Sortierreihenfolge.

Die Gruppe von Spalten, die die Abfrage zurückgibt, wird als Projektion bezeichnet. Die Variable ist mProjection.

Der Ausdruck, der die abzurufenden Zeilen angibt, wird in eine Auswahlklausel und Auswahlargumente aufgeteilt. Die Auswahlklausel ist eine Kombination aus logischen und booleschen Ausdrücken, Spaltennamen und Werten. Die Variable lautet mSelectionClause. Wenn Sie den austauschbaren Parameter ? anstelle eines Werts angeben, ruft die Abfragemethode den Wert aus dem Array der Auswahlargumente ab. Dabei handelt es sich um die Variable mSelectionArgs.

Wenn der Nutzer im nächsten Snippet kein Wort eingibt, wird die Auswahlklausel auf null gesetzt und die Abfrage gibt alle Wörter des Anbieters zurück. Wenn der Nutzer ein Wort eingibt, wird die Auswahlklausel auf UserDictionary.Words.WORD + " = ?" gesetzt und das erste Element des Arrays mit Auswahlargumenten auf das vom Nutzer eingegebene Wort festgelegt.

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

}

Diese Abfrage entspricht der folgenden SQL-Anweisung:

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

In dieser SQL-Anweisung werden anstelle von Vertragsklassenkonstanten die tatsächlichen Spaltennamen verwendet.

Vor böswilliger Eingabe schützen

Wenn sich die vom Contentanbieter verwalteten Daten in einer SQL-Datenbank befinden, kann das Einbinden externer, nicht vertrauenswürdiger Daten in rohe SQL-Anweisungen zu SQL-Injections führen.

Betrachten Sie die folgende Auswahlklausel:

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;

In diesem Fall kann der Benutzer möglicherweise schädliche SQL-Anweisungen mit Ihrer SQL-Anweisung verketten. Beispielsweise kann der Nutzer für mUserInput "nothing; DROP TABLE *;" eingeben, was zur Auswahlklausel var = nothing; DROP TABLE *; führt.

Da die Auswahlklausel als SQL-Anweisung behandelt wird, löscht der Anbieter möglicherweise alle Tabellen in der zugrunde liegenden SQLite-Datenbank, es sei denn, er ist so eingerichtet, dass Versuche SQL-Injection abgefangen werden.

Um dieses Problem zu vermeiden, verwenden Sie eine Auswahlklausel, die ? als austauschbaren Parameter verwendet, und ein separates Array mit Auswahlargumenten. Auf diese Weise ist die Nutzereingabe direkt an die Abfrage gebunden und wird nicht als Teil einer SQL-Anweisung interpretiert. Da sie nicht als SQL behandelt wird, kann die Nutzereingabe kein schädliches SQL einschleusen. Anstelle der Verkettung, um die Nutzereingabe einzubeziehen, verwenden Sie diese Auswahlklausel:

Kotlin

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

Java

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

Richten Sie das Array der Auswahlargumente wie folgt ein:

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 = {""};

Geben Sie einen Wert in das Array mit Auswahlargumenten wie folgt ein:

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;

Eine Auswahlklausel, die ? als austauschbaren Parameter und ein Array mit Auswahlargumenten verwendet, ist die bevorzugte Methode, um eine Auswahl anzugeben, auch wenn der Anbieter nicht auf einer SQL-Datenbank basiert.

Abfrageergebnisse anzeigen

Die Clientmethode ContentResolver.query() gibt immer einen Cursor mit den Spalten zurück, die von der Projektion der Abfrage für die Zeilen angegeben wurden, die den Auswahlkriterien der Abfrage entsprechen. Ein Cursor-Objekt bietet zufälligen Lesezugriff auf die darin enthaltenen Zeilen und Spalten.

Mit Cursor-Methoden können Sie über die Zeilen in den Ergebnissen iterieren, den Datentyp der einzelnen Spalten ermitteln, die Daten aus einer Spalte abrufen und andere Eigenschaften der Ergebnisse untersuchen.

Bei einigen Cursor-Implementierungen wird das Objekt automatisch aktualisiert, wenn sich die Daten des Anbieters ändern. Außerdem werden Methoden in einem Beobachterobjekt ausgelöst, wenn sich Cursor ändert, oder beides.

Hinweis:Ein Anbieter kann den Zugriff auf Spalten je nach Art des Objekts einschränken, von dem die Abfrage durchgeführt wird. Der Contacts Provider schränkt beispielsweise den Zugriff für einige Spalten zur Synchronisierung von Adaptern ein, sodass sie nicht an eine Aktivität oder einen Dienst zurückgegeben werden.

Wenn keine Zeilen den Auswahlkriterien entsprechen, gibt der Anbieter ein Cursor-Objekt zurück, für das Cursor.getCount() 0 ist, also einen leeren Cursor.

Wenn ein interner Fehler auftritt, hängen die Ergebnisse der Abfrage vom jeweiligen Anbieter ab. Sie kann null zurückgeben oder ein Exception auslösen.

Da eine Cursor eine Liste von Zeilen ist, ist es eine gute Möglichkeit, den Inhalt einer Cursor anzuzeigen, indem Sie sie mithilfe eines SimpleCursorAdapter mit einem ListView verknüpfen.

Das folgende Snippet setzt den Code aus dem vorherigen Snippet fort. Es wird ein SimpleCursorAdapter-Objekt erstellt, das den von der Abfrage abgerufenen Cursor enthält, und legt dieses Objekt als Adapter für einen ListView fest.

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);

Hinweis: Wenn ein ListView mit einem Cursor gesichert werden soll, muss der Cursor eine Spalte mit dem Namen _ID enthalten. Aus diesem Grund ruft die zuvor gezeigte Abfrage die Spalte _ID für die Tabelle Words ab, obwohl sie in ListView nicht angezeigt wird. Diese Einschränkung erklärt auch, warum die meisten Anbieter für jede ihrer Tabellen eine _ID-Spalte haben.

Daten aus Abfrageergebnissen abrufen

Sie können sie nicht nur für die Anzeige von Abfrageergebnissen, sondern auch für andere Aufgaben verwenden. Du kannst beispielsweise Schreibweisen vom Nutzerwörterbuchanbieter abrufen und dann bei anderen Anbietern nachschlagen. Dazu iterieren Sie über die Zeilen im Cursor, wie im folgenden Beispiel gezeigt:

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
}

Cursor-Implementierungen enthalten mehrere get-Methoden zum Abrufen verschiedener Datentypen aus dem Objekt. Das vorherige Snippet verwendet beispielsweise getString(). Sie verfügen auch über die Methode getType(), die einen Wert zurückgibt, der den Datentyp der Spalte angibt.

Contentanbieterberechtigungen

Die Anwendung eines Anbieters kann Berechtigungen festlegen, die andere Anwendungen haben müssen, um auf die Daten des Anbieters zuzugreifen. Mit diesen Berechtigungen wird dem Nutzer mitgeteilt, auf welche Daten eine Anwendung zugreifen möchte. Je nach Anforderungen des Anbieters fordern andere Anwendungen die Berechtigungen an, die sie für den Zugriff auf den Anbieter benötigen. Endnutzer sehen die angeforderten Berechtigungen, wenn sie die Anwendung installieren.

Wenn in der Anwendung eines Anbieters keine Berechtigungen angegeben sind, haben andere Anwendungen keinen Zugriff auf die Daten des Anbieters, es sei denn, der Anbieter wird exportiert. Außerdem haben Komponenten in der Anwendung des Anbieters unabhängig von den angegebenen Berechtigungen immer vollständigen Lese- und Schreibzugriff.

Der Anbieter des Wörterbuchs benötigt die Berechtigung android.permission.READ_USER_DICTIONARY, um Daten abzurufen. Der Anbieter hat eine separate android.permission.WRITE_USER_DICTIONARY-Berechtigung zum Einfügen, Aktualisieren oder Löschen von Daten.

Die für den Zugriff auf einen Anbieter erforderlichen Berechtigungen werden von einer App mit einem <uses-permission>-Element in der Manifestdatei angefordert. Wenn der Android-Paketmanager die App installiert, muss der Nutzer alle Berechtigungen genehmigen, die von der Anwendung angefordert werden. Wenn der Nutzer sie genehmigt, fährt der Paketmanager die Installation fort. Wenn der Nutzer sie nicht genehmigt, stoppt der Paketmanager die Installation.

Mit dem folgenden Beispielelement <uses-permission> wird Lesezugriff beim User Dictionary Provider angefordert:

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

Die Auswirkungen von Berechtigungen auf den Anbieterzugriff werden in den Sicherheitstipps ausführlicher erläutert.

Daten einfügen, aktualisieren und löschen

Auf die gleiche Weise wie Sie Daten von einem Anbieter abrufen, verwenden Sie auch die Interaktion zwischen einem Anbieterclient und der ContentProvider des Anbieters, um Daten zu ändern. Sie rufen eine Methode von ContentResolver mit Argumenten auf, die an die entsprechende Methode von ContentProvider übergeben werden. Der Anbieter und der Anbieterclient übernehmen automatisch die Sicherheits- und Interprozesskommunikation.

Daten einfügen

Wenn Sie Daten in einen Anbieter einfügen möchten, rufen Sie die Methode ContentResolver.insert() auf. Mit dieser Methode wird eine neue Zeile in den Anbieter eingefügt und ein Inhalts-URI für diese Zeile zurückgegeben. Das folgende Snippet zeigt, wie ein neues Wort in den Nutzerwörterbuchanbieter eingefügt wird:

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
);

Die Daten für die neue Zeile werden in ein einzelnes ContentValues-Objekt eingegeben, dessen Form einem einzeiligen Cursor ähnelt. Die Spalten in diesem Objekt müssen nicht denselben Datentyp haben. Wenn Sie überhaupt keinen Wert angeben möchten, können Sie eine Spalte mit ContentValues.putNull() auf null setzen.

Im vorherigen Snippet wird die Spalte _ID nicht hinzugefügt, da diese Spalte automatisch aktualisiert wird. Der Anbieter weist jeder hinzugefügten Zeile einen eindeutigen Wert von _ID zu. Anbieter verwenden diesen Wert in der Regel als Primärschlüssel der Tabelle.

Der in newUri zurückgegebene Inhalts-URI identifiziert die neu hinzugefügte Zeile im folgenden Format:

content://user_dictionary/words/<id_value>

<id_value> ist der Inhalt von _ID für die neue Zeile. Die meisten Anbieter können diese Art von Inhalts-URI automatisch erkennen und dann den angeforderten Vorgang für diese bestimmte Zeile ausführen.

Rufen Sie ContentUris.parseId() auf, um den Wert von _ID aus der zurückgegebenen Uri abzurufen.

Daten aktualisieren

Um eine Zeile zu aktualisieren, verwenden Sie wie bei einer Abfrage ein ContentValues-Objekt mit den aktualisierten Werten. Sie verwenden die Clientmethode ContentResolver.update(). Sie müssen dem Objekt ContentValues nur für Spalten, die Sie aktualisieren, Werte hinzufügen. Wenn Sie den Inhalt einer Spalte löschen möchten, setzen Sie den Wert auf null.

Mit dem folgenden Snippet werden alle Zeilen mit der Sprache "en" in die Sprache null geändert. Der Rückgabewert ist die Anzahl der aktualisierten Zeilen.

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
);

Bereinigen Sie die Nutzereingabe beim Aufrufen von ContentResolver.update(). Weitere Informationen dazu finden Sie im Abschnitt Schutz vor schädlicher Eingabe.

Daten löschen

Das Löschen von Zeilen ähnelt dem Abrufen von Zeilendaten. Sie geben Auswahlkriterien für die Zeilen an, die Sie löschen möchten, und die Clientmethode gibt die Anzahl der gelöschten Zeilen zurück. Mit dem folgenden Snippet werden Zeilen gelöscht, deren App-ID mit "user" übereinstimmt. Die Methode gibt die Anzahl der gelöschten Zeilen zurück.

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
);

Bereinigen Sie die Nutzereingabe beim Aufrufen von ContentResolver.delete(). Weitere Informationen dazu finden Sie im Abschnitt Vor böswilliger Eingabe schützen.

Datentypen des Anbieters

Contentanbieter können viele verschiedene Datentypen anbieten. Der „User Dictionary Provider“ bietet nur Text, kann aber auch die folgenden Formate nutzen:

  • Ganzzahl
  • Long Integer (long)
  • Gleitkommawert
  • langer Gleitkommawert (doppelt)

Ein weiterer Datentyp, der von Anbietern häufig verwendet wird, ist ein BLOB (Binary Large Object), das als 64-KB-Byte-Array implementiert wird. Die verfügbaren Datentypen finden Sie in den „get“-Methoden der Cursor-Klasse.

Der Datentyp für jede Spalte bei einem Anbieter ist normalerweise in der zugehörigen Dokumentation aufgeführt. Die Datentypen für den User Dictionary Provider sind in der Referenzdokumentation für die zugehörige Vertragsklasse UserDictionary.Words aufgeführt. Vertragsklassen werden im Abschnitt Vertragsklassen beschrieben. Sie können den Datentyp auch durch Aufrufen von Cursor.getType() bestimmen.

Anbieter speichern außerdem MIME-Datentypinformationen für jeden von ihnen definierten Inhalts-URI. Anhand der MIME-Typ-Informationen können Sie herausfinden, ob Ihre Anwendung die vom Anbieter angebotenen Daten verarbeiten kann, oder anhand des MIME-Typs eine Art der Verarbeitung auswählen. In der Regel benötigen Sie den MIME-Typ, wenn Sie mit einem Anbieter zusammenarbeiten, der komplexe Datenstrukturen oder Dateien enthält.

Die Tabelle ContactsContract.Data im Kontakteanbieter verwendet beispielsweise MIME-Typen, um dem Typ der Kontaktdaten, die in den einzelnen Zeilen gespeichert sind, mit einem Label zu versehen. Rufen Sie ContentResolver.getType() auf, um den MIME-Typ für einen Inhalts-URI abzurufen.

Im Abschnitt MIME-Typ-Referenz wird die Syntax sowohl von standardmäßigen als auch von benutzerdefinierten MIME-Typen beschrieben.

Alternative Formen des Anbieterzugriffs

Drei alternative Formen des Anbieterzugriffs sind bei der Anwendungsentwicklung wichtig:

Der Batchzugriff und die Änderung mithilfe von Intents werden in den folgenden Abschnitten beschrieben.

Batchzugriff

Der Batchzugriff auf einen Anbieter ist nützlich, um eine große Anzahl von Zeilen einzufügen, Zeilen in mehrere Tabellen in denselben Methodenaufruf einzufügen und im Allgemeinen eine Reihe von Vorgängen über Prozessgrenzen hinweg als Transaktion auszuführen, die als atomarer Vorgang bezeichnet wird.

Wenn Sie im Batchmodus auf einen Anbieter zugreifen möchten, erstellen Sie ein Array mit ContentProviderOperation-Objekten und leiten Sie sie dann an einen Contentanbieter mit ContentResolver.applyBatch() weiter. Sie übergeben die Autorität des Contentanbieters an diese Methode und nicht an einen bestimmten Inhalts-URI.

Dadurch kann jedes ContentProviderOperation-Objekt im Array mit einer anderen Tabelle arbeiten. Bei einem Aufruf von ContentResolver.applyBatch() wird ein Array von Ergebnissen zurückgegeben.

Die Beschreibung der Vertragsklasse ContactsContract.RawContacts enthält ein Code-Snippet, das das Batch-Einfügung veranschaulicht.

Datenzugriff mithilfe von Intents

Intents können indirekten Zugriff auf einen Contentanbieter bieten. Sie können dem Nutzer auch dann Zugriff auf Daten bei einem Anbieter gewähren, wenn Ihre Anwendung keine Zugriffsberechtigungen hat. Dazu können Sie entweder einen Ergebnis-Intent von einer Anwendung mit Berechtigungen abrufen oder eine Anwendung mit Berechtigungen aktivieren und dem Nutzer die Arbeit in dieser Anwendung zu ermöglichen.

Zugriff mit temporären Berechtigungen erhalten

Auch wenn Sie nicht die erforderlichen Zugriffsberechtigungen haben, können Sie auf Daten eines Contentanbieters zugreifen. Dazu senden Sie einen Intent an eine Anwendung, die über die entsprechenden Berechtigungen verfügt, und erhalten einen Ergebnis-Intent mit URI-Berechtigungen zurück. Dies sind Berechtigungen für einen bestimmten Inhalts-URI, die so lange gelten, bis die Aktivität, die sie empfängt, abgeschlossen ist. Die Anwendung mit dauerhaften Berechtigungen gewährt temporäre Berechtigungen, indem sie im Ergebnis-Intent ein Flag festlegt:

Hinweis:Diese Flags gewähren dem Anbieter, dessen Berechtigung im Inhalts-URI enthalten ist, keinen allgemeinen Lese- oder Schreibzugriff. Der Zugriff gilt nur für den URI selbst.

Wenn Sie Inhalts-URIs an eine andere App senden, fügen Sie mindestens eines dieser Flags hinzu. Die Flags bieten für jede App, die einen Intent empfängt und auf Android 11 (API-Level 30) oder höher ausgerichtet ist, die folgenden Funktionen:

  • Aus den vom Inhalts-URI repräsentierten Daten lesen oder in diese schreiben, je nachdem, welches Flag im Intent enthalten ist
  • Sie erhalten Paketsichtbarkeit in der Anwendung mit dem Contentanbieter, der der URI-Zertifizierungsstelle entspricht. Die Anwendung, die den Intent sendet, und die Anwendung, die den Inhaltsanbieter enthält, sind möglicherweise zwei verschiedene Anwendungen.

Ein Anbieter definiert in seinem Manifest URI-Berechtigungen für Inhalts-URIs mithilfe des Attributs android:grantUriPermissions des Elements <provider> und des untergeordneten Elements <grant-uri-permission> des Elements <provider>. Der Mechanismus für URI-Berechtigungen wird im Leitfaden zu Berechtigungen unter Android ausführlicher erläutert.

Beispielsweise können Sie Daten für einen Kontakt im Contacts Provider abrufen, auch wenn Sie nicht die Berechtigung READ_CONTACTS haben. Sie können dies z. B. in einer Anwendung tun, die E-Mails an einen Kontakt an seinem Geburtstag sendet. Anstatt READ_CONTACTS anzufordern, wodurch du Zugriff auf alle Kontakte des Nutzers und auf alle zugehörigen Informationen erhältst, kannst du ihm erlauben, zu steuern, welche Kontakte deine Anwendung verwendet. Gehen Sie dazu so vor:

  1. Senden Sie in Ihrer Anwendung mit der Methode startActivityForResult() einen Intent mit der Aktion ACTION_PICK und dem MIME-Typ CONTENT_ITEM_TYPE für „Kontakte“.
  2. Da dieser Intent dem Intent-Filter für die „Auswahl“-Aktivität der Personen-App entspricht, wird die Aktivität in den Vordergrund verschoben.
  3. In der Auswahlaktivität wählt der Nutzer einen Kontakt aus, der aktualisiert werden soll. In diesem Fall ruft die Auswahlaktivität setResult(resultcode, intent) auf, um einen Intent einzurichten, der an Ihre Anwendung zurückgegeben werden soll. Der Intent enthält den Inhalts-URI des Kontakts, den der Nutzer ausgewählt hat, und die zusätzlichen Flags FLAG_GRANT_READ_URI_PERMISSION. Mit diesen Flags erhält Ihre App die URI-Berechtigung, Daten für den Kontakt zu lesen, auf den der Inhalts-URI verweist. Die Auswahlaktivität ruft dann finish() auf, um die Steuerung an Ihre Anwendung zurückzugeben.
  4. Ihre Aktivität kehrt in den Vordergrund zurück und das System ruft die Methode onActivityResult() Ihrer Aktivität auf. Diese Methode empfängt den Ergebnis-Intent, der durch die Auswahlaktivität in der Kontakte-App erstellt wurde.
  5. Mit dem Inhalts-URI aus dem Ergebnis-Intent können Sie die Daten des Kontakts aus dem Contacts Provider lesen, auch wenn Sie dem Anbieter in Ihrem Manifest keine dauerhafte Lesezugriff-Berechtigung angefordert haben. Sie können dann den Geburtstag des Kontakts oder seine E-Mail-Adresse abfragen und die E-Mail senden.

Andere App verwenden

Eine andere Möglichkeit, dem Nutzer Daten zu ändern, für die Sie keine Zugriffsberechtigungen haben, besteht darin, eine Anwendung mit Berechtigungen zu aktivieren und dem Nutzer die Arbeit dort zu überlassen.

Die Kalenderanwendung akzeptiert beispielsweise den Intent ACTION_INSERT, mit dem Sie die Einfüge-UI der Anwendung aktivieren können. In diesem Intent können Sie zusätzliche Daten übergeben, mit denen die Anwendung die Benutzeroberfläche vorab füllt. Da wiederkehrende Termine eine komplexe Syntax haben, wird das Einfügen von Terminen in den Kalenderanbieter bevorzugt, indem die Kalender App mit einer ACTION_INSERT aktiviert wird und der Nutzer den Termin dann dort einfügen kann.

Daten mit einer Hilfsanwendung anzeigen

Auch wenn Ihre Anwendung Zugriffsberechtigungen hat, können Sie einen Intent verwenden, um Daten in einer anderen Anwendung anzuzeigen. Die Kalenderanwendung akzeptiert beispielsweise den Intent ACTION_VIEW, der ein bestimmtes Datum oder einen bestimmten Termin anzeigt. So können Sie Kalenderinformationen anzeigen, ohne eine eigene Benutzeroberfläche erstellen zu müssen. Weitere Informationen zu dieser Funktion finden Sie im Hilfeartikel Kalenderanbieter.

Die Anwendung, an die Sie den Intent senden, muss nicht die Anwendung sein, die dem Anbieter zugeordnet ist. Sie können beispielsweise einen Kontakt vom Kontaktanbieter abrufen und dann einen ACTION_VIEW-Intent mit dem Inhalts-URI für das Bild des Kontakts an einen Bildanzeige senden.

Vertragsklassen

Eine Vertragsklasse definiert Konstanten, die Anwendungen dabei unterstützen, mit den Inhalts-URIs, Spaltennamen, Intent-Aktionen und anderen Funktionen eines Inhaltsanbieters zu arbeiten. Vertragsklassen werden bei Anbietern nicht automatisch berücksichtigt. Der Entwickler des Anbieters muss sie definieren und dann anderen Entwicklern zur Verfügung stellen. Viele der in der Android-Plattform enthaltenen Anbieter haben entsprechende Vertragsklassen im Paket android.provider.

Der Anbieter des Nutzerwörterbuchs hat beispielsweise eine Vertragsklasse UserDictionary, die konstanten Inhalts-URI und Spaltennamen enthält. Der Inhalts-URI für die Tabelle Words wird in der konstanten UserDictionary.Words.CONTENT_URI definiert. Die Klasse UserDictionary.Words enthält auch Konstanten für Spaltennamen, die in den Beispiel-Snippets in diesem Leitfaden verwendet werden. Eine Abfrageprojektion kann beispielsweise so definiert werden:

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
};

Eine weitere Vertragsklasse ist ContactsContract für den Contacts Provider. Die Referenzdokumentation für diese Klasse enthält Beispielcode-Snippets. Eine der abgeleiteten Klassen, ContactsContract.Intents.Insert, ist eine Vertragsklasse, die Konstanten für Intents und Intent-Daten enthält.

MIME-Typ-Referenz

Contentanbieter können Standard-MIME-Medientypen, benutzerdefinierte MIME-Typ-Strings oder beides zurückgeben.

MIME-Typen haben das folgende Format:

type/subtype

Der bekannte MIME-Typ text/html hat beispielsweise den Typ text und den Untertyp html. Wenn der Anbieter diesen Typ für einen URI zurückgibt, gibt eine Abfrage mit diesem URI Text zurück, der HTML-Tags enthält.

Benutzerdefinierte MIME-Typ-Strings, die auch als anbieterspezifische MIME-Typen bezeichnet werden, haben komplexere type- und subtype-Werte. Bei mehreren Zeilen lautet der Typwert immer wie folgt:

vnd.android.cursor.dir

Für eine einzelne Zeile lautet der Typwert immer wie folgt:

vnd.android.cursor.item

subtype ist anbieterspezifisch. Die Anbieter mit integrierter Android-Version haben in der Regel einen einfachen Untertyp. Wenn die Anwendung Kontakte beispielsweise eine Zeile für eine Telefonnummer erstellt, wird der folgende MIME-Typ in der Zeile festgelegt:

vnd.android.cursor.item/phone_v2

Der Wert des Untertyps ist phone_v2.

Entwickler anderer Anbieter können basierend auf den Autoritäts- und Tabellennamen des Anbieters eigene Untertypenmuster erstellen. Nehmen wir als Beispiel einen Anbieter, der Zugfahrpläne enthält. Die Berechtigung des Anbieters ist com.example.trains und er enthält die Tabellen „Line1“, „Line2“ und „Line3“. Als Antwort auf den folgenden Inhalts-URI für Tabelle Line1:

content://com.example.trains/Line1

Der Anbieter gibt folgenden MIME-Typ zurück:

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

Als Antwort auf den folgenden Inhalts-URI für Zeile 5 in Tabelle Line2:

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

Der Anbieter gibt folgenden MIME-Typ zurück:

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

Die meisten Contentanbieter legen für die verwendeten MIME-Typen Konstanten für Vertragsklassen fest. Die Vertragsklasse ContactsContract.RawContacts des Contact Providers definiert beispielsweise die Konstante CONTENT_ITEM_TYPE für den MIME-Typ einer einzelnen Zeile mit Rohkontakten.

Inhalts-URIs für einzelne Zeilen werden im Abschnitt Inhalts-URIs beschrieben.