Ein Contentanbieter verwaltet den Zugriff auf ein zentrales Datenverzeichnis. Anbieter werden zusammen mit Elementen in der Manifestdatei als eine oder mehrere Klassen in einer Android-App implementiert. Eine Ihrer Klassen implementiert eine abgeleitete Klasse von ContentProvider
. Diese ist die Schnittstelle zwischen Ihrem Anbieter und anderen Anwendungen.
Obwohl Contentanbieter anderen Anwendungen Daten zur Verfügung stellen sollen, können Sie Aktivitäten in Ihrer Anwendung haben, mit denen der Nutzer die von Ihrem Anbieter verwalteten Daten abfragen und ändern kann.
Auf dieser Seite werden der grundlegende Prozess zum Erstellen eines Contentanbieters und eine Liste der zu verwendenden APIs beschrieben.
Bevor Sie mit dem Erstellen
Bevor Sie mit dem Erstellen eines Anbieters beginnen, sollten Sie Folgendes berücksichtigen:
-
Überlegen Sie, ob Sie einen Contentanbieter benötigen. Sie müssen einen Contentanbieter erstellen, wenn Sie eine oder mehrere der folgenden Funktionen bereitstellen möchten:
- Sie möchten anderen Anwendungen komplexe Daten oder Dateien zur Verfügung stellen.
- Sie möchten Nutzern ermöglichen, komplexe Daten aus Ihrer App in andere Apps zu kopieren.
- Sie möchten mithilfe des Such-Frameworks benutzerdefinierte Suchvorschläge bereitstellen.
- Sie möchten Ihre Anwendungsdaten Widgets zur Verfügung stellen.
- Sie möchten die Klassen
AbstractThreadedSyncAdapter
,CursorAdapter
oderCursorLoader
implementieren.
Sie benötigen keinen Anbieter, um Datenbanken oder andere Arten von nichtflüchtigen Speichern zu verwenden, wenn die Verwendung ausschließlich innerhalb Ihrer eigenen Anwendung erfolgt und Sie keine der oben aufgeführten Features benötigen. Stattdessen können Sie eines der unter Daten- und Dateispeicher – Übersicht beschriebenen Speichersysteme verwenden.
- Im Artikel Grundlagen zu Contentanbietern erhältst du weitere Informationen zu Anbietern und ihrer Funktionsweise.
Führen Sie als Nächstes die folgenden Schritte aus, um Ihren Anbieter zu erstellen:
-
Entwerfen Sie den Rohspeicher für Ihre Daten. Ein Contentanbieter stellt Daten auf zwei Arten bereit:
- Dateidaten
- Daten, die normalerweise in Dateien gespeichert werden, z. B. Fotos, Audiodateien oder Videos. Speichern Sie die Dateien im privaten Bereich Ihrer Anwendung. Als Antwort auf eine Anfrage für eine Datei von einer anderen Anwendung kann Ihr Anbieter einen Handle für die Datei anbieten.
- „Strukturierte“ Daten
- Daten, die normalerweise in eine Datenbank, ein Array oder eine ähnliche Struktur einfließen. Speichern Sie die Daten in einem Formular, das mit Tabellen mit Zeilen und Spalten kompatibel ist. Eine Zeile stellt eine Entität dar, z. B. eine Person oder einen Artikel im Inventar. Eine Spalte repräsentiert einige Daten für die Entität, z. B. den Namen einer Person oder den Preis eines Artikels. Eine gängige Methode zum Speichern dieser Art von Daten ist eine SQLite-Datenbank. Sie können jedoch auch jede Art von nichtflüchtigen Speicher verwenden. Weitere Informationen zu den im Android-System verfügbaren Speichertypen finden Sie im Abschnitt Datenspeicher entwerfen.
-
Definieren Sie eine konkrete Implementierung der
ContentProvider
-Klasse und der erforderlichen Methoden. Diese Klasse ist die Schnittstelle zwischen Ihren Daten und dem Rest des Android-Systems. Weitere Informationen zu dieser Klasse finden Sie im Abschnitt ContentProvider-Klasse implementieren. - Definieren Sie den Zertifizierungsstring, die Inhalts-URIs und die Spaltennamen des Anbieters. Wenn die Anwendung des Anbieters Intents verarbeiten soll, definieren Sie auch Intent-Aktionen, Extrasdaten und Flags. Definieren Sie auch die Berechtigungen, die Sie für Anwendungen benötigen, die auf Ihre Daten zugreifen möchten. Überlegen Sie, alle diese Werte als Konstanten in einer separaten Vertragsklasse zu definieren. Später können Sie diese Klasse anderen Entwicklern zugänglich machen. Weitere Informationen zu Inhalts-URIs finden Sie im Abschnitt Inhalts-URIs entwerfen. Weitere Informationen zu Intents finden Sie im Abschnitt Intents und Datenzugriff.
-
Fügen Sie weitere optionale Elemente hinzu, z. B. Beispieldaten oder eine Implementierung von
AbstractThreadedSyncAdapter
, die Daten zwischen dem Anbieter und cloudbasierten Daten synchronisieren können.
Designdatenspeicherung
Ein Contentanbieter ist die Schnittstelle zu Daten, die in einem strukturierten Format gespeichert sind. Entscheiden Sie vor dem Erstellen der Schnittstelle, wie die Daten gespeichert werden sollen. Sie können die Daten in beliebiger Form speichern und dann die Schnittstelle so gestalten, dass die Daten nach Bedarf gelesen und geschrieben werden.
Dies sind einige der Datenspeichertechnologien, die auf Android zur Verfügung stehen:
- Wenn Sie mit strukturierten Daten arbeiten, sollten Sie entweder eine relationale Datenbank wie SQLite oder einen nicht relationalen Schlüssel/Wert-Datenspeicher wie LevelDB in Betracht ziehen. Wenn Sie mit unstrukturierten Daten wie Audio-, Bild- oder Videomedien arbeiten, sollten Sie diese als Dateien speichern. Sie können verschiedene Speichertypen nach Belieben kombinieren und sie bei Bedarf über einen einzigen Contentanbieter verfügbar machen.
-
Das Android-System kann mit der Room-Persistenzbibliothek interagieren, die Zugriff auf die SQLite-Datenbank-API bietet, mit der die Anbieter von Android tabellenorientierte Daten speichern. Instanziieren Sie eine abgeleitete Klasse von
RoomDatabase
, wie unter Daten mit Room in einer lokalen Datenbank speichern beschrieben, um eine Datenbank mit dieser Bibliothek zu erstellen.Sie müssen keine Datenbank verwenden, um Ihr Repository zu implementieren. Ein Anbieter wird extern als ein Satz von Tabellen angezeigt, ähnlich wie eine relationale Datenbank. Dies ist jedoch keine Voraussetzung für die interne Implementierung des Anbieters.
- Android verfügt zum Speichern von Dateidaten über eine Vielzahl dateiorientierter APIs. Weitere Informationen zum Dateispeicher finden Sie unter Daten- und Dateispeicher – Übersicht. Wenn Sie einen Anbieter entwerfen, der medienbezogene Daten wie Musik oder Videos anbietet, können Sie einen Anbieter haben, der Tabellendaten und Dateien kombiniert.
- In seltenen Fällen kann es von Vorteil sein, mehr als einen Contentanbieter für eine einzelne Anwendung zu implementieren. Sie können beispielsweise einige Daten für ein Widget über einen Contentanbieter freigeben und einen anderen Datensatz für die Freigabe an andere Anwendungen bereitstellen.
-
Verwenden Sie für die Arbeit mit netzwerkbasierten Daten Klassen in
java.net
undandroid.net
. Sie können auch netzwerkbasierte Daten mit einem lokalen Datenspeicher wie einer Datenbank synchronisieren und die Daten dann als Tabellen oder Dateien anbieten.
Hinweis: Wenn Sie eine Änderung an Ihrem Repository vornehmen, die nicht abwärtskompatibel ist, müssen Sie das Repository mit einer neuen Versionsnummer markieren. Außerdem musst du die Versionsnummer für deine App erhöhen, in der der neue Inhaltsanbieter implementiert ist. Durch diese Änderung wird verhindert, dass das System durch Downgrades abstürzt, wenn es versucht, eine App mit einem inkompatiblen Contentanbieter neu zu installieren.
Überlegungen zum Datendesign
Hier sind einige Tipps zum Entwerfen der Datenstruktur Ihres Anbieters:
-
Tabellendaten müssen immer eine Primärschlüsselspalte haben, die vom Anbieter als eindeutigen numerischen Wert für jede Zeile verwaltet wird. Sie können diesen Wert verwenden, um die Zeile mit verwandten Zeilen in anderen Tabellen zu verknüpfen (als „Fremdschlüssel“). Sie können einen beliebigen Namen für diese Spalte verwenden. Die Verwendung von
BaseColumns._ID
ist jedoch die beste Wahl, da eine der abgerufenen Spalten den Namen_ID
haben muss, um die Ergebnisse einer Anbieterabfrage mit einerListView
zu verknüpfen. -
Wenn Sie Bitmapbilder oder andere sehr große dateiorientierte Daten bereitstellen möchten, speichern Sie die Daten in einer Datei und stellen Sie sie dann indirekt bereit, anstatt sie direkt in einer Tabelle zu speichern. In diesem Fall müssen Sie die Nutzer Ihres Anbieters darüber informieren, dass sie eine
ContentResolver
-Dateimethode für den Zugriff auf die Daten verwenden müssen. -
Verwenden Sie den BLOB-Datentyp (Binary Large Object), um Daten zu speichern, die unterschiedlich groß oder unterschiedlich strukturiert sind. Beispielsweise können Sie eine BLOB-Spalte verwenden, um einen Protokollpuffer oder eine JSON-Struktur zu speichern.
Sie können einen BLOB auch verwenden, um eine schemaunabhängige Tabelle zu implementieren. In diesem Tabellentyp definieren Sie eine Primärschlüsselspalte, eine Spalte des MIME-Typs und eine oder mehrere allgemeine Spalten als BLOB. Die Bedeutung der Daten in den BLOB-Spalten wird durch den Wert in der Spalte „MIME-Typ“ angegeben. So können Sie verschiedene Zeilentypen in derselben Tabelle speichern. Die „Daten“-Tabelle
ContactsContract.Data
des Kontaktanbieters ist ein Beispiel für eine schemaunabhängige Tabelle.
Inhalts-URIs entwerfen
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 oder Datei verweist (ein Pfad). Der optionale ID-Teil verweist auf eine einzelne Zeile in einer Tabelle. Jede Datenzugriffsmethode von ContentProvider
hat einen Inhalts-URI als Argument. So legen Sie fest, auf welche Tabelle, Zeile oder Datei zugegriffen werden soll.
Informationen zu Inhalts-URIs finden Sie unter Grundlagen von Contentanbietern.
Befugnis entwickeln
Ein Anbieter hat in der Regel eine einzige Autorität, die als interner Android-Name dient. Zur Vermeidung von Konflikten mit anderen Anbietern sollten Sie die Internetdomain-Eigentumsrechte (umgekehrt) als Grundlage Ihrer Anbieterautorität verwenden. Da diese Empfehlung auch für Android-Paketnamen gilt, können Sie Ihre Anbieterautorität als Erweiterung des Namens des Pakets definieren, das den Anbieter enthält.
Wenn Ihr Android-Paketname beispielsweise com.example.<appname>
lautet, weisen Sie Ihrem Anbieter die Berechtigung com.example.<appname>.provider
zu.
Eine Pfadstruktur entwerfen
Entwickler erstellen in der Regel Inhalts-URIs aus der Zertifizierungsstelle, indem sie Pfade anhängen, die auf einzelne Tabellen verweisen. Wenn Sie beispielsweise die beiden Tabellen table1 und table2 haben, können Sie diese mit der Autorität aus dem vorherigen Beispiel kombinieren, um die Inhalts-URIs com.example.<appname>.provider/table1
und com.example.<appname>.provider/table2
zu erhalten. Pfade sind nicht auf ein einzelnes Segment beschränkt und es muss nicht für jede Ebene des Pfads eine Tabelle vorhanden sein.
Inhalts-URI-IDs verarbeiten
Konventionsgemäß bieten Anbieter Zugriff auf eine einzelne Zeile in einer Tabelle, indem sie einen Inhalts-URI mit einem ID-Wert für die Zeile am Ende des URI akzeptieren. Konventionsgemäß gleichen Anbieter den ID-Wert mit der Spalte _ID
der Tabelle ab und führen den angeforderten Zugriff für die entsprechende Zeile durch.
Diese Konvention ermöglicht ein gemeinsames Designmuster für Anwendungen, die auf einen Anbieter zugreifen. Die Anwendung fragt den Anbieter ab und zeigt das resultierende Cursor
in einer ListView
mithilfe eines CursorAdapter
an.
Gemäß der Definition von CursorAdapter
muss eine der Spalten im Cursor
den Wert _ID
haben
Der Nutzer wählt dann eine der angezeigten Zeilen aus der Benutzeroberfläche aus, um die Daten anzusehen oder zu ändern. Die Anwendung ruft die entsprechende Zeile aus dem Cursor
ab, die den ListView
-Wert unterstützt, ruft den _ID
-Wert für diese Zeile ab, hängt ihn an den Inhalts-URI an und sendet die Zugriffsanfrage an den Anbieter. Der Anbieter kann dann die Abfrage oder Änderung genau für die vom Nutzer ausgewählte Zeile vornehmen.
URI-Muster des Inhalts
Damit Sie leichter auswählen können, welche Aktion bei einem eingehenden Inhalts-URI ausgeführt werden soll, enthält die Anbieter-API die Convenience-Klasse UriMatcher
, die Inhalts-URI-Muster ganzzahligen Werten zuordnet. Sie können die Ganzzahlwerte in einer switch
-Anweisung verwenden, die die gewünschte Aktion für die Inhalts-URIs oder URIs auswählt, die einem bestimmten Muster entsprechen.
Ein Inhalts-URI-Muster gleicht Inhalts-URIs mit Platzhalterzeichen ab:
-
*
entspricht einem String mit beliebigen gültigen Zeichen beliebiger Länge. -
#
entspricht einem String aus numerischen Zeichen beliebiger Länge.
Als Beispiel für das Entwerfen und Codieren der Verarbeitung von Inhalts-URIs eignet sich ein Anbieter mit der Berechtigung com.example.app.provider
, der die folgenden Inhalts-URIs erkennt, die auf Tabellen verweisen:
-
content://com.example.app.provider/table1
: eine Tabelle mit dem Namentable1
. -
content://com.example.app.provider/table2/dataset1
: eine Tabelle mit dem Namendataset1
. -
content://com.example.app.provider/table2/dataset2
: eine Tabelle mit dem Namendataset2
. -
content://com.example.app.provider/table3
: eine Tabelle mit dem Namentable3
.
Der Anbieter erkennt diese Inhalts-URIs auch, wenn ihnen eine Zeilen-ID angehängt ist, z. B. content://com.example.app.provider/table3/1
für die durch 1
in table3
identifizierte Zeile.
Folgende Inhalts-URI-Muster sind möglich:
-
content://com.example.app.provider/*
- Stimmt mit jedem Inhalts-URI im Anbieter überein.
-
content://com.example.app.provider/table2/*
- Stimmt mit einem Inhalts-URI für die Tabellen
dataset1
unddataset2
überein, aber nicht mit Inhalts-URIs fürtable1
odertable3
. -
content://com.example.app.provider/table3/#
- Entspricht einem Inhalts-URI für einzelne Zeilen in
table3
, z. B.content://com.example.app.provider/table3/6
für die durch6
identifizierte Zeile.
Das folgende Code-Snippet zeigt, wie die Methoden in UriMatcher
funktionieren.
Dieser Code behandelt URIs für eine ganze Tabelle anders als URIs für eine einzelne Zeile. Dazu wird das Inhalts-URI-Muster content://<authority>/<path>
für Tabellen und content://<authority>/<path>/<id>
für einzelne Zeilen verwendet.
Die Methode addURI()
ordnet einem Ganzzahlwert eine Behörde und einen Pfad zu. Die Methode match()
gibt den ganzzahligen Wert für einen URI zurück. Mit einer switch
-Anweisung wird zwischen der Abfrage der gesamten Tabelle und der Abfrage eines einzelnen Eintrags ausgewählt.
Kotlin
private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { /* * The calls to addURI() go here for all the content URI patterns that the provider * recognizes. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path. */ addURI("com.example.app.provider", "table3", 1) /* * Sets the code for a single row to 2. In this case, the # wildcard is * used. content://com.example.app.provider/table3/3 matches, but * content://com.example.app.provider/table3 doesn't. */ addURI("com.example.app.provider", "table3/#", 2) } ... class ExampleProvider : ContentProvider() { ... // Implements ContentProvider.query() override fun query( uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { var localSortOrder: String = sortOrder ?: "" var localSelection: String = selection ?: "" when (sUriMatcher.match(uri)) { 1 -> { // If the incoming URI was for all of table3 if (localSortOrder.isEmpty()) { localSortOrder = "_ID ASC" } } 2 -> { // If the incoming URI was for a single row /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query. */ localSelection += "_ID ${uri?.lastPathSegment}" } else -> { // If the URI isn't recognized, // do some error handling here } } // Call the code to actually do the query } }
Java
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { /* * The calls to addURI() go here for all the content URI patterns that the provider * recognizes. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to one. No wildcard is used * in the path. */ uriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the # wildcard is * used. content://com.example.app.provider/table3/3 matches, but * content://com.example.app.provider/table3 doesn't. */ uriMatcher.addURI("com.example.app.provider", "table3/#", 2); } ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (uriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query. */ selection = selection + "_ID = " + uri.getLastPathSegment(); break; default: ... // If the URI isn't recognized, do some error handling here } // Call the code to actually do the query }
Eine weitere Klasse, ContentUris
, bietet praktische Methoden zum Arbeiten mit dem Teil id
von Inhalts-URIs. Die Klassen Uri
und Uri.Builder
bieten praktische Methoden zum Parsen vorhandener Uri
-Objekte und zum Erstellen neuer Objekte.
ContentProvider-Klasse implementieren
Die Instanz ContentProvider
verwaltet den Zugriff auf einen strukturierten Satz von Daten durch die Verarbeitung von Anfragen von anderen Anwendungen. Bei allen Arten von Zugriff wird letztendlich ContentResolver
aufgerufen, das dann eine konkrete ContentProvider
-Methode aufruft, um Zugriff zu erhalten.
Erforderliche Methoden
Die abstrakte Klasse ContentProvider
definiert sechs abstrakte Methoden, die Sie als Teil Ihrer konkreten Unterklasse implementieren. Alle diese Methoden außer onCreate()
werden von einer Clientanwendung aufgerufen, die versucht, auf Ihren Inhaltsanbieter zuzugreifen.
-
query()
-
Daten von Ihrem Anbieter abrufen. Wählen Sie mithilfe der Argumente die abzufragende Tabelle, die zurückzugebenden Zeilen und Spalten sowie die Sortierreihenfolge des Ergebnisses aus.
Gibt die Daten als
Cursor
-Objekt zurück. -
insert()
- Fügen Sie eine neue Zeile in Ihren Anbieter ein. Verwenden Sie die Argumente, um die Zieltabelle auszuwählen und die zu verwendenden Spaltenwerte abzurufen. Gibt einen Inhalts-URI für die neu eingefügte Zeile zurück.
-
update()
- Aktualisieren Sie vorhandene Zeilen bei Ihrem Anbieter. Verwenden Sie die Argumente, um die Tabelle und die Zeilen auszuwählen, die aktualisiert werden sollen, und die aktualisierten Spaltenwerte abzurufen. Gibt die Anzahl der aktualisierten Zeilen zurück.
-
delete()
- Löschen Sie Zeilen bei Ihrem Anbieter. Verwenden Sie die Argumente, um die Tabelle und die zu löschenden Zeilen auszuwählen. Gibt die Anzahl der gelöschten Zeilen zurück.
-
getType()
- Gibt den MIME-Typ zurück, der einem Inhalts-URI entspricht. Diese Methode wird im Abschnitt MIME-Typen des Contentanbieters implementieren ausführlicher beschrieben.
-
onCreate()
-
Initialisieren Sie Ihren Anbieter. Das Android-System ruft diese Methode sofort auf, nachdem der Anbieter erstellt wurde. Der Anbieter wird erst erstellt, wenn ein
ContentResolver
-Objekt versucht, darauf zuzugreifen.
Diese Methoden haben die gleiche Signatur wie die identisch benannten ContentResolver
-Methoden.
Bei der Implementierung dieser Methoden muss Folgendes berücksichtigt werden:
-
Alle diese Methoden außer
onCreate()
können von mehreren Threads gleichzeitig aufgerufen werden. Sie müssen daher Thread-sicher sein. Weitere Informationen zu mehreren Threads finden Sie unter Prozesse und Threads – Übersicht. -
Vermeiden Sie langwierige Vorgänge in
onCreate()
. Initialisierungsaufgaben auf später verschieben, bis sie tatsächlich benötigt werden. Im Abschnitt zur Implementierung der onCreate()-Methode wird dies ausführlicher behandelt. -
Diese Methoden müssen zwar implementiert werden, der Code muss aber nichts weiter tun, außer den erwarteten Datentyp zurückzugeben. Wenn Sie beispielsweise verhindern möchten, dass andere Anwendungen Daten in einige Tabellen einfügen, ignorieren Sie den Aufruf von
insert()
und geben Sie 0 zurück.
Die Methode „query()“ implementieren
Die Methode ContentProvider.query()
muss ein Cursor
-Objekt zurückgeben oder bei einem Fehler ein Exception
auslösen. Wenn Sie eine SQLite-Datenbank als Datenspeicher verwenden, können Sie das Cursor
zurückgeben, das von einer der query()
-Methoden der SQLiteDatabase
-Klasse zurückgegeben wird.
Wenn die Abfrage mit keinen Zeilen übereinstimmt, wird eine Cursor
-Instanz zurückgegeben, deren getCount()
-Methode 0 zurückgibt.
Geben Sie null
nur zurück, wenn während des Abfrageprozesses ein interner Fehler aufgetreten ist.
Wenn Sie keine SQLite-Datenbank als Datenspeicher verwenden, verwenden Sie eine der konkreten abgeleiteten Klassen von Cursor
. Die Klasse MatrixCursor
implementiert beispielsweise einen Cursor, bei dem jede Zeile ein Array von Object
-Instanzen ist. Verwenden Sie bei dieser Klasse addRow()
, um eine neue Zeile hinzuzufügen.
Das Android-System muss die Exception
über Prozessgrenzen hinweg kommunizieren können. Android kann dies für die folgenden Ausnahmen tun, die für die Verarbeitung von Abfragefehlern hilfreich sind:
-
IllegalArgumentException
. Sie können diese Meldung auslösen, wenn Ihr Anbieter einen ungültigen Inhalts-URI erhält. -
NullPointerException
Die insert()-Methode implementieren
Die Methode insert()
fügt der entsprechenden Tabelle eine neue Zeile hinzu. Dazu werden die Werte im Argument ContentValues
verwendet. Wenn ein Spaltenname nicht im Argument ContentValues
enthalten ist, sollten Sie entweder in Ihrem Anbietercode oder in Ihrem Datenbankschema einen Standardwert dafür angeben.
Diese Methode gibt den Inhalts-URI für die neue Zeile zurück. Dazu hängen Sie den Primärschlüssel der neuen Zeile, normalerweise den Wert _ID
, mithilfe von withAppendedId()
an den Inhalts-URI der Tabelle an.
Methode „delete()“ implementieren
Bei der Methode delete()
müssen keine Zeilen aus dem Datenspeicher gelöscht werden. Wenn Sie einen Synchronisierungsadapter bei Ihrem Anbieter verwenden, sollten Sie eine gelöschte Zeile mit dem Flag „delete“ markieren, anstatt sie vollständig zu entfernen. Der Synchronisierungsadapter kann nach gelöschten Zeilen suchen und sie vom Server entfernen, bevor sie vom Anbieter gelöscht werden.
Methode „update()“ implementieren
Die Methode update()
verwendet das gleiche ContentValues
-Argument, das von insert()
verwendet wird, sowie die gleichen selection
- und selectionArgs
-Argumente, die von delete()
und ContentProvider.query()
verwendet werden.
So können Sie Code für diese Methoden wiederverwenden.
onCreate()-Methode implementieren
Das Android-System ruft onCreate()
auf, wenn es den Anbieter startet. Führen Sie mit dieser Methode nur schnell laufende Initialisierungsaufgaben aus und verschieben Sie die Datenbankerstellung und das Laden der Daten, bis der Anbieter tatsächlich eine Anfrage für die Daten erhält. Wenn Sie langwierige Aufgaben in onCreate()
ausführen, verlangsamen Sie den Start Ihres Anbieters. Dies wiederum verlangsamt die Antwort des Anbieters auf andere Anwendungen.
Die folgenden beiden Snippets veranschaulichen die Interaktion zwischen ContentProvider.onCreate()
und
Room.databaseBuilder()
. Das erste Snippet zeigt die Implementierung von ContentProvider.onCreate()
, bei der das Datenbankobjekt erstellt und die Handles für die Datenzugriffsobjekte erstellt werden:
Kotlin
// Defines the database name private const val DBNAME = "mydb" ... class ExampleProvider : ContentProvider() { // Defines a handle to the Room database private lateinit var appDatabase: AppDatabase // Defines a Data Access Object to perform the database operations private var userDao: UserDao? = null override fun onCreate(): Boolean { // Creates a new database object appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build() // Gets a Data Access Object to perform the database operations userDao = appDatabase.userDao return true } ... // Implements the provider's insert method override fun insert(uri: Uri, values: ContentValues?): Uri? { // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc. } }
Java
public class ExampleProvider extends ContentProvider // Defines a handle to the Room database private AppDatabase appDatabase; // Defines a Data Access Object to perform the database operations private UserDao userDao; // Defines the database name private static final String DBNAME = "mydb"; public boolean onCreate() { // Creates a new database object appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build(); // Gets a Data Access Object to perform the database operations userDao = appDatabase.getUserDao(); return true; } ... // Implements the provider's insert method public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc. } }
ContentProvider-MIME-Typen implementieren
Die Klasse ContentProvider
hat zwei Methoden zum Zurückgeben von MIME-Typen:
-
getType()
- Eine der erforderlichen Methoden, die Sie für alle Anbieter implementieren.
-
getStreamTypes()
- Eine Methode, die Sie implementieren müssen, wenn Ihr Anbieter Dateien anbietet.
MIME-Typen für Tabellen
Die Methode getType()
gibt eine String
im MIME-Format zurück, die den Typ der vom Inhalts-URI-Argument zurückgegebenen Daten beschreibt. Das Argument Uri
kann ein Muster und kein bestimmter URI sein.
Geben Sie in diesem Fall den Datentyp zurück, der mit Inhalts-URIs verknüpft ist, die dem Muster entsprechen.
Für gängige Datentypen wie Text, HTML oder JPEG gibt getType()
den Standard-MIME-Typ für diese Daten zurück. Eine vollständige Liste dieser Standardtypen finden Sie auf der Website zu den IANA-MIME-Medientypen.
Für Inhalts-URIs, die auf eine oder mehrere Zeilen mit Tabellendaten verweisen, gibt getType()
einen MIME-Typ im anbieterspezifischen MIME-Format von Android zurück:
-
Teil eingeben:
vnd
-
Teil des Untertyps:
-
Wenn das URI-Muster für eine einzelne Zeile gilt:
android.cursor.item/
-
Wenn das URI-Muster für mehr als eine Zeile gilt:
android.cursor.dir/
-
Wenn das URI-Muster für eine einzelne Zeile gilt:
-
Anbieterspezifischer Teil:
vnd.<name>
.<type>
Sie geben
<name>
und<type>
an. Der Wert<name>
ist global eindeutig und der Wert<type>
für das entsprechende URI-Muster eindeutig. Eine gute Wahl für<name>
ist der Name Ihres Unternehmens oder ein Teil des Android-Paketnamens Ihrer App. Eine gute Wahl für<type>
ist ein String, mit dem die mit dem URI verknüpfte Tabelle identifiziert wird.
Wenn die Berechtigung eines Anbieters beispielsweise com.example.app.provider
ist und eine Tabelle mit dem Namen table1
verfügbar gemacht wird, lautet der MIME-Typ für mehrere Zeilen in table1
:
vnd.android.cursor.dir/vnd.com.example.provider.table1
Für eine einzelne Zeile von table1
lautet der MIME-Typ:
vnd.android.cursor.item/vnd.com.example.provider.table1
MIME-Typen für Dateien
Wenn dein Anbieter Dateien anbietet, implementiere getStreamTypes()
.
Die Methode gibt ein String
-Array mit MIME-Typen für die Dateien zurück, die Ihr Anbieter für einen bestimmten Inhalts-URI zurückgeben kann. Filtern Sie die von Ihnen angebotenen MIME-Typen mit dem Argument für den MIME-Typ-Filter, sodass Sie nur die MIME-Typen zurückgeben, die der Client verarbeiten möchte.
Nehmen wir beispielsweise einen Anbieter, der Fotobilder als Dateien im JPG-, PNG- und GIF-Format anbietet.
Wenn eine Anwendung ContentResolver.getStreamTypes()
mit dem Filterstring image/*
für etwas, das ein „Bild“ ist, aufruft, gibt die Methode ContentProvider.getStreamTypes()
das Array zurück:
{ "image/jpeg", "image/png", "image/gif"}
Wenn für die Anwendung nur JPG-Dateien benötigt werden, kann sie ContentResolver.getStreamTypes()
mit dem Filterstring *\/jpeg
aufrufen. getStreamTypes()
gibt dann Folgendes zurück:
{"image/jpeg"}
Wenn Ihr Anbieter einen der im Filterstring angeforderten MIME-Typen nicht anbietet, gibt getStreamTypes()
den Wert null
zurück.
Vertragsklasse implementieren
Eine Vertragsklasse ist eine public final
-Klasse, die konstante Definitionen für die URIs, Spaltennamen, MIME-Typen und andere Metadaten enthält, die zum Anbieter gehören. Die Klasse schließt einen Vertrag zwischen dem Anbieter und anderen Anwendungen. Sie sorgt dafür, dass auf den Anbieter auch dann korrekt zugegriffen werden kann, wenn sich die tatsächlichen Werte von URIs, Spaltennamen usw. ändern.
Eine Vertragsklasse ist auch für Entwickler hilfreich, da sie in der Regel mnemonische Namen für ihre Konstanten hat, sodass Entwickler die Wahrscheinlichkeit verringern, dass sie falsche Werte für Spaltennamen oder URIs verwenden. Da es sich um eine Klasse handelt, kann sie Javadoc-Dokumentation enthalten. Integrierte Entwicklungsumgebungen wie Android Studio können Konstantennamen aus der Vertragsklasse automatisch vervollständigen und Javadoc für die Konstanten anzeigen lassen.
Entwickler können über Ihre Anwendung nicht auf die Klassendatei der Vertragsklasse zugreifen, können sie jedoch über eine von Ihnen bereitgestellte JAR-Datei statisch in ihre Anwendung kompilieren.
Die ContactsContract
-Klasse und ihre verschachtelten Klassen sind Beispiele für Vertragsklassen.
Contentanbieterberechtigungen implementieren
Berechtigungen und der Zugriff für alle Aspekte des Android-Systems werden ausführlich in Sicherheitstipps beschrieben. Unter Daten- und Dateispeicher – Übersicht werden außerdem die Sicherheitseinstellungen und Berechtigungen für verschiedene Speichertypen beschrieben. Hier noch einmal die wichtigsten Punkte:
- Standardmäßig sind Datendateien, die im internen Speicher des Geräts gespeichert sind, nur für Ihre Anwendung und Ihren Anbieter zugänglich.
-
Die von Ihnen erstellten
SQLiteDatabase
-Datenbanken sind nur für Ihre Anwendung und Ihren Anbieter zugänglich. - Standardmäßig sind Datendateien, die Sie im externen Speicher speichern, öffentlich und weltweit lesbar. Sie können keinen Contentanbieter verwenden, um den Zugriff auf Dateien im externen Speicher einzuschränken, da andere Anwendungen andere API-Aufrufe zum Lesen und Schreiben verwenden können.
- Die Methodenaufrufe zum Öffnen oder Erstellen von Dateien oder SQLite-Datenbanken im internen Speicher des Geräts können Lese- und Schreibzugriff auf alle anderen Anwendungen gewähren. Wenn Sie eine interne Datei oder Datenbank als Repository des Anbieters verwenden und ihr Zugriff „für alle Nutzer lesbar“ oder „schreibgeschützt“ gewähren, schützen die Berechtigungen, die Sie in seinem Manifest für Ihren Anbieter festlegen, Ihre Daten nicht. Der Standardzugriff auf Dateien und Datenbanken im internen Speicher ist „privat“. Ändern Sie diesen Zugriff für das Repository Ihres Anbieters nicht.
Wenn Sie die Berechtigungen von Contentanbietern verwenden möchten, um den Zugriff auf Ihre Daten zu steuern, sollten Sie die Daten in internen Dateien, SQLite-Datenbanken oder in der Cloud (z. B. auf einem Remoteserver) speichern und Dateien und Datenbanken nur für Ihre Anwendung zugänglich halten.
Berechtigungen implementieren
Standardmäßig können alle Anwendungen von Ihrem Anbieter lesen oder schreiben, auch wenn die zugrunde liegenden Daten privat sind, da Ihr Anbieter standardmäßig keine Berechtigungen hat. Wenn du dies ändern möchtest, lege in der Manifestdatei Berechtigungen für deinen Anbieter fest. Verwende dazu Attribute oder untergeordnete Elemente des Elements
<provider>
. Sie können Berechtigungen festlegen, die für den gesamten Anbieter, für bestimmte Tabellen, für bestimmte Datensätze oder für alle drei gelten.
Berechtigungen für deinen Anbieter werden mit einem oder mehreren
<permission>
-Elementen in deiner Manifestdatei definiert. Damit die Berechtigung für Ihren Anbieter eindeutig ist, verwenden Sie für das Attribut
android:name
einen Umfang im Java-Stil. Nennen Sie die Leseberechtigung beispielsweise com.example.app.provider.permission.READ_PROVIDER
.
In der folgenden Liste wird der Umfang der Anbieterberechtigungen beschrieben. Beginnend mit den Berechtigungen, die für den gesamten Anbieter gelten, werden diese dann detaillierter. Detailliertere Berechtigungen haben Vorrang vor Berechtigungen mit größerem Umfang.
- Einzelne Lese-/Schreibberechtigung auf Anbieterebene
-
Eine Berechtigung, die sowohl den Lese- als auch den Schreibzugriff auf den gesamten Anbieter steuert und mit dem Attribut
android:permission
des Elements<provider>
angegeben wird. - Separate Lese- und Schreibberechtigungen auf Anbieterebene
-
Eine Lese- und eine Schreibberechtigung für den gesamten Anbieter. Sie geben sie mit den Attributen
android:readPermission
undandroid:writePermission
des Elements<provider>
an. Sie haben Vorrang vor der fürandroid:permission
erforderlichen Berechtigung. - Berechtigung auf Pfadebene
-
Lese-, Schreib- oder Lese-/Schreibberechtigung für einen Inhalts-URI bei Ihrem Anbieter. Sie geben jeden URI, den Sie steuern möchten, mit einem untergeordneten
<path-permission>
-Element des<provider>
-Elements an. Für jeden von Ihnen angegebenen Inhalts-URI können Sie eine Lese-/Schreibberechtigung, eine Leseberechtigung, eine Schreibberechtigung oder alle drei Berechtigungen angeben. Die Lese- und Schreibberechtigungen haben Vorrang vor den Lese-/Schreibberechtigungen. Außerdem haben Berechtigungen auf Pfadebene Vorrang vor Berechtigungen auf Anbieterebene. - Temporäre Berechtigung
-
Eine Berechtigungsstufe, die temporären Zugriff auf eine Anwendung gewährt, auch wenn die Anwendung nicht die Berechtigungen hat, die normalerweise erforderlich sind. Durch das Feature für den temporären Zugriff wird die Anzahl der Berechtigungen reduziert, die eine App in ihrem Manifest anfordern muss. Wenn Sie temporäre Berechtigungen aktivieren, benötigen nur die Anwendungen dauerhafte Berechtigungen für Ihren Anbieter, die kontinuierlich auf alle Ihre Daten zugreifen.
Überlegen Sie beispielsweise, welche Berechtigungen Sie benötigen, wenn Sie einen E-Mail-Anbieter und eine App implementieren und zulassen möchten, dass eine externe Bildanzeige Anhänge Ihres Anbieters anzeigt. Um der Bildanzeige den erforderlichen Zugriff zu gewähren, ohne dass entsprechende Berechtigungen erforderlich sind, können Sie temporäre Berechtigungen für Inhalts-URIs für Fotos einrichten.
Gestalten Sie Ihre E-Mail-App so, dass, wenn der Nutzer ein Foto anzeigen möchte, die App einen Intent mit dem Inhalts-URI des Fotos und Berechtigungs-Flags an die Bildanzeige sendet. Die Bildanzeige kann Ihren E-Mail-Anbieter dann abfragen, um das Foto abzurufen, auch wenn der Betrachter nicht über die normale Leseberechtigung für Ihren Anbieter verfügt.
Wenn du temporäre Berechtigungen aktivieren möchtest, musst du entweder das Attribut
android:grantUriPermissions
des Elements<provider>
festlegen oder dem Element<provider>
mindestens ein untergeordnetes<grant-uri-permission>
-Element hinzufügen. Rufen SieContext.revokeUriPermission()
immer dann auf, wenn Sie die Unterstützung für einen Inhalts-URI entfernen, der mit einer temporären Berechtigung von Ihrem Anbieter verknüpft ist.Der Wert des Attributs bestimmt, wie viel von Ihrem Anbieter zugänglich ist. Wenn das Attribut auf
"true"
gesetzt ist, gewährt das System Ihrem gesamten Anbieter eine vorübergehende Berechtigung und überschreibt alle anderen Berechtigungen, die für Ihre Berechtigungen auf Anbieter- oder Pfadebene erforderlich sind.Wenn dieses Flag auf
"false"
gesetzt ist, fügen Sie dem<provider>
-Unterelement<grant-uri-permission>
untergeordnete Elemente hinzu. Jedes untergeordnete Element gibt den Inhalts-URI oder die URIs an, für die vorübergehend Zugriff gewährt wird.Zum Delegieren des temporären Zugriffs auf eine Anwendung muss ein Intent das Flag
FLAG_GRANT_READ_URI_PERMISSION
, das FlagFLAG_GRANT_WRITE_URI_PERMISSION
oder beides enthalten. Diese werden mit der MethodesetFlags()
festgelegt.Wenn das Attribut
android:grantUriPermissions
nicht vorhanden ist, wird angenommen, dass es"false"
ist.
Das <provider>-Element
Wie die Activity
- und Service
-Komponenten wird eine abgeleitete Klasse von ContentProvider
in der Manifestdatei der zugehörigen Anwendung mit dem Element
<provider>
definiert. Das Android-System erhält die folgenden Informationen vom Element:
-
Behörde
(
android:authorities
) - Symbolische Namen, die den gesamten Anbieter innerhalb des Systems identifizieren. Dieses Attribut wird im Abschnitt Designinhalts-URIs ausführlicher beschrieben.
-
Name der Anbieterklasse (
android:name
) -
Die Klasse, die
ContentProvider
implementiert. Diese Klasse wird im Abschnitt ContentProvider-Klasse implementieren ausführlicher beschrieben. - Berechtigungen
-
Attribute, die die Berechtigungen angeben, die andere Anwendungen haben müssen, um auf die Daten des Anbieters zuzugreifen:
-
android:grantUriPermissions
: temporäres Berechtigungs-Flag -
android:permission
: Lese-/Schreibberechtigung für einen einzigen Anbieter -
android:readPermission
: anbieterweite Leseberechtigung -
android:writePermission
: anbieterweite Schreibberechtigung
Berechtigungen und die zugehörigen Attribute werden im Abschnitt Inhaltsanbieterberechtigungen implementieren ausführlicher beschrieben.
-
- Start- und Steuerattribute
-
Diese Attribute bestimmen, wie und wann das Android-System den Anbieter startet, die Prozessmerkmale des Anbieters und andere Laufzeiteinstellungen:
-
android:enabled
: Flag, mit dem das System den Anbieter starten kann -
android:exported
: Flag, mit dem andere Anwendungen diesen Anbieter verwenden können -
android:initOrder
: die Reihenfolge, in der dieser Anbieter gestartet wird, im Verhältnis zu anderen Anbietern im selben Prozess -
android:multiProcess
: Flag, mit dem das System den Anbieter im selben Prozess wie der aufrufende Client starten kann -
android:process
: Name des Prozesses, in dem der Anbieter ausgeführt wird -
android:syncable
: Flag, das angibt, dass die Daten des Anbieters mit Daten auf einem Server synchronisiert werden sollen
Diese Attribute sind im Leitfaden zum
<provider>
-Element vollständig dokumentiert. -
- Informationsattribute
-
Optionales Symbol und Label für den Anbieter:
-
android:icon
: eine Drawable-Ressource mit einem Symbol für den Anbieter. Das Symbol wird in der App-Liste unter Einstellungen > Apps > Alle neben dem Label des Anbieters angezeigt. -
android:label
: ein Informationslabel, das den Anbieter, seine Daten oder beides beschreibt. Das Label wird in der App-Liste unter Einstellungen > Apps > Alle angezeigt.
Diese Attribute sind im Leitfaden zum
<provider>
-Element vollständig dokumentiert. -
Intents und Datenzugriff
Anwendungen können indirekt mit einer Intent
auf einen Contentanbieter zugreifen.
Die Anwendung ruft keine der Methoden ContentResolver
oder ContentProvider
auf. Stattdessen wird ein Intent gesendet, der eine Aktivität startet. Diese ist oft Teil der eigenen Anwendung des Anbieters. Die Zielaktivität ist für das Abrufen und Anzeigen der Daten in der UI zuständig.
Abhängig von der Aktion im Intent kann der Nutzer durch die Zielaktivität auch aufgefordert werden, Änderungen an den Daten des Anbieters vorzunehmen. Ein Intent kann auch „Extras“ enthalten, die von der Zielaktivität in der UI angezeigt werden. Der Nutzer hat dann die Möglichkeit, diese Daten zu ändern, bevor er damit die Daten beim Anbieter ändern kann.
Sie können den Intent-Zugriff für die Datenintegrität verwenden. Ihr Anbieter kann sich darauf verlassen, dass Daten gemäß einer genau definierten Geschäftslogik eingefügt, aktualisiert und gelöscht werden. Wenn dies der Fall ist, kann es zu ungültigen Daten kommen, wenn andere Anwendungen Ihre Daten direkt ändern lassen.
Wenn Sie möchten, dass Entwickler Intent-Zugriff verwenden, müssen Sie ihn sorgfältig dokumentieren. Erläutern Sie, warum der Intent-Zugriff über die Benutzeroberfläche Ihrer Anwendung besser ist als zu versuchen, die Daten mit ihrem Code zu ändern.
Die Verarbeitung eines eingehenden Intents, der die Daten Ihres Anbieters ändern möchte, unterscheidet sich nicht von der Verarbeitung anderer Intents. Weitere Informationen zur Verwendung von Intents finden Sie unter Intents und Intent-Filter.
Weitere Informationen finden Sie in der Übersicht zum Kalenderanbieter.