Ein Contentanbieter verwaltet den Zugriff auf ein zentrales Repository mit Daten. Ein Anbieter ist Teil einer Android-App, die oft eine eigene Benutzeroberfläche für die Arbeit mit mit den Daten. Contentanbieter werden jedoch hauptsächlich von anderen Anwendungen, die über ein Anbieter-Clientobjekt auf den Anbieter zugreifen. Gemeinsam können Anbieter und Provider-Kunden bieten eine einheitliche, standardisierte Schnittstelle für Daten, die auch die Kommunikation zwischen Prozessen und den sicheren Datenzugriff.
Normalerweise arbeiten Sie mit Contentanbietern in einem von zwei Szenarien zusammen: Implementierung um auf einen vorhandenen Contentanbieter in einer anderen Anwendung zuzugreifen, einen neuen Contentanbieter in Ihrer Anwendung verwenden, um Daten mit anderen Anwendungen zu teilen.
Diese Seite deckt die Grundlagen der Zusammenarbeit mit bestehenden Contentanbietern ab. Um mehr über die Implementierung Contentanbieter in Ihren eigenen Anwendungen <ph type="x-smartling-placeholder"></ph> Contentanbieter erstellen
In diesem Thema wird Folgendes beschrieben:
- Funktionsweise von Contentanbietern
- Die API, die Sie zum Abrufen von Daten von einem Contentanbieter verwenden.
- Die API, die Sie zum Einfügen, Aktualisieren oder Löschen von Daten bei einem Contentanbieter verwenden.
- Weitere API-Funktionen, die die Zusammenarbeit mit Anbietern erleichtern.
Übersicht
Ein Contentanbieter präsentiert Daten für externe Anwendungen als eine oder mehrere Tabellen, die ähnlich den Tabellen in einer relationalen Datenbank. Eine Zeile steht für eine Instanz eines Typs. Daten erfasst, die der Anbieter erfasst, und jede Spalte in der Zeile steht für ein einzelnes für eine Instanz erfasste Daten.
Ein Contentanbieter koordiniert den Zugriff auf die Datenspeicherebene in Ihrer Anwendung für eine APIs und Komponenten zu erstellen. Wie in Abbildung 1 dargestellt, umfassen diese Folgendes:
- Anderen Anwendungen Zugriff auf Ihre Anwendungsdaten gewähren
- Daten an ein Widget senden
- Über die Suche benutzerdefinierte Suchvorschläge für Ihre Anwendung zurückgeben
Framework mit
SearchRecentSuggestionsProvider
- Synchronisieren von Anwendungsdaten mit Ihrem Server mithilfe einer Implementierung von
AbstractThreadedSyncAdapter
- Laden von Daten in Ihre UI mit einem
CursorLoader
Auf einen Anbieter zugreifen
Wenn Sie auf Daten bei einem Contentanbieter zugreifen möchten, verwenden Sie die Methode
ContentResolver
-Objekt im
Context
, um mit dem Anbieter als Kunde zu kommunizieren. Die
Das ContentResolver
-Objekt kommuniziert mit dem Anbieterobjekt, einem
Instanz einer Klasse, die ContentProvider
implementiert.
Anbieter
-Objekt Datenanforderungen von Clients empfängt, die angeforderte Aktion durchführt und den Fehlerwert
Ergebnisse. Dieses Objekt verfügt über Methoden, die identisch benannte Methoden im Anbieterobjekt aufrufen,
Eine Instanz einer der konkreten abgeleiteten Klassen von ContentProvider
. Die
ContentResolver
-Methoden stellen die grundlegenden
„CRUD“ (Erstellen, Abrufen, Aktualisieren und Löschen) des nichtflüchtigen Speichers.
Ein häufiges Muster für den Zugriff auf ein ContentProvider
über Ihre Benutzeroberfläche verwendet einen
Mit CursorLoader
können Sie eine asynchrone Abfrage im Hintergrund ausführen. Die
Activity
oder Fragment
in Ihrer UI ruft ein
CursorLoader
hinzu, die wiederum den Wert
ContentProvider
mit ContentResolver
.
So bleibt die Benutzeroberfläche für den Nutzer verfügbar, während die Abfrage ausgeführt wird. Dieses die Interaktion verschiedener Objekte und die zugrunde liegende Speichermechanismus, wie in Abbildung 2 dargestellt.
Hinweis:Für den Zugriff auf einen Anbieter muss Ihre Anwendung in der Regel bestimmte Berechtigungen in der Manifest-Datei. Dieses Entwicklungsmuster wird ausführlicher in der Berechtigungen für Contentanbieter.
Einer der integrierten Anbieter der Android-Plattform ist der User Dictionary Provider, nicht standardmäßige Wörter speichert, die die Nutzenden behalten möchten. Tabelle 1 zeigt, könnten die Daten in der Tabelle dieses Anbieters aussehen:
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 steht jede Zeile für ein Wort, das nicht
Standardwörterbuch gefunden. Jede Spalte repräsentiert ein Datenelement für dieses Wort, z. B. die
Sprache, in der es zum ersten Mal aufgetreten ist. Die Spaltenüberschriften sind Spaltennamen, die in
und den Anbieter. Um beispielsweise auf die Sprache einer Zeile zu verweisen, verweisen Sie auf die entsprechende Spalte locale
. Für
bei diesem Anbieter dient die Spalte _ID
als Primärschlüsselspalte, die
automatisch verwaltet werden.
Um eine Liste der Wörter und ihrer Sprachen vom User Dictionary Provider zu erhalten,
rufst du ContentResolver.query()
an.
Die Methode query()
ruft die Methode
ContentProvider.query()
-Methode definiert durch das
Mein Wörterbuch. Die folgenden Codezeilen zeigen eine
ContentResolver.query()
-Anruf:
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
query(Uri,projection,selection,selectionArgs,sortOrder)
stimmt mit einer SQL-SELECT-Anweisung überein:
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 in jeder Zeile enthalten ist.
abgerufen werden.
|
selection |
WHERE col = value |
selection gibt die Kriterien für die Auswahl von Zeilen an. |
selectionArgs |
Kein genaues Äquivalent. Auswahlargumente ersetzen ? -Platzhalter in der
Auswahlklausel.
|
|
sortOrder |
ORDER BY col,col,... |
sortOrder gibt die Reihenfolge an, in der Zeilen im zurückgegebenen
Cursor .
|
Inhalts-URIs
Ein Inhalts-URI ist ein URI, der Daten bei einem Anbieter identifiziert. Inhalts-URIs den symbolischen Namen des gesamten Anbieters – seiner Zertifizierungsstelle – und einen der auf eine Tabelle verweist, also einen Pfad. Wenn du anrufst Clientmethode für den Zugriff auf eine Tabelle bei einem Anbieter, ist der Inhalts-URI für die Tabelle einer der folgenden Werte: die Argumente.
In den vorhergehenden Codezeilen wird die Konstante
CONTENT_URI
enthält den Inhalts-URI von
Die Tabelle Words
des Nutzerwörterbuchanbieters. Das ContentResolver
parst die Autorität des URI und verwendet sie, um den Anbieter aufzulösen.
Vergleich der Befugnisse mit einer Systemtabelle bekannter Anbieter. Die
ContentResolver
kann dann die Abfrageargumente an die richtige
Dienstanbieter.
Bei ContentProvider
wird anhand des Pfadteils des Inhalts-URI der
um darauf zuzugreifen. Ein Anbieter verfügt in der Regel über 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 identifiziert dies als Inhalts-URI. - Der String
user_dictionary
ist die Befugnis 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 anhängen.
am Ende des URI ein. Um beispielsweise eine Zeile abzurufen, deren _ID
gleich
4
vom User Dictionary Provider, 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 häufig ID-Werte, wenn Sie eine Reihe von Zeilen abrufen und dann aktualisieren oder löschen möchten eine davon.
Hinweis:Die Klassen Uri
und Uri.Builder
Praktische Methoden zur Konstruktion wohlgeformter URI-Objekte aus Strings enthalten. Die
Die Klasse ContentUris
enthält praktische Methoden zum Anhängen von ID-Werten an
URI. Im vorherigen Snippet wird withAppendedId()
verwendet, um eine ID an den Inhalts-URI des User Dictionary-Anbieters anzuhängen.
Daten vom Anbieter abrufen
In diesem Abschnitt wird beschrieben, wie Sie mithilfe des User Dictionary Providers Daten von einem Anbieter abrufen können. als Beispiel.
Der Einfachheit halber rufen die Code-Snippets in diesem Abschnitt
ContentResolver.query()
für den UI-Thread. In
tatsächlicher Code hingegen asynchron in einem separaten Thread. Sie können
verwenden Sie die Klasse CursorLoader
, die im
in den
Anleitung zu Ladeprogrammen. Außerdem handelt es sich bei den Codezeilen nur um Snippets. Es wird kein vollständiges
.
So rufen Sie Daten von einem Anbieter ab:
- Fordern Sie Lesezugriff für den Anbieter an.
- Definieren Sie den Code, der eine Abfrage an den Anbieter sendet.
Lesezugriff auf Berechtigung anfordern
Zum Abrufen von Daten von einem Anbieter benötigt Ihre Anwendung Lesezugriff für den
Dienstanbieter. Sie können diese Berechtigung nicht zur Laufzeit anfordern. Stattdessen müssen Sie angeben,
benötigen Sie diese Berechtigung in Ihrem Manifest. Verwenden Sie dazu den
<uses-permission>
und den genauen Berechtigungsnamen, der vom
Dienstanbieter.
Wenn Sie dieses Element in Ihrem Manifest angeben, fordern Sie Folgendes an: Berechtigung für Ihre Anwendung. Wenn Nutzer Ihre Anwendung installieren, gewähren sie implizit für diese Anfrage.
Wie Sie den genauen Namen der Leseberechtigung für den verwendeten Anbieter finden, Namen anderer Zugriffsberechtigungen, die der Anbieter verwendet, finden Sie in der Dokumentation.
Die Rolle der Berechtigungen beim Zugriff auf Anbieter wird ausführlicher in der Berechtigungen für Contentanbieter.
Der User Dictionary Provider definiert die Berechtigung.
android.permission.READ_USER_DICTIONARY
in seiner Manifest-Datei, sodass ein
Anwendung, die Daten vom Anbieter lesen möchte, muss diese Berechtigung anfordern.
Abfrage erstellen
Der nächste Schritt beim Abrufen von Daten von einem Anbieter besteht darin, eine Abfrage zu erstellen. Das folgende Snippet definiert einige Variablen für den Zugriff auf den Nutzerwörterbuchanbieter:
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 sehen Sie,
ContentResolver.query()
, mithilfe des Nutzerwörterbuchs
Anbieter als Beispiel. Eine Anbieter-Client-Abfrage ähnelt einer SQL-Abfrage und enthält eine
Satz von Spalten, die zurückgegeben werden sollen, ein Satz von Auswahlkriterien und eine Sortierreihenfolge.
Die von der Abfrage zurückgegebene Gruppe von Spalten wird als Projektion bezeichnet.
ist die Variable mProjection
.
Der Ausdruck, der die abzurufenden Zeilen angibt, wird in eine Auswahlklausel aufgeteilt und
Auswahlargumente. Die Auswahlklausel ist eine Kombination
aus logischen und booleschen Ausdrücken,
Spaltennamen und -werten. Die Variable lautet mSelectionClause
. Wenn Sie den Parameter
austauschbarer Parameter ?
anstelle eines Werts verwendet, ruft die Abfragemethode den Wert
aus dem Array der Auswahlargumente, d. h. der Variable mSelectionArgs
.
Wenn im nächsten Snippet der Nutzer kein Wort eingibt, wird die Auswahlklausel auf
null
und die Abfrage gibt alle Wörter im Anbieter zurück. Wenn der Nutzer
Wort enthalten, wird die Auswahlklausel auf UserDictionary.Words.WORD + " = ?"
gesetzt und
wird das erste Element des Arrays der Auswahlargumente auf das vom Nutzer eingegebene Wort gesetzt.
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 der Vertragsklassenkonstanten verwendet.
Schutz vor schädlichen Eingaben
Wenn sich die vom Contentanbieter verwalteten Daten in einer SQL-Datenbank befinden, einschließlich externer, nicht vertrauenswürdiger Daten Daten in SQL-Rohanweisungen einschleusen.
Sehen Sie sich die folgende Auswahlklausel an:
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;
Wenn Sie dies tun, kann der Nutzer möglicherweise schädlichen SQL-Code mit Ihrer SQL-Anweisung verketten.
Nutzende können beispielsweise „nothing; DROP TABLE *;"
für mUserInput
, die
führt zur Auswahlklausel var = nothing; DROP TABLE *;
.
Da die als SQL-Anweisung behandelt wird, löscht der Anbieter möglicherweise alle Tabellen in der zugrunde liegenden SQLite-Datenbank, es sei denn, der Anbieter ist so eingerichtet, SQL-Einschleusungsversuche
Um dieses Problem zu vermeiden, sollten Sie eine Auswahlklausel verwenden, die ?
als austauschbares Objekt verwendet.
und einem separaten Array von Auswahlargumenten. Auf diese Weise können die
direkt an die Abfrage gebunden ist, anstatt als Teil einer SQL-Anweisung interpretiert zu werden.
Da sie nicht als SQL behandelt wird, kann die Nutzereingabe kein schädliches SQL einschleusen. Anstelle von
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 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 der 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 verwendet, und ein Array von
Das Array der Auswahlargumente ist die bevorzugte Methode, um eine Auswahl anzugeben, auch wenn der Anbieter nicht
basierend auf einer SQL-Datenbank.
Abfrageergebnisse anzeigen
Die Clientmethode ContentResolver.query()
immer
gibt ein Cursor
zurück, das die durch die Abfrage
Projektion für die Zeilen, die den Auswahlkriterien der Abfrage entsprechen. A
Das Objekt Cursor
bietet zufälligen Lesezugriff auf die Zeilen und Spalten, die es enthält
enthält.
Mit den Methoden Cursor
können Sie über die Zeilen im
den Datentyp jeder Spalte bestimmen, die Daten aus einer Spalte abrufen und
Eigenschaften der Ergebnisse.
Einige Cursor
-Implementierungen werden automatisch
Objekt aktualisieren, wenn sich die Daten des Anbieters ändern, Methoden in einem Beobachterobjekt auslösen
wenn sich Cursor
ändert, oder beides.
Hinweis:Ein Anbieter kann den Zugriff auf Spalten basierend auf der Art des Objekt, das die Abfrage durchführt. Zum Beispiel schränkt der Contacts Provider den Zugriff für einige Spalten auf Synchronisierungsadapter verwenden, sodass sie nicht zu einer Aktivität oder einem Dienst zurückgeleitet werden.
Wenn keine Zeilen den Auswahlkriterien entsprechen,
gibt ein Cursor
-Objekt zurück, für das
Cursor.getCount()
ist
0, d. h. ein leerer Cursor.
Wenn ein interner Fehler auftritt, hängen die Ergebnisse der Abfrage vom jeweiligen Anbieter ab. Möglicherweise
null
zurückgeben oder eine Exception
ausgeben.
Da es sich bei Cursor
um eine Liste von Zeilen handelt, ist es sinnvoll,
ist die Verknüpfung mit einem ListView
-Cursor
mit SimpleCursorAdapter
.
Mit dem folgenden Snippet wird der Code des vorherigen Snippets fortgesetzt. Es wird ein
SimpleCursorAdapter
-Objekt, das Cursor
enthält
und legt dieses Objekt als Adapter für ein
ListView
Kotlin
// Defines a list of columns to retrieve from the Cursor and load into an output row val wordListColumns : Array<String> = arrayOf( UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name ) // Defines a list of View IDs that receive the Cursor columns for each row val wordListItems = intArrayOf(R.id.dictWord, R.id.locale) // Creates a new SimpleCursorAdapter cursorAdapter = SimpleCursorAdapter( applicationContext, // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0 // Flags (usually none are needed) ) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter)
Java
// Defines a list of columns to retrieve from the Cursor and load into an output row String[] wordListColumns = { UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name }; // Defines a list of View IDs that receive the Cursor columns for each row int[] wordListItems = { R.id.dictWord, R.id.locale}; // Creates a new SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter( getApplicationContext(), // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0); // Flags (usually none are needed) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter);
Hinweis: Um ein ListView
mit einem
Cursor
muss der Cursor eine Spalte mit dem Namen _ID
enthalten.
Aus diesem Grund ruft die zuvor gezeigte Abfrage die Spalte _ID
für den
Words
angezeigt, auch wenn sie von ListView
nicht angezeigt wird.
Diese Einschränkung erklärt auch, warum die meisten Anbieter jeweils eine _ID
-Spalte für jede der
ihre Tabellen.
Daten aus Abfrageergebnissen abrufen
Sie können sie nicht nur für Abfrageergebnisse, sondern auch für andere Aufgaben verwenden. Für
Sie können z. B. Schreibweisen vom User Dictionary Provider abrufen und sie dann in
anderen Anbietern. 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“-Elemente Methoden für
das Abrufen verschiedener Datentypen aus dem Objekt. Das vorherige Snippet
verwendet getString()
. Sie haben auch eine
getType()
-Methode, die einen Wert zurückgibt, der angibt,
den Datentyp der Spalte.
Ergebnisressourcen der Releaseabfrage
Cursor
-Objekte müssen
geschlossen werden, wenn sie nicht mehr benötigt werden, sodass mit ihnen verknüpfte Ressourcen freigegeben werden
früher. Rufen Sie dazu entweder
close()
oder mithilfe von
eine try-with-resources
-Anweisung in der Programmiersprache Java oder die
use()
in der Programmiersprache Kotlin ab.
Berechtigungen für Contentanbieter
Die Anwendung eines Anbieters kann Berechtigungen festlegen, die andere Anwendungen benötigen, um auf die Daten des Anbieters zugreifen. Diese Berechtigungen teilen dem Nutzer mit, welche Daten auf die eine App zugreifen möchte. Je nach Anbieteranforderungen werden andere Anwendungen die Berechtigungen anfordern, die sie für den Zugriff auf den Anbieter benötigen. Endnutzer sehen die angeforderten wenn sie die App installieren.
Wenn die Anwendung eines Anbieters keine Berechtigungen festlegt, haben andere Anwendungen keine Zugriff auf die Daten des Anbieters, es sei denn, der Anbieter wird exportiert. Außerdem können Komponenten in der Anwendung des Anbieters immer vollständigen Lese- und Schreibzugriff haben, unabhängig von angegebenen Berechtigungen.
Der User Dictionary Provider benötigt die
Berechtigung android.permission.READ_USER_DICTIONARY
, Daten daraus abzurufen.
Der Anbieter hat eine separate android.permission.WRITE_USER_DICTIONARY
Berechtigung zum Einfügen, Aktualisieren oder Löschen von Daten.
Um die erforderlichen Berechtigungen für den Zugriff auf einen Anbieter zu erhalten, fordert eine Anwendung sie mit einem
<uses-permission>
-Element in seiner Manifest-Datei. Wenn der Android Package Manager die App installiert,
muss alle Berechtigungen genehmigen, die von der Anwendung angefordert werden. Wenn der Nutzer sie genehmigt,
Der Paketmanager setzt die Installation fort. Wenn der Nutzer sie nicht genehmigt,
die Installation beendet.
Im folgenden Beispiel
<uses-permission>
-Element fordert Lesezugriff beim Nutzerwörterbuchanbieter an:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
Die Auswirkungen von Berechtigungen auf den Anbieterzugriff werden unter Sicherheitstipps
Daten einfügen, aktualisieren und löschen
So wie Sie Daten von einem Anbieter abrufen, verwenden Sie auch die Interaktion zwischen
einen Anbieterclient und die ContentProvider
des Anbieters, um Daten zu ändern.
Sie rufen eine Methode von ContentResolver
mit Argumenten auf, die an
die entsprechende Methode von ContentProvider
. Anbieter und Anbieter
für Sicherheit und Interprozesskommunikation.
Daten einfügen
Um Daten in einen Anbieter einzufügen, rufen Sie die Methode
ContentResolver.insert()
. 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 User Dictionary Provider 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 übertragen,
ähnelt in Form einem Cursor mit einer Zeile. Für die Spalten in diesem Objekt ist der Wert
denselben Datentyp haben und keinen Wert angeben möchten, können Sie eine Spalte
an null
mit ContentValues.putNull()
.
Im vorherigen Snippet wurde die Spalte _ID
nicht hinzugefügt, da diese Spalte beibehalten wird
automatisch. Der Anbieter weist jeder Zeile, die_ID
hinzugefügt. Anbieter verwenden diesen Wert normalerweise als Primärschlüssel der Tabelle.
Der in newUri
zurückgegebene Inhalts-URI identifiziert die neu hinzugefügte Zeile mit
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 Inhalts-URI automatisch erkennen und dann den angeforderten
für diese Zeile.
Um den Wert von _ID
aus dem zurückgegebenen Uri
zu erhalten, rufen Sie
ContentUris.parseId()
Daten aktualisieren
Um eine Zeile zu aktualisieren, verwenden Sie ein ContentValues
-Objekt mit dem aktualisierten
Werte, genau wie bei Einfügungen und Auswahlkriterien wie bei einer Abfrage.
Die Client-Methode, die Sie verwenden, ist
ContentResolver.update()
Sie müssen nur
in das ContentValues
-Objekt für die Spalten, die Sie aktualisieren. Wenn Sie
den Inhalt einer Spalte löschen möchten, setzen Sie den Wert auf null
.
Das folgende Snippet ändert alle Zeilen, deren Sprache die Sprache "en"
hat, in
haben die Sprache null
. Der Rückgabewert ist die Anzahl der Zeilen, die aktualisiert wurden.
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 );
Nutzereingabe beim Anruf bereinigen
ContentResolver.update()
Weitere Informationen über
lesen Sie den Abschnitt Schutz vor schädlichen Eingaben.
Daten löschen
Das Löschen von Zeilen ähnelt dem Abrufen von Zeilendaten. Sie legen Auswahlkriterien für die Zeilen fest.
die Sie löschen möchten, und die Client-Methode 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 den Fehlerwert
Anzahl der gelöschten Zeilen.
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 Anruf bereinigen
ContentResolver.delete()
Weitere Informationen über
lesen Sie den Abschnitt Schutz vor schädlichen Eingaben.
Datentypen des Anbieters
Contentanbieter können viele verschiedene Datentypen anbieten. Der User Dictionary Provider bietet nur Textanzeige. Anbieter können jedoch auch die folgenden Formate anbieten:
- Ganzzahl
- Long Integer (long)
- Gleitkommawert
- langer Gleitkommawert (doppelt)
Ein weiterer Datentyp, den Anbieter häufig verwenden, ist ein BLOB (Binary Large Object), das als
64 KB Byte-Array. Die verfügbaren Datentypen finden Sie in der
Cursor
-Klasse „get“ .
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 aufgeführt.
für die Vertragsklasse UserDictionary.Words
. Vertragsklassen sind
wie im Abschnitt Vertragsklassen 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. Sie können können Sie anhand der MIME-Typ-Informationen herausfinden, ob Ihre Anwendung Daten verarbeiten kann, oder die Art der Handhabung basierend auf dem MIME-Typ wählen. In der Regel benötigen Sie MIME-Typ, wenn Sie mit einem Anbieter zusammenarbeiten, der komplexe Datenstrukturen oder Dateien.
Beispiel: ContactsContract.Data
-Tabelle im Contacts Provider verwendet MIME-Typen, um den Typ der in den einzelnen
Zeile. Um den MIME-Typ für einen Inhalts-URI abzurufen, rufen Sie folgenden Befehl auf:
ContentResolver.getType()
Im Abschnitt MIME-Typ-Referenz wird beschrieben, Syntax von Standard- und benutzerdefinierten MIME-Typen.
Alternative Möglichkeiten des Anbieterzugriffs
Für die Anwendungsentwicklung sind drei alternative Formen des Anbieterzugriffs wichtig:
-
Batch-Zugriff: Sie können einen Batch von Zugriffsaufrufen mit den Methoden in
ContentProviderOperation
und wenden sie dann mitContentResolver.applyBatch()
. -
Asynchrone Abfragen: Führen Sie Abfragen in einem separaten Thread durch. Sie können
Verwenden Sie ein
CursorLoader
-Objekt. Die Beispiele in den Ladeprogramm demonstrieren, wie das geht. - Datenzugriff mit Intents: Sie können jedoch keinen Intent senden. direkt an einen Anbieter senden, können Sie einen Intent an die Anwendung des Anbieters senden. am besten ausgerüstet sind, um die Daten des Anbieters zu modifizieren.
Der Batchzugriff und die Batch-Änderung mit Intents werden in den folgenden Abschnitten beschrieben.
Batch-Zugriff
Der Batchzugriff auf einen Anbieter eignet sich zum Einfügen einer großen Anzahl von Zeilen, in mehreren Tabellen im selben Methodenaufruf verwenden. Vorgänge über Prozessgrenzen hinweg als Transaktion, die als atomarer Vorgang bezeichnet wird.
So greifen Sie im Batchmodus auf einen Anbieter zu:
ein Array mit ContentProviderOperation
-Objekten und dann
an einen Contentanbieter mit
ContentResolver.applyBatch()
. Sie bestehen am
die Zertifizierungsstelle des Contentanbieters für diese Methode und nicht für einen bestimmten Inhalts-URI.
So funktioniert jedes ContentProviderOperation
-Objekt im Array.
mit einer anderen Tabelle vergleichen. Ein Aufruf von ContentResolver.applyBatch()
gibt ein Array von Ergebnissen zurück.
Die Beschreibung der Vertragsklasse ContactsContract.RawContacts
enthält ein Code-Snippet, das die Batch-Einfügung veranschaulicht.
Datenzugriff mit Intents
Intents können indirekten Zugriff auf einen Contentanbieter ermöglichen. Sie können dem Nutzer Zugriff auch wenn Ihre App keine Zugriffsberechtigungen hat. das Zurückgeben eines Ergebnis-Intents von einer Anwendung mit Berechtigungen oder durch Aktivieren eines -Anwendung, die über Berechtigungen verfügt und die der Nutzer darin arbeiten kann.
Zugriff mit temporären Berechtigungen erhalten
Sie können auch dann auf Daten bei einem Contentanbieter zugreifen, wenn Sie nicht die erforderlichen Zugriffsrechte haben. Berechtigungen, indem ein Intent an eine Anwendung gesendet wird, die über die einen Ergebnis-Intent mit URI-Berechtigungen zurückerhalten. Dies sind Berechtigungen für einen bestimmten Inhalts-URI, die bis zu der Aktivität gültig bleiben, die wenn sie abgeschlossen sind. Die Anwendung, die permanente Berechtigungen hat, gewährt vorübergehende Berechtigungen. indem Sie im Ergebnis-Intent ein Flag festlegen:
-
Leseberechtigung:
FLAG_GRANT_READ_URI_PERMISSION
-
Schreibberechtigung:
FLAG_GRANT_WRITE_URI_PERMISSION
Hinweis:Diese Flags gewähren dem Anbieter keinen allgemeinen Lese- oder Schreibzugriff. deren Berechtigung im Inhalts-URI enthalten ist. Der Zugriff gilt nur für den URI selbst.
Wenn du Inhalts-URIs an eine andere App sendest, füge mindestens einen dieser URIs ein Flags. Die Flags stellen die folgenden Funktionen für jede Anwendung bereit, die eine Absicht haben und auf Android 11 (API-Level 30) oder höher ausgerichtet ist:
- Aus den Daten lesen oder in diese schreiben, die der Inhalts-URI repräsentiert, abhängig von dem im Intent enthaltenen Flag.
- Paket gewinnen Sichtbarkeit der App mit dem Contentanbieter, der mit dem URI-Befugnis. Die App, die den Intent sendet, und die App, die Contentanbieter zwei verschiedene Apps sein können.
Ein Anbieter definiert URI-Berechtigungen für Inhalts-URIs in seinem Manifest, indem er die
android:grantUriPermissions
des Tags
<provider>
-Element sowie das
<grant-uri-permission>
-Element der
<provider>
-Elements. Der URI-Berechtigungsmechanismus wird in der
Leitfaden zu Berechtigungen unter Android
So können Sie beispielsweise Daten für einen Kontakt im Contacts Provider abrufen, auch wenn Sie
haben die Berechtigung READ_CONTACTS
. Vielleicht möchten Sie
in einer Anwendung, die an Geburtstage E-Grüße an Kontakte sendet. Anstelle von
READ_CONTACTS
wird angefordert, wodurch Sie Zugriff auf alle
Kontakte des Nutzers und alle zugehörigen Informationen zu verwalten, kann der Nutzer festlegen,
Kontakte, die Ihre Anwendung verwendet. Gehen Sie dazu so vor:
-
Senden Sie in der Anwendung einen Intent mit der Aktion.
ACTION_PICK
und die "Kontakte" MIME-TypCONTENT_ITEM_TYPE
mit demstartActivityForResult()
-Methode. - Da dieser Intent mit dem Intent-Filter für den "Auswahl" der Personen-App wird die Aktivität in den Vordergrund gestellt.
-
In der Auswahlaktivität wählt die nutzende Person eine
zu aktualisieren. In diesem Fall ruft die Auswahlaktivität
setResult(resultcode, intent)
um einen Intent zu erstellen, um Ihrer Anwendung etwas zurückzugeben. Der Intent enthält den Inhalts-URI den vom Nutzer ausgewählten Kontakt und die FlagsFLAG_GRANT_READ_URI_PERMISSION
Diese Flags gewähren URI Berechtigung für deine App, Daten des Kontakts zu lesen, auf den das Inhalts-URI. Die Auswahlaktivität ruft dannfinish()
auf, um die Kontrolle an Ihre Anwendung zurückgeben. -
Ihre Aktivität wird wieder in den Vordergrund verschoben und das System ruft die
onActivityResult()
. Diese Methode empfängt den Ergebnis-Intent, der von der Auswahlaktivität in App „Kontakte“. - Mit dem Inhalts-URI aus dem Ergebnis-Intent können Sie die Daten des Kontakts lesen vom Contacts Provider, auch wenn Sie keine dauerhafte Berechtigung für den Lesezugriff angefordert haben in Ihrem Manifest an den Anbieter. Sie können dann das Geburtsdatum des Kontakts oder E-Mail-Adresse und senden Sie dann die Grußnachricht.
Andere Anwendung 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 über Berechtigungen verfügt, und dem Nutzer die Arbeit dort überlassen.
Die Kalenderanwendung akzeptiert z. B. ein
ACTION_INSERT
-Intent, mit dem Sie den
-Benutzeroberfläche zum Einfügen. Sie können „Extras“ übergeben. in diesem Intent enthalten, die von der Anwendung
um die Benutzeroberfläche vorab auszufüllen. Da wiederkehrende Termine eine komplexe Syntax haben, sollten
können Sie Termine in den Kalenderanbieter einfügen, indem Sie die Kalender App mit einem
ACTION_INSERT
und lasse den Nutzer dann das Ereignis dort einfügen.
Daten mit einer Hilfs-App anzeigen
Wenn Ihre App Zugriffsberechtigungen hat, können Sie trotzdem ein
Daten in einer anderen Anwendung anzuzeigen. Die Kalenderanwendung akzeptiert z. B. ein
ACTION_VIEW
-Intent, der ein bestimmtes Datum oder Ereignis anzeigt.
Auf diese Weise können Sie Kalenderinformationen anzeigen, ohne eine eigene Benutzeroberfläche erstellen zu müssen.
Weitere Informationen zu dieser Funktion finden Sie in der
Übersicht des Kalenderanbieters
Die Anwendung, an die Sie den Intent senden, muss nicht die Anwendung sein
die mit dem Anbieter verknüpft sind. Sie können beispielsweise einen Kontakt aus der
Anbieter kontaktieren und dann den Intent ACTION_VIEW
senden
, der den Inhalts-URI für das Bild des Kontakts für eine Bildanzeige enthält.
Vertragsklassen
Eine Vertragsklasse definiert Konstanten, damit Anwendungen mit Inhalts-URIs arbeiten können. Spalte
Namen, Absichtsaktionen und anderen Funktionen eines Contentanbieters. Vertragsklassen keine
automatisch mit einem Anbieter verknüpft werden. Diese müssen vom Entwickler des Anbieters definiert werden.
anderen Entwicklern zur Verfügung zu stellen. Viele der in der Android-App enthaltenen Anbieter
Plattform hat entsprechende Vertragsklassen im Paket android.provider
.
Der User Dictionary Provider verfügt beispielsweise über eine
UserDictionary
mit Inhalts-URI und Konstanten für Spaltennamen. Die
Der Inhalts-URI für die Tabelle Words
ist in der Konstante definiert
UserDictionary.Words.CONTENT_URI
.
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
wie folgt definiert:
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 Beispielcode-Snippets. Eines der
abgeleiteten Klassen, ContactsContract.Intents.Insert
, ist ein Vertrag
Klasse, 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, bedeutet dies, dass ein
mit diesem URI gibt Text zurück, der HTML-Tags enthält.
Benutzerdefinierte MIME-Typ-Strings, auch anbieterspezifische MIME-Typen genannt, haben mehr komplexe type- und subtype-Werte. Bei mehreren Zeilen ist der Typwert immer der folgende:
vnd.android.cursor.dir
Für eine einzelne Zeile lautet der Typwert immer:
vnd.android.cursor.item
Die subtype ist anbieterspezifisch. Die Android-Anbieter haben in der Regel eine einfache . Wenn die Kontakte-Anwendung 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
.
Andere Entwickler können eigene Untertypen erstellen, die auf den Einstellungen des Anbieters basieren.
Autoritäts- und Tabellennamen. Stellen Sie sich beispielsweise einen Anbieter mit Zugfahrplänen vor.
Der Anbieter ist com.example.trains
. 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
gibt der Anbieter den 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
gibt der Anbieter den folgenden MIME-Typ zurück:
vnd.android.cursor.item/vnd.example.line2
Die meisten Contentanbieter definieren Kontraktklassenkonstanten für die von ihnen verwendeten MIME-Typen. Die
Kontaktklasse für Anbieter-Vertragsklasse ContactsContract.RawContacts
,
definiert zum Beispiel die Konstante
CONTENT_ITEM_TYPE
für den MIME-Typ von
eine einzelne Rohkontaktzeile.
Inhalts-URIs für einzelne Zeilen werden in der Abschnitt Inhalts-URIs angezeigt.