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
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.
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:
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:
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:
- Fordern Sie eine Lesezugriff-Berechtigung für den Anbieter an.
- 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:
-
Batchzugriff: Sie können einen Batch von Zugriffsaufrufen mit Methoden in der Klasse
ContentProviderOperation
erstellen und sie dann mitContentResolver.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 zeigen, wie das funktioniert. - Datenzugriff mithilfe von Intents: Sie können einen Intent zwar nicht direkt an einen Anbieter senden, haben aber die Möglichkeit, einen Intent an die Anwendung des Anbieters zu senden, die normalerweise am besten dazu geeignet ist, die Daten des Anbieters zu ändern.
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:
-
Leseberechtigung:
FLAG_GRANT_READ_URI_PERMISSION
-
Schreibberechtigung:
FLAG_GRANT_WRITE_URI_PERMISSION
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:
-
Senden Sie in Ihrer Anwendung mit der Methode
startActivityForResult()
einen Intent mit der AktionACTION_PICK
und dem MIME-TypCONTENT_ITEM_TYPE
für „Kontakte“. - Da dieser Intent dem Intent-Filter für die „Auswahl“-Aktivität der Personen-App entspricht, wird die Aktivität in den Vordergrund verschoben.
-
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 FlagsFLAG_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 dannfinish()
auf, um die Steuerung an Ihre Anwendung zurückzugeben. -
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. - 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.