Contentanbieter – Grundlagen

Ein Contentanbieter verwaltet den Zugriff auf ein zentrales Daten-Repository. Ein Anbieter ist Teil einer Android-Anwendung, die oft eine eigene Benutzeroberfläche für die Arbeit mit den Daten bietet. Contentanbieter werden jedoch hauptsächlich von anderen Anwendungen verwendet, die über ein Anbieterclientobjekt auf den Anbieter zugreifen. Zusammen bieten Anbieter und Anbieterclients eine einheitliche, standardmäßige Schnittstelle zu Daten, die auch die interprozedurale Kommunikation und den sicheren Datenzugriff abwickelt.

Normalerweise arbeiten Sie mit Contentanbietern in einem von zwei Szenarien: Sie implementieren Code, um in einer anderen Anwendung auf einen vorhandenen Contentanbieter zuzugreifen, oder Sie erstellen in Ihrer Anwendung einen neuen Contentanbieter, um Daten für andere Anwendungen freizugeben.

Auf dieser Seite werden die Grundlagen der Zusammenarbeit mit bestehenden Contentanbietern beschrieben. Informationen zum Implementieren von Inhaltsanbietern in Ihren eigenen Anwendungen finden Sie unter Inhaltsanbieter erstellen.

In diesem Thema wird Folgendes beschrieben:

  • So funktionieren Contentanbieter
  • Die API, mit der Sie Daten von einem Inhaltsanbieter abrufen.
  • Die API, mit der du Daten bei einem Contentanbieter einfügen, aktualisieren oder löschen kannst.
  • Andere API-Funktionen, die die Zusammenarbeit mit Anbietern erleichtern.

Übersicht

Ein Contentanbieter stellt Daten externen Anwendungen in einer oder mehreren Tabellen zur Verfügung, die den Tabellen in einer relationalen Datenbank ähneln. Eine Zeile stellt eine Instanz eines Datentyps dar, den der Anbieter erhebt. Jede Spalte in der Zeile steht für ein einzelnes Datenelement, das für eine Instanz erfasst wurde.

Ein Contentanbieter koordiniert den Zugriff auf die Datenspeicherebene in Ihrer Anwendung für verschiedene 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 über eine Implementierung von AbstractThreadedSyncAdapter mit dem Server synchronisieren
  • Laden von Daten in Ihre UI mit einem CursorLoader
Beziehung zwischen dem Inhaltsanbieter und anderen Komponenten.

Abbildung 1. Beziehung zwischen einem Inhaltsanbieter und anderen Komponenten.

Auf einen Anbieter zugreifen

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

Das Anbieterobjekt empfängt Datenanfragen von Kunden, führt die angeforderte Aktion aus und gibt die Ergebnisse zurück. Dieses Objekt hat Methoden, die identisch benannte Methoden im Anbieterobjekt aufrufen, einer Instanz einer der konkreten Unterklassen von ContentProvider. Die ContentResolver-Methoden bieten die grundlegenden CRUD-Funktionen (Erstellen, Abrufen, Aktualisieren und Löschen) des nichtflüchtigen Speichers.

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

So ist die Benutzeroberfläche während der Ausführung der Abfrage weiterhin verfügbar. Dieses Muster umfasst die Interaktion einer Reihe verschiedener Objekte sowie des 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:Damit Ihre Anwendung auf einen Anbieter zugreifen kann, muss sie in der Manifestdatei in der Regel bestimmte Berechtigungen anfordern. Dieses Entwicklungsmuster wird im Abschnitt Berechtigungen von Contentanbietern ausführlicher beschrieben.

Einer der integrierten Anbieter auf der Android-Plattform ist der Anbieter des Nutzerwörterbuchs, in dem die nicht standardmäßigen Wörter gespeichert werden, die der Nutzer behalten möchte. In Tabelle 1 sehen Sie, wie die Daten in der Tabelle dieses Anbieters aussehen könnten:

Tabelle 1:Beispiel für eine Tabelle mit Nutzerwörterbuch

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

In Tabelle 1 steht jede Zeile für ein Wort, das in keinem Standardwörterbuch zu finden ist. Jede Spalte stellt einen Datensatz für dieses Wort dar, z. B. die Sprache, in der es zuerst gefunden wurde. Die Spaltenüberschriften sind Spaltennamen, die beim Anbieter gespeichert sind. Wenn Sie beispielsweise auf die Sprache einer Zeile verweisen möchten, verwenden Sie die Spalte locale. Bei diesem Anbieter dient die Spalte _ID als Primärschlüsselspalte, die vom Anbieter automatisch verwaltet wird.

Um eine Liste der Wörter und ihrer Sprachen vom User Dictionary Provider zu erhalten, rufe ContentResolver.query() auf. Die Methode query() ruft die vom Anbieter des Nutzerwörterbuchs definierte Methode ContentProvider.query() auf. 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 zu einer SQL-Abfrage.

query()-Argument SELECT-Keyword/-Parameter Hinweise
Uri FROM table_name Uri ist der Tabelle im Anbieter table_name zugeordnet.
projection col,col,col,... projection ist ein Array von Spalten, das für jede abgerufene Zeile enthalten ist.
selection WHERE col = value selection gibt die Kriterien für die Auswahl von Zeilen 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. Content-URIs enthalten den symbolischen Namen des gesamten Anbieters – seine Autorität – und einen Namen, der auf eine Tabelle verweist – einen Pfad. Wenn Sie eine Clientmethode aufrufen, um auf eine Tabelle in einem Anbieter zuzugreifen, ist der Inhalts-URI für die Tabelle eines der Argumente.

In den vorherigen Codezeilen enthält die Konstante CONTENT_URI den Inhalts-URI der Tabelle Words des Anbieters des Nutzerwörterbuchs. Das ContentResolver-Objekt analysiert die Zertifizierungsstelle des URI und verwendet sie, um den Anbieter zu auflösen, indem die Zertifizierungsstelle mit einer Systemtabelle bekannter Anbieter verglichen wird. Die 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 content://-String ist das Schema, das immer vorhanden ist und den URI als Inhalts-URI identifiziert.
  • Der String user_dictionary ist die Autorität 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 am Ende des URI einen ID-Wert anhängen. Wenn Sie beispielsweise eine Zeile mit der _ID 4 vom Anbieter des Nutzerwörterbuchs abrufen möchten, können Sie diesen Inhalts-URI verwenden:

Kotlin

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

Java

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

Sie verwenden ID-Werte häufig, wenn Sie eine Reihe 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 korrekt 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 dem Inhalts-URI des Anbieters des Nutzerwörterbuchs eine ID anzuhängen.

Daten vom Anbieter abrufen

In diesem Abschnitt wird beschrieben, wie Sie Daten von einem Anbieter abrufen. Als Beispiel dient der User Dictionary Provider.

Zur besseren Verständlichkeit wird in den Code-Snippets in diesem Abschnitt ContentResolver.query() im UI-Thread aufgerufen. Im eigentlichen Code führen Sie Abfragen jedoch asynchron in einem separaten Thread durch. Sie können die Klasse CursorLoader verwenden, die im Leitfaden Lademechanismen ausführlicher beschrieben wird. Außerdem sind die Codezeilen nur Snippets. Sie zeigen keine vollständige Anwendung.

So rufen Sie Daten von einem Anbieter ab:

  1. Fordern Sie Lesezugriff für den Anbieter an.
  2. Definieren Sie den Code, mit dem eine Abfrage an den Anbieter gesendet wird.

Lesezugriff anfordern

Damit Ihre Anwendung Daten von einem Anbieter abrufen kann, benötigt sie Lesezugriffsberechtigungen für den Anbieter. Sie können diese Berechtigung nicht zur Laufzeit anfordern. Stattdessen müssen Sie in Ihrem Manifest angeben, dass Sie diese Berechtigung benötigen. Verwenden Sie dazu das Element <uses-permission> und den genauen Namen der Berechtigung, der vom Anbieter definiert wurde.

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

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 der Berechtigungen beim Zugriff auf Anbieter wird im Abschnitt Berechtigungen für Contentanbieter ausführlicher beschrieben.

Der User Dictionary Provider definiert die Berechtigung android.permission.READ_USER_DICTIONARY in seiner Manifestdatei. Eine Anwendung, die Daten vom Anbieter lesen möchte, muss diese Berechtigung also 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 Anbieter des Nutzerwörterbuchs 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 = {""};

Im nächsten Snippet wird gezeigt, wie ContentResolver.query() verwendet wird. Dabei wird der Nutzerwörterbuchanbieter als Beispiel verwendet. Eine Abfrage des Anbieterclients ähnelt einer SQL-Abfrage und enthält eine Reihe von Spalten, die zurückgegeben werden sollen, eine Reihe von Auswahlkriterien und eine Sortierreihenfolge.

Die von der Abfrage zurückgegebenen Spalten werden als Projektion bezeichnet und die Variable ist mProjection.

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

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

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 die tatsächlichen Spaltennamen anstelle von Konstanten der Vertragsklasse verwendet.

Schutz vor schädlichen Eingaben

Wenn sich die vom Contentanbieter verwalteten Daten in einer SQL-Datenbank befinden, kann das Einschleusen externer nicht vertrauenswürdiger Daten in rohe SQL-Anweisungen zu einer SQL-Injection 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;

Dadurch können Nutzer möglicherweise schädliche SQL-Codezeilen in Ihre SQL-Anweisung einfügen. Der Nutzer kann beispielsweise „nothing; DROP TABLE *;“ für mUserInput eingeben, was zur Auswahlklausel var = nothing; DROP TABLE *; führt.

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

Verwenden Sie zur Vermeidung dieses Problems eine Auswahlklausel mit ? als austauschbarem Parameter und einem separaten Array von Auswahlargumenten. Auf diese Weise wird die Nutzereingabe direkt an die Abfrage gebunden, anstatt als Teil einer SQL-Anweisung interpretiert zu werden. Da die Nutzereingaben nicht als SQL behandelt werden, kann über sie kein schädliches SQL-Code eingeschleust werden. Verwenden Sie anstelle der Zusammenführung, um die Nutzereingabe einzubeziehen, 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 von Auswahlargumenten so 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 für Auswahlargumente 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 zurück, der die Spalten enthält, die in der Projektion der Abfrage für die Zeilen angegeben sind, 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 die Zeilen in den Ergebnissen durchgehen, den Datentyp jeder Spalte ermitteln, die Daten aus einer Spalte abrufen und andere Eigenschaften der Ergebnisse prüfen.

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

Hinweis:Ein Anbieter kann den Zugriff auf Spalten basierend auf der Art des Objekts einschränken, das die Abfrage ausführt. Beispielsweise schränkt der Kontaktdatenanbieter den Zugriff auf einige Spalten auf Synchronadapter 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 ein leerer Cursor.

Bei einem internen Fehler hängen die Ergebnisse der Abfrage vom jeweiligen Anbieter ab. Es kann null zurückgeben oder eine Exception auslösen.

Da ein Cursor eine Liste von Zeilen ist, können Sie den Inhalt eines Cursor gut mithilfe eines SimpleCursorAdapter mit einem ListView verknüpfen.

Im folgenden Snippet wird der Code aus dem vorherigen Snippet fortgesetzt. Es wird ein SimpleCursorAdapter-Objekt mit dem Cursor erstellt, das durch die Abfrage abgerufen wurde, und dieses Objekt wird als Adapter für eine ListView festgelegt.

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: Um ein ListView mit einem Cursor zu stützen, muss der Cursor eine Spalte mit dem Namen _ID enthalten. Daher wird in der oben gezeigten Abfrage die Spalte _ID für die Tabelle Words abgerufen, 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 Abfrageergebnisse, sondern auch für andere Aufgaben verwenden. Sie können beispielsweise Rechtschreibvarianten vom Anbieter des Nutzerwörterbuchs abrufen und dann bei anderen Anbietern nachsehen. Dazu iterieren Sie über die Zeilen in 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. Im vorherigen Snippet wird beispielsweise getString() verwendet. Außerdem gibt es die Methode getType(), die einen Wert zurückgibt, der den Datentyp der Spalte angibt.

Ergebnisressourcen der Releaseabfrage

Cursor-Objekte müssen geschlossen werden, wenn sie nicht mehr benötigt werden, damit die zugehörigen Ressourcen schneller freigegeben werden. Dies kann entweder durch Aufrufen von close() oder durch Verwendung einer try-with-resources-Anweisung in der Java-Programmiersprache oder der use()-Funktion in der Kotlin-Programmiersprache erfolgen.

Berechtigungen von Contentanbietern

Die Anwendung eines Anbieters kann Berechtigungen festlegen, die andere Anwendungen benötigen, um auf die Daten des Anbieters zuzugreifen. Anhand dieser Berechtigungen kann der Nutzer sehen, auf welche Daten eine Anwendung zugreifen möchte. Je nach den 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 die Anwendung eines Anbieters keine Berechtigungen angibt, 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 vollen Lese- und Schreibzugriff.

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

Eine Anwendung fordert die erforderlichen Berechtigungen für den Zugriff auf einen Anbieter mit einem <uses-permission>-Element in der Manifestdatei an. Wenn der Android-Paketmanager die Anwendung installiert, muss der Nutzer alle von der Anwendung angeforderten Berechtigungen genehmigen. Wenn der Nutzer sie genehmigt, fährt der Paketmanager mit der Installation fort. Wenn der Nutzer sie nicht genehmigt, beendet der Paketmanager die Installation.

Im folgenden Beispielelement <uses-permission> fordert der Nutzerwörterbuchanbieter Lesezugriff an:

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

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

Daten einfügen, aktualisieren und löschen

Genauso 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 Providerclient ü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 Anbieter des Nutzerwörterbuchs 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 aufgenommen, das in seiner Form einem Cursor mit einer Zeile ähnelt. Die Spalten in diesem Objekt müssen nicht denselben Datentyp haben. Wenn Sie 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 sie automatisch verwaltet wird. Der Anbieter weist jeder hinzugefügten Zeile den eindeutigen Wert _ID zu. Anbieter verwenden diesen Wert normalerweise 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 Form von Content-URI automatisch erkennen und dann den angeforderten Vorgang auf diese bestimmte Zeile ausführen.

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

Daten aktualisieren

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

Im folgenden Snippet wird das Gebietsschema aller Zeilen, deren Sprache "en" ist, in 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, wenn Sie ContentResolver.update() aufrufen. Weitere Informationen finden Sie im Abschnitt Schutz vor schädlichen Eingaben.

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

Nutzereingabe beim Aufrufen von ContentResolver.delete() bereinigen Weitere Informationen finden Sie im Abschnitt Vor schädlichen Eingaben schützen.

Datentypen des Anbieters

Contentanbieter können viele verschiedene Datentypen anbieten. Der Anbieter des Nutzerwörterbuchs bietet nur Text an, aber Anbieter können auch die folgenden Formate anbieten:

  • Ganzzahl
  • Ganzzahl (long)
  • Gleitkommawert
  • Gleitkomma mit doppelter Genauigkeit (long)

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

Der Datentyp für jede Spalte eines Anbieters ist in der Regel in der Dokumentation aufgeführt. Die Datentypen für den Anbieter des Nutzerwörterbuchs sind in der Referenzdokumentation für die Vertragsklasse UserDictionary.Words aufgeführt. Contract-Klassen werden im Abschnitt Contract-Klassen beschrieben. Sie können den Datentyp auch durch Aufrufen von Cursor.getType() ermitteln.

Anbieter speichern auch MIME-Datentypinformationen für jeden von ihnen definierten Inhalts-URI. Anhand der Informationen zum MIME-Typ können Sie herausfinden, ob Ihre Anwendung Daten verarbeiten kann, die der Anbieter anbietet, oder eine Art der Verarbeitung basierend auf dem MIME-Typ auswählen. Sie benötigen den MIME-Typ in der Regel, wenn Sie mit einem Anbieter arbeiten, der komplexe Datenstrukturen oder Dateien enthält.

In der Tabelle ContactsContract.Data im Kontaktdatenanbieter werden beispielsweise MIME-Typen verwendet, um den Typ der Kontaktdaten zu kennzeichnen, die in jeder Zeile gespeichert sind. Rufe ContentResolver.getType() auf, um den MIME-Typ abzurufen, der einem Inhalts-URI entspricht.

Im Abschnitt MIME-Typreferenz wird die Syntax sowohl für Standard- als auch für benutzerdefinierte MIME-Typen beschrieben.

Alternative Formen des Anbieterzugriffs

Bei der Anwendungsentwicklung sind drei alternative Formen des Anbieterzugriffs wichtig:

  • Batchzugriff: Sie können mithilfe von Methoden in der Klasse ContentProviderOperation einen Batch von Zugriffsaufrufen erstellen und dann mit ContentResolver.applyBatch() anwenden.
  • Asynchrone Abfragen: Führen Sie Abfragen in einem separaten Thread durch. Sie können ein CursorLoader-Objekt verwenden. Die Beispiele im Leitfaden zu Ladeprogrammen veranschaulichen dies.
  • Datenzugriff über Intents: Sie können zwar keinen Intent direkt an einen Anbieter senden, aber an die Anwendung des Anbieters, die in der Regel am besten für die Änderung der Daten des Anbieters geeignet ist.

Der Batchzugriff und die Batchä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 mehreren Tabellen innerhalb desselben Methodenaufrufs einzufügen und im Allgemeinen eine Reihe von Vorgängen über Prozessgrenzen hinweg als Transaktion auszuführen. Dieser Vorgang wird als atomarer Vorgang bezeichnet.

Wenn du im Batchmodus auf einen Anbieter zugreifen möchtest, musst du ein Array von ContentProviderOperation-Objekten erstellen und sie dann mit ContentResolver.applyBatch() an einen Contentanbieter senden. Sie übergeben die Zertifizierungsstelle des Contentanbieters anstelle eines bestimmten Inhalts-URI an diese Methode.

So kann jedes ContentProviderOperation-Objekt im Array mit einer anderen Tabelle verwendet werden. Ein Aufruf von ContentResolver.applyBatch() gibt ein Array von Ergebnissen zurück.

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

Datenzugriff mit Intents

Intents können indirekten Zugriff auf einen Inhaltsanbieter bieten. Sie können den Nutzer auch dann auf Daten bei einem Anbieter zugreifen lassen, wenn Ihre App keine Zugriffsberechtigungen hat. Dazu können Sie entweder eine Ergebnisabsicht von einer App zurückgeben lassen, die Berechtigungen hat, oder eine App mit Berechtigungen aktivieren und den Nutzer darin arbeiten lassen.

Zugriff mit temporären Berechtigungen erhalten

Sie können auf Daten in einem Inhaltsanbieter zugreifen, auch wenn Sie nicht über die erforderlichen Zugriffsberechtigungen verfügen. Dazu senden Sie eine Intent an eine Anwendung, die die Berechtigungen hat, und erhalten eine Ergebnis-Intent mit URI-Berechtigungen zurück. Dies sind Berechtigungen für einen bestimmten Inhalts-URI, die bis zum Ende der Aktivität gültig sind, die sie empfängt. Die Anwendung mit dauerhaften Berechtigungen gewährt temporäre Berechtigungen, indem sie ein Flag in der Ergebnisabsicht festlegt:

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

Wenn Sie Inhalts-URIs an eine andere App senden, müssen Sie mindestens eines dieser Flags angeben. Die Flags bieten allen Apps, die eine Intent-Nachricht empfangen und auf Android 11 (API-Level 30) oder höher ausgerichtet sind, die folgenden Funktionen:

  • Lesen oder Schreiben von Daten, die durch den Inhalts-URI dargestellt werden, je nach Flag in der Absicht.
  • Sie erhalten Paketsichtbarkeit in der App, die den Inhaltsanbieter enthält, der mit der URI-Autorität übereinstimmt. Die App, die den Intent sendet, und die App, die den Contentanbieter enthält, können zwei verschiedene Apps sein.

Ein Anbieter definiert URI-Berechtigungen für Inhalts-URIs in seinem Manifest. Dazu werden das Attribut android:grantUriPermissions des <provider>-Elements und das untergeordnete <grant-uri-permission>-Element des <provider>-Elements verwendet. Der URI-Berechtigungsmechanismus wird unter Berechtigungen unter Android ausführlicher erläutert.

So können Sie beispielsweise Daten für einen Kontakt im Contacts Provider abrufen, auch wenn Sie nicht die Berechtigung READ_CONTACTS haben. Das kann beispielsweise in einer Anwendung der Fall sein, die an einem Kontakts Geburtstag eine E-Mail-Grußkarte sendet. Anstatt READ_CONTACTS anzufordern, wodurch Sie Zugriff auf alle Kontakte und Informationen des Nutzers erhalten, lassen Sie den Nutzer festlegen, welche Kontakte Ihre App 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 „Kontakte“ CONTENT_ITEM_TYPE.
  2. Da dieser Intent mit dem Intent-Filter für die Aktivität „Auswahl“ der Kontakte-App übereinstimmt, wird die Aktivität in den Vordergrund gestellt.
  3. In der Auswahlaktivität wählt der Nutzer einen Kontakt zum Aktualisieren aus. In diesem Fall ruft die Auswahlaktivität setResult(resultcode, intent) auf, um eine Intent-Funktion einzurichten, die etwas für Ihre Anwendung zurückgibt. Der Intent enthält den Inhalts-URI des vom Nutzer ausgewählten Kontakts und die Flags „Extras“ FLAG_GRANT_READ_URI_PERMISSION. Diese Flags gewähren Ihrer App die URI-Berechtigung zum Lesen von Daten für den Kontakt, auf den der Inhalts-URI verweist. Die Auswahlaktivität ruft dann finish() auf, um die Steuerung an die 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 von der Auswahlaktivität in der Personen-App erstellt wurde.
  5. Mit dem Inhalts-URI aus dem Ergebnis-Intent können Sie die Daten des Kontakts aus dem Contacts Provider lesen, obwohl Sie in Ihrem Manifest keine dauerhafte Lesezugriffsberechtigung an den Anbieter angefordert haben. Sie können dann die Geburtstagsinformationen oder die E-Mail-Adresse des Kontakts abrufen und die E-Mail-Grüße senden.

Andere App verwenden

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

Die Kalenderanwendung akzeptiert beispielsweise einen ACTION_INSERT-Intent, mit dem Sie die Einfüge-UI der Anwendung aktivieren können. Sie können in diesem Intent zusätzliche Daten übergeben, mit denen die Anwendung die UI vorab füllt. Da wiederkehrende Termine eine komplexe Syntax haben, besteht die bevorzugte Methode zum Einfügen von Terminen in den Kalenderanbieter darin, die Kalender-App mit einem ACTION_INSERT zu aktivieren und dem Nutzer die Möglichkeit zu geben, den Termin dort einzufügen.

Daten mit einer Hilfs-App anzeigen

Wenn Ihre Anwendung Zugriffsberechtigungen hat, können Sie einen Intent möglicherweise trotzdem zum Anzeigen von Daten in einer anderen Anwendung verwenden. Die Kalender-App akzeptiert beispielsweise eine ACTION_VIEW-Intent, die ein bestimmtes Datum oder Ereignis anzeigt. So können Sie Kalenderinformationen anzeigen, ohne eine eigene Benutzeroberfläche erstellen zu müssen. Weitere Informationen zu dieser Funktion finden Sie in der Übersicht über Kalenderanbieter.

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

Vertragsklassen

Eine Vertragsklasse definiert Konstanten, die Anwendungen bei der Arbeit mit den Inhalts-URIs, Spaltennamen, Intent-Aktionen und anderen Funktionen eines Inhaltsanbieters unterstützen. Vertragsklassen sind nicht automatisch bei einem Anbieter enthalten. Der Entwickler des Anbieters muss sie definieren und dann für andere Entwickler verfügbar machen. 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 mit Konstanten für Inhalts-URIs und Spaltennamen. Der Inhalts-URI für die Tabelle Words ist in der Konstante 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 Kontaktanbieter. Die Referenzdokumentation für diese Klasse enthält Beispiel-Code-Snippets. Eine ihrer Unterklassen, 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 folgendes 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.

Strings für benutzerdefinierte MIME-Typen, auch anbieterspezifische MIME-Typen genannt, haben komplexere type- und subtype-Werte. Bei mehreren Zeilen ist der Typwert immer:

vnd.android.cursor.dir

Für eine einzelne Zeile lautet der Typwert immer:

vnd.android.cursor.item

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

vnd.android.cursor.item/phone_v2

Der Wert des Untertyps ist phone_v2.

Andere Entwickler von Anbietern können basierend auf der Befugnis des Anbieters und den Tabellennamen eigene Untertypen erstellen. Angenommen, ein Anbieter stellt Zugfahrpläne bereit. Die Autorität des Anbieters ist com.example.trains und enthält die Tabellen „Line1“, „Line2“ und „Line3“. Als Antwort auf den folgenden Inhalts-URI für die Tabelle „Line1“:

content://com.example.trains/Line1

gibt der Anbieter den folgenden MIME-Typ zurück:

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

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

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

Der Anbieter gibt den folgenden MIME-Typ zurück:

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

Die meisten Contentanbieter definieren Vertragsklassenkonstanten für die von ihnen verwendeten MIME-Typen. Die Vertragsklasse ContactsContract.RawContacts des Kontaktdatenanbieters definiert beispielsweise die Konstante CONTENT_ITEM_TYPE für den MIME-Typ einer einzelnen Zeile mit Kontakten im Rohformat.

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