Contentanbieter erstellen

Ein Contentanbieter verwaltet den Zugriff auf ein zentrales Repository mit Daten. Anbieter implementieren Sie als eine oder mehrere Klassen in einer Android-App, zusammen mit Elementen in der Manifestdatei. Eine Ihrer Klassen implementiert eine abgeleitete Klasse von ContentProvider, der Schnittstelle zwischen Ihrem Anbieter und anderen Anwendungen.

Contentanbieter sollen Daten für andere Anwendungen zur Verfügung stellen. Sie können aber auch Aktivitäten in Ihrer Anwendung anbieten, mit denen Nutzer die von Ihrem Anbieter verwalteten Daten abfragen und ändern können.

Diese Seite enthält den grundlegenden Prozess zum Erstellen eines Contentanbieters und eine Liste der zu verwendenden APIs.

Bevor Sie mit dem Erstellen

Bevor Sie mit der Erstellung eines Anbieters beginnen, sollten Sie Folgendes berücksichtigen:

  • Entscheiden 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 es 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 für Widgets freigeben.
    • Sie möchten die Klassen AbstractThreadedSyncAdapter, CursorAdapter oder CursorLoader implementieren.

    Sie benötigen keinen Anbieter, um Datenbanken oder andere Arten von nichtflüchtigem Speicher zu verwenden, wenn die Verwendung vollständig durch Ihre eigene Anwendung erfolgt und Sie keine der oben aufgeführten Funktionen benötigen. Stattdessen können Sie eines der unter Daten- und Dateispeicher – Übersicht beschriebenen Speichersysteme verwenden.

  • Falls noch nicht geschehen, lesen Sie den Artikel Grundlagen von Contentanbietern, um mehr über Anbieter und ihre Funktionsweise zu erfahren.

Führen Sie als Nächstes die folgenden Schritte aus, um Ihren Anbieter zu erstellen:

  1. Entwerfen Sie den Rohdatenspeicher für Ihre Daten. Ein Contentanbieter stellt Daten auf zwei Arten zur Verfügung:
    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 ein Handle für die Datei anbieten.
    „Strukturierte“ Daten
    Daten, die normalerweise in eine Datenbank, ein Array oder eine ähnliche Struktur aufgenommen werden. Speichern Sie die Daten in einem Formular, das mit Tabellen mit Zeilen und Spalten kompatibel ist. Eine Zeile steht für eine Entität, z. B. eine Person oder einen Artikel im Inventar. Eine Spalte enthält 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 jede Art von persistentem Speicher verwenden. Weitere Informationen zu den im Android-System verfügbaren Speichertypen finden Sie im Abschnitt Datenspeicher gestalten.
  2. Definieren Sie eine konkrete Implementierung der ContentProvider-Klasse und ihrer erforderlichen Methoden. Diese Klasse ist die Schnittstelle zwischen Ihren Daten und dem Rest des Android-Systems. Weitere Informationen zu dieser Klasse findest du im Abschnitt ContentProvider-Klasse implementieren.
  3. Definieren Sie den Autorisierungsstring des Anbieters, die Inhalts-URIs und die Spaltennamen. Wenn die Anwendung des Anbieters Intents verarbeiten soll, müssen Sie auch Intent-Aktionen, Extradaten und Flags definieren. Definieren Sie außerdem die Berechtigungen, die Anwendungen benötigen, die auf Ihre Daten zugreifen möchten. Sie sollten alle diese Werte als Konstanten in einer separaten Vertragsklasse definieren. Später können Sie diese Klasse für andere Entwickler freigeben. Weitere Informationen zu Inhalts-URIs finden Sie im Abschnitt Inhalts-URIs entwerfen. Weitere Informationen zu Intents finden Sie im Abschnitt Intents und Datenzugriff.
  4. Fügen Sie weitere optionale Informationen hinzu, z. B. Beispieldaten oder eine Implementierung von AbstractThreadedSyncAdapter, die Daten zwischen dem Anbieter und cloudbasierten Daten synchronisieren können.

Datenspeicherung entwerfen

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.

Für Android stehen unter anderem die folgenden Datenspeichertechnologien zur Verfügung:

  • Wenn Sie mit strukturierten Daten arbeiten, sollten Sie entweder eine relationale Datenbank wie SQLite oder einen nicht relationalen Datenspeicher mit Schlüssel/Wert-Paaren wie LevelDB in Betracht ziehen. Wenn Sie mit unstrukturierten Daten wie Audio-, Bild- oder Videomedien arbeiten, empfiehlt es sich, die Daten als Dateien zu speichern. Sie können verschiedene Speichertypen mischen und abgleichen und bei Bedarf über einen einzigen Inhaltsanbieter 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 zum Erstellen einer Datenbank mit dieser Bibliothek eine abgeleitete Klasse von RoomDatabase, wie unter Daten mithilfe von Raum in einer lokalen Datenbank speichern beschrieben.

    Sie müssen Ihr Repository nicht mit einer Datenbank implementieren. Ein Anbieter wird extern als ein Satz von Tabellen angezeigt, ähnlich wie bei einer relationalen Datenbank. Dies ist jedoch keine Voraussetzung für die interne Implementierung des Anbieters.

  • Zum Speichern von Dateidaten bietet Android eine Vielzahl von dateiorientierten APIs. Weitere Informationen zum Dateispeicher finden Sie unter Daten- und Dateispeicher – Übersicht. Wenn Sie einen Anbieter entwerfen, der medienbezogene Daten wie Musik oder Videos bereitstellt, können Sie einen Anbieter verwenden, 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. Beispielsweise können Sie einige Daten für ein Widget über einen Contentanbieter freigeben und andere Daten für die Freigabe an andere Anwendungen.
  • Verwenden Sie Klassen in java.net und android.net, um mit netzwerkbasierten Daten zu arbeiten. 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 Contentanbieter implementiert ist. Durch diese Änderung wird verhindert, dass das System durch Downgrades des Systems abstürzt, wenn versucht wird, 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 eindeutiger numerischer 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. Dabei wird er als "Fremdschlüssel" verwendet. Sie können für diese Spalte einen beliebigen Namen 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 einer ListView 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 zur Verfügung, anstatt sie direkt in einer Tabelle zu speichern. In diesem Fall müssen Sie die Nutzer Ihres Anbieters darüber informieren, dass sie für den Zugriff auf die Daten die Dateimethode ContentResolver verwenden müssen.
  • Verwenden Sie den BLOB-Datentyp (Binary Large Object) zum Speichern von Daten mit unterschiedlicher Größe oder unterschiedlicher Struktur. Sie können beispielsweise eine BLOB-Spalte verwenden, um einen Protokollpuffer oder eine JSON-Struktur zu speichern.

    Mit einem BLOB können Sie auch eine schemaunabhängige Tabelle implementieren. Bei diesem Tabellentyp definieren Sie eine Primärschlüsselspalte, eine Spalte vom Typ MIME sowie eine oder mehrere generische Spalten als BLOB. Die Bedeutung der Daten in den BLOB-Spalten wird durch den Wert in der Spalte für den MIME-Typ angegeben. Auf diese Weise können Sie verschiedene Zeilentypen in derselben Tabelle speichern. Die Tabelle ContactsContract.Data des Kontaktanbieters „data“ 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 enthalten 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. Auf diese Weise können Sie die Tabelle, Zeile oder Datei bestimmen, auf die zugegriffen werden soll.

Informationen zu Inhalts-URIs finden Sie unter Grundlagen von Contentanbietern.

Eine Behörde entwerfen

Ein Anbieter hat in der Regel eine einzige Behörde, die als Android-interner Name dient. Verwenden Sie die Inhaberschaft der Internetdomain (umgekehrt) als Grundlage Ihrer Anbieterberechtigung, um Konflikte mit anderen Anbietern zu vermeiden. Da diese Empfehlung auch für Android-Paketnamen gilt, kannst du deine Anbieterberechtigung als Erweiterung des Namens des Pakets definieren, das den Anbieter enthält.

Lautet der Name Ihres Android-Pakets beispielsweise com.example.<appname>, erteilen Sie Ihrem Anbieter die Berechtigung com.example.<appname>.provider.

Eine Pfadstruktur entwerfen

Entwickler erstellen in der Regel Inhalts-URIs über die Zertifizierungsstelle, indem sie Pfade anhängen, die auf einzelne Tabellen verweisen. Beispiel: Wenn Sie die beiden Tabellen table1 und table2 haben, können Sie diese mit der Befugnis 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 Pfadebene 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. Außerdem gleichen Anbieter den ID-Wert standardmäßig mit der Spalte _ID der Tabelle ab und führen den angeforderten Zugriff für die entsprechende Zeile aus.

Diese Konvention ermöglicht ein gemeinsames Designmuster für Apps, die auf einen Anbieter zugreifen. Die Anwendung führt eine Abfrage für den Anbieter durch und zeigt die resultierende Cursor in einer ListView mithilfe eines CursorAdapter an. Gemäß der Definition von CursorAdapter muss eine der Spalten in der Cursor den Wert _ID haben

Der Nutzer wählt dann eine der angezeigten Zeilen aus der UI aus, um die Daten anzusehen oder zu ändern. Die Anwendung ruft die entsprechende Zeile aus der Cursor ab, die den ListView 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 für genau die vom Nutzer ausgewählte Zeile vornehmen.

Inhalts-URI-Muster

Damit Sie besser entscheiden können, welche Aktion für einen eingehenden Inhalts-URI ausgeführt werden soll, enthält die Provider API die Convenience-Klasse UriMatcher. Diese ordnet URI-Muster für Inhalte ganzzahligen Werten zu. Sie können die ganzzahligen Werte in einer switch-Anweisung verwenden, mit der die gewünschte Aktion für den Inhalts-URI oder die URIs ausgewählt wird, die einem bestimmten Muster entsprechen.

Ein Inhalts-URI-Muster gleicht Inhalts-URIs mit Platzhalterzeichen ab:

  • * entspricht einem String beliebiger gültiger Zeichen.
  • # entspricht einer Zeichenfolge aus numerischen Zeichen beliebiger Länge.

Als Beispiel für die Gestaltung und Codierung von Inhalts-URIs ist ein Anbieter mit der Befugnis com.example.app.provider, der die folgenden Inhalts-URIs erkennt, die auf Tabellen verweisen:

  • content://com.example.app.provider/table1: eine Tabelle mit dem Namen table1.
  • content://com.example.app.provider/table2/dataset1: eine Tabelle mit dem Namen dataset1.
  • content://com.example.app.provider/table2/dataset2: eine Tabelle mit dem Namen dataset2.
  • content://com.example.app.provider/table3: eine Tabelle mit dem Namen table3.

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 Zeile, die durch 1 in table3 identifiziert wird.

Die folgenden Inhalts-URI-Muster sind möglich:

content://com.example.app.provider/*
Entspricht jedem Inhalts-URI im Anbieter.
content://com.example.app.provider/table2/*
Sucht nach einem Inhalts-URI für die Tabellen dataset1 und dataset2, stimmt aber nicht mit Inhalts-URIs für table1 oder table3 überein.
content://com.example.app.provider/table3/#
Gleicht einen Inhalts-URI für einzelne Zeilen in table3 ab, z. B. content://com.example.app.provider/table3/6 für die durch 6 angegebene Zeile.

Das folgende Code-Snippet zeigt, wie die Methoden in UriMatcher funktionieren. In diesem Code werden URIs für eine ganze Tabelle anders als für eine einzelne Zeile verarbeitet. 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 eine Zertifizierungsstelle und einen Pfad einem ganzzahligen Wert zu. Die Methode match() gibt den ganzzahligen Wert für einen URI zurück. Eine switch-Anweisung kann zwischen der Abfrage der gesamten Tabelle und der Abfrage eines einzelnen Eintrags wählen.

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 für die Arbeit mit dem Teil id von Inhalts-URIs. Die Klassen Uri und Uri.Builder enthalten praktische Methoden zum Parsen von vorhandenen Uri-Objekten und zum Erstellen neuer Objekte.

ContentProvider-Klasse implementieren

Die Instanz ContentProvider verwaltet den Zugriff auf einen strukturierten Datensatz, indem sie Anfragen von anderen Anwendungen verarbeitet. Bei allen Zugriffsformen wird schließlich ContentResolver aufgerufen, woraufhin eine konkrete Methode von ContentProvider aufgerufen wird, 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 Contentanbieter zuzugreifen.

query()
Rufen Sie Daten von Ihrem Anbieter ab. Wählen Sie mithilfe der Argumente die abzufragende Tabelle, die zurückzugebenden Zeilen und Spalten sowie die Sortierreihenfolge des Ergebnisses aus. Geben Sie die Daten als Cursor-Objekt zurück.
insert()
Fügen Sie in Ihrem Anbieter eine neue Zeile 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 um die aktualisierten Spaltenwerte abzurufen. Gibt die Anzahl der aktualisierten Zeilen zurück.
delete()
Zeilen von Ihrem Anbieter löschen. 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 für einen Inhalts-URI zurück. Diese Methode wird im Abschnitt MIME-Typen für Contentanbieter implementieren ausführlicher beschrieben.
onCreate()
Initialisieren Sie Ihren Anbieter. Das Android-System ruft diese Methode sofort auf, nachdem es Ihren Anbieter erstellt hat. Der Anbieter wird erst erstellt, wenn ein ContentResolver-Objekt versucht, darauf zuzugreifen.

Diese Methoden haben dieselbe Signatur wie die Methoden ContentResolver mit identischem Namen.

Bei der Implementierung dieser Methoden muss Folgendes berücksichtigt werden:

  • Alle diese Methoden außer onCreate() können von mehreren Threads gleichzeitig aufgerufen werden und müssen deshalb threadsicher sein. Weitere Informationen zu mehreren Threads finden Sie in der Übersicht zu Prozessen und Threads.
  • Vermeiden Sie langwierige Vorgänge in onCreate(). Initialisierungsaufgaben aufschieben, bis sie tatsächlich benötigt werden Im Abschnitt zum Implementieren der Methode "onCreate()" wird dies ausführlicher beschrieben.
  • Sie müssen diese Methoden zwar implementieren, der Code muss jedoch nichts weiter tun, außer den erwarteten Datentyp zurückzugeben. Sie können beispielsweise verhindern, dass andere Anwendungen Daten in einige Tabellen einfügen, indem Sie den Aufruf von insert() ignorieren und 0 zurückgeben.

Methode query() implementieren

Die Methode ContentProvider.query() muss ein Cursor-Objekt zurückgeben. Wenn sie fehlschlägt, muss ein Exception ausgelöst werden. Wenn Sie eine SQLite-Datenbank als Datenspeicher verwenden, können Sie die Cursor zurückgeben, die von einer der query()-Methoden der Klasse SQLiteDatabase zurückgegeben wird.

Wenn die Abfrage mit keinen Zeilen übereinstimmt, wird eine Cursor-Instanz zurückgegeben, deren Methode getCount() den Wert 0 zurückgibt. Geben Sie null nur dann zurück, wenn während des Abfragevorgangs ein interner Fehler aufgetreten ist.

Wenn Sie keine SQLite-Datenbank als Datenspeicher verwenden, verwenden Sie eine der konkreten abgeleiteten Klassen von Cursor. Beispielsweise implementiert die Klasse MatrixCursor 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 in der Lage sein, die Exception über Prozessgrenzen hinweg zu kommunizieren. Android kann dies für die folgenden Ausnahmen tun, die bei der Behandlung von Abfragefehlern hilfreich sind:

insert()-Methode implementieren

Mit der Methode insert() wird der entsprechenden Tabelle eine neue Zeile hinzugefügt. Dabei werden die Werte im Argument ContentValues verwendet. Wenn ein Spaltenname nicht im Argument ContentValues enthalten ist, können 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. Hängen Sie dazu den Primärschlüssel der neuen Zeile, in der Regel den Wert _ID, mithilfe von withAppendedId() an den Inhalts-URI der Tabelle an.

Methode delete() implementieren

Mit der Methode delete() müssen keine Zeilen aus dem Datenspeicher gelöscht werden. Wenn Sie einen Synchronisierungsadapter mit 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.

update()-Methode implementieren

Die Methode update() verwendet dasselbe Argument ContentValues, das von insert() verwendet wird, sowie die gleichen Argumente selection und selectionArgs, die von delete() und ContentProvider.query() verwendet werden. So können Sie Code zwischen diesen Methoden wiederverwenden.

onCreate()-Methode implementieren

Das Android-System ruft onCreate() beim Start des Anbieters auf. Führen Sie in dieser Methode nur schnell laufende Initialisierungsaufgaben aus und verschieben Sie die Datenbankerstellung und das Laden von Daten, bis der Anbieter tatsächlich eine Anfrage für die Daten erhält. Wenn Sie langwierige Aufgaben in onCreate() ausführen, verlangsamen Sie das Start-up Ihres Anbieters. Dies wiederum verlangsamt die Antwort vom Anbieter 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(), in der das Datenbankobjekt 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 jeden Anbieter implementieren.
getStreamTypes()
Eine Methode, die Sie implementieren sollten, 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 anstelle eines bestimmten URI sein. Geben Sie in diesem Fall den Datentyp zurück, der mit Inhalts-URIs verknüpft ist, die mit dem Muster übereinstimmen.

Bei gängigen 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 IANA-Website für MIME-Medientypen.

Für Inhalts-URIs, die auf eine oder mehrere Tabellendatenzeilen verweisen, gibt getType() einen MIME-Typ im anbieterspezifischen MIME-Format von Android zurück:

  • Teil eingeben: vnd
  • Untertypteil:
    • 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/
  • Anbieterspezifischer Teil: vnd.<name>.<type>

    Sie geben <name> und <type> an. Der Wert <name> ist global eindeutig und der Wert <type> darf im entsprechenden URI-Muster nur einmal vorkommen. Eine gute Wahl für <name> ist der Name deines Unternehmens oder ein Teil des Android-Paketnamens deiner App. Eine gute Wahl für <type> ist ein String, der die mit dem URI verknüpfte Tabelle identifiziert.

Wenn die Befugnis 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 von MIME-Typen für die Dateien zurück, die Ihr Anbieter für einen bestimmten Inhalts-URI zurückgeben kann. Filtern Sie die angebotenen MIME-Typen mit dem Filterargument für MIME-Typen, sodass Sie nur die MIME-Typen zurückgeben, die der Client verarbeiten möchte.

Nehmen wir als Beispiel 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 aufruft, das ein "Bild" ist, gibt die Methode ContentProvider.getStreamTypes() das Array zurück:

{ "image/jpeg", "image/png", "image/gif"}

Wenn die Anwendung nur an JPG-Dateien interessiert ist, kann sie ContentResolver.getStreamTypes() mit dem Filterstring *\/jpeg aufrufen. getStreamTypes() gibt dann Folgendes zurück:

{"image/jpeg"}

Wenn Ihr Anbieter keinen der im Filterstring angeforderten MIME-Typen 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 des Anbieters enthält. Die Klasse stellt einen Vertrag zwischen dem Anbieter und anderen Anwendungen her, der dafür sorgt, dass auch bei Änderungen der tatsächlichen Werte von URIs, Spaltennamen usw. korrekt auf den Anbieter zugegriffen werden kann.

Eine Vertragsklasse ist auch hilfreich für Entwickler, da ihre Konstanten in der Regel mnemonische Namen enthalten, sodass Entwickler weniger wahrscheinlich falsche Werte für Spaltennamen oder URIs verwenden. Da es sich um eine Klasse handelt, kann sie Javadoc-Dokumentation enthalten. In integrierten Entwicklungsumgebungen wie Android Studio können konstante Namen aus der Vertragsklasse automatisch vervollständigt und Javadoc für die Konstanten angezeigt werden.

Entwickler können über Ihre Anwendung nicht auf die Klassendatei der Vertragsklasse zugreifen. Sie können sie jedoch aus einer von Ihnen bereitgestellten JAR-Datei statisch in ihre Anwendung kompilieren.

Die Klasse ContactsContract und ihre verschachtelten Klassen sind Beispiele für Vertragsklassen.

Berechtigungen für Contentanbieter implementieren

Die Berechtigungen und der Zugriff für alle Aspekte des Android-Systems werden unter Sicherheitstipps ausführlich beschrieben. Unter Daten- und Dateispeicher – Übersicht werden auch die Sicherheit und Berechtigungen beschrieben, die für verschiedene Speicherarten gelten. Die wichtigsten Punkte im Überblick:

  • Standardmäßig sind Datendateien, die im internen Speicher des Geräts gespeichert sind, für Ihre Anwendung und Ihren Anbieter privat.
  • Von Ihnen erstellte SQLiteDatabase-Datenbanken sind auf Ihre Anwendung und Ihren Anbieter beschränkt.
  • Standardmäßig sind Datendateien, die Sie auf einem 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 dieser Dateien verwenden können.
  • Die Methode zum Öffnen oder Erstellen von Dateien oder SQLite-Datenbanken im internen Speicher Ihres Geräts kann möglicherweise Lese- und Schreibzugriff auf alle anderen Anwendungen gewähren. Wenn Sie eine interne Datei oder Datenbank als Repository Ihres Anbieters verwenden und dieser Zugriff gewähren, werden Ihre Daten nicht durch die Berechtigungen geschützt, die Sie in seinem Manifest für Ihren Anbieter festlegen. Der Standardzugriff für Dateien und Datenbanken im internen Speicher ist „privat“. Ändern Sie dies für das Repository Ihres Anbieters nicht.

Wenn Sie den Zugriff auf Ihre Daten mithilfe von Contentanbieterberechtigungen steuern möchten, speichern Sie die Daten in internen Dateien, SQLite-Datenbanken oder in der Cloud, z. B. auf einem Remoteserver. Außerdem sollten Sie festlegen, dass Dateien und Datenbanken in Ihrer Anwendung privat bleiben.

Berechtigungen implementieren

Standardmäßig können alle Anwendungen von Ihrem Anbieter lesen oder bei ihm schreiben, auch wenn die zugrunde liegenden Daten privat sind, da Ihr Anbieter standardmäßig keine Berechtigungen festgelegt hat. Wenn du dies ändern möchtest, lege in der Manifestdatei Berechtigungen für deinen Anbieter fest. Verwende dazu Attribute oder untergeordnete Elemente des <provider>-Elements. 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.

Die Berechtigungen für deinen Anbieter werden mit einem oder mehreren <permission>-Elementen in der Manifestdatei definiert. Um die Berechtigung für Ihren Anbieter eindeutig zu machen, verwenden Sie den Gültigkeitsbereich im Java-Stil für das Attribut android:name. Nennen Sie die Leseberechtigung z. B. com.example.app.provider.permission.READ_PROVIDER.

In der folgenden Liste wird der Umfang der Anbieterberechtigungen beschrieben. Die Liste beginnt mit den Berechtigungen, die für den gesamten Anbieter gelten, und wird dann detaillierter. Detailliertere Berechtigungen haben Vorrang vor Berechtigungen mit einem größeren Umfang.

Eine einzelne Lese-/Schreibberechtigung auf Anbieterebene
Eine Berechtigung, die sowohl den Lese- als auch den Schreibzugriff auf den gesamten Anbieter steuert, angegeben mit dem Attribut android:permission des Elements <provider>.
Separate Lese- und Schreibberechtigungen auf Anbieterebene
Eine Lese- und eine Schreibberechtigung für den gesamten Anbieter. Sie geben sie mit den Attributen android:readPermission und android:writePermission des Elements <provider> an. Sie haben Vorrang vor den für android:permission erforderlichen Berechtigungen.
Berechtigung auf Pfadebene
Lese-, Schreib- oder Lese-/Schreibberechtigung für einen Inhalts-URI bei Ihrem Anbieter. Dazu geben Sie jeden URI, den Sie steuern möchten, mit einem untergeordneten Element <path-permission> des Elements <provider> an. Für jeden Inhalts-URI, den Sie angeben, können Sie eine Lese-/Schreibberechtigung, eine Leseberechtigung, eine Schreibberechtigung oder alle drei Optionen angeben. Die Lese- und Schreibberechtigungen haben Vorrang vor der Lese-/Schreibberechtigung. 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 normalerweise erforderlichen Berechtigungen hat. Das Feature für den temporären Zugriff reduziert die Anzahl der Berechtigungen, die eine Anwendung in ihrem Manifest anfordern muss. Wenn Sie temporäre Berechtigungen aktivieren, benötigen nur Anwendungen dauerhafte Berechtigungen für Ihren Anbieter, die kontinuierlich auf alle Ihre Daten zugreifen.

Berücksichtigen Sie beispielsweise die Berechtigungen, die Sie benötigen, wenn Sie einen E-Mail-Anbieter und eine App implementieren und einer externen Bild-Viewer-Anwendung erlauben möchten, Fotoanhänge von Ihrem Anbieter anzuzeigen. Um dem Bildanzeige den erforderlichen Zugriff zu gewähren, ohne dass Berechtigungen erforderlich sind, können Sie temporäre Berechtigungen für Inhalts-URIs für Fotos einrichten.

Konzipiere deine E-Mail-App so, dass, wenn der Nutzer ein Foto anzeigen möchte, die App einen Intent mit dem Inhalts-URI des Fotos und den Berechtigungs-Flags an die Bildanzeige sendet. Der Viewer kann dann eine Anfrage an Ihren E-Mail-Anbieter senden, um das Foto abzurufen, obwohl der Viewer nicht über die normale Leseberechtigung für Ihren Anbieter verfügt.

Wenn du temporäre Berechtigungen aktivieren möchtest, lege entweder das Attribut android:grantUriPermissions des <provider>-Elements fest oder füge deinem <provider>-Element ein oder mehrere untergeordnete <grant-uri-permission>-Elemente hinzu. Rufen Sie immer Context.revokeUriPermission() 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, welcher Anteil Ihres Anbieters verfügbar ist. Wenn das Attribut auf "true" gesetzt ist, gewährt das System Ihrem gesamten Anbieter temporäre Berechtigungen 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>-Element untergeordnete <grant-uri-permission>-Elemente hinzu. Jedes untergeordnete Element gibt den Inhalts-URI oder die URIs an, für die temporärer Zugriff gewährt wird.

Zum Delegieren des temporären Zugriffs auf eine Anwendung muss ein Intent das Flag FLAG_GRANT_READ_URI_PERMISSION, das Flag FLAG_GRANT_WRITE_URI_PERMISSION oder beides enthalten. Diese werden mit der Methode setFlags() 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 für die zugehörige Anwendung mithilfe des Elements <provider> definiert. Das Android-System ruft die folgenden Informationen aus dem Element ab:

Zertifizierungsstelle (android:authorities)
Symbolische Namen, die den gesamten Anbieter innerhalb des Systems identifizieren. Dieses Attribut wird im Abschnitt URIs für Designinhalte 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:

Eine ausführlichere Beschreibung der Berechtigungen und der zugehörigen Attribute finden Sie im Abschnitt Berechtigungen für Contentanbieter implementieren.

Start- und Steuerattribute
Diese Attribute bestimmen, wie und wann das Android-System den Anbieter startet, die Prozesseigenschaften des Anbieters und andere Laufzeiteinstellungen:
  • android:enabled: Flag, mit dem das System den Anbieter starten kann
  • android:exported: Flag, das anderen Anwendungen den Zugriff auf diesen Anbieter ermöglicht
  • android:initOrder: die Reihenfolge, in der dieser Anbieter gestartet wird, im Vergleich 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: der 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, die ein Symbol für den Anbieter enthält Das Symbol wird neben dem Label des Anbieters in der App-Liste unter Einstellungen > Apps > Alle angezeigt.
  • android:label: ein Informationslabel, das den Anbieter und/oder seine Daten beschreibt. Das Label wird in der Liste der Apps unter Einstellungen > Apps > Alle angezeigt.

Diese Attribute sind im Leitfaden zum <provider>-Element vollständig dokumentiert.

Hinweis:Wenn deine App auf Android 11 oder höher ausgerichtet ist, findest du in der Dokumentation zur Paketsichtbarkeit weitere Konfigurationsanforderungen.

Intents und Datenzugriff

Anwendungen können über eine Intent indirekt auf einen Contentanbieter zugreifen. Die Anwendung ruft keine der Methoden von ContentResolver oder ContentProvider auf. Stattdessen sendet er einen Intent, der eine Aktivität startet, die oft Teil der eigenen Anwendung des Anbieters ist. Die Zielaktivität dient dem Abrufen und Anzeigen der Daten in der UI.

Abhängig von der Aktion im Intent kann die Zielaktivität den Nutzer auch dazu auffordern, Änderungen an den Daten des Anbieters vorzunehmen. Ein Intent kann auch zusätzliche Daten enthalten, die von der Zielaktivität in der UI angezeigt werden. Der Nutzer hat dann die Möglichkeit, diese Daten zu ändern, bevor er sie zum Ändern der Daten beim Anbieter verwendet.

Mit dem Intent-Zugriff können Sie die Datenintegrität verbessern. Ihr Anbieter kann davon abhängen, dass Daten gemäß einer genau definierten Geschäftslogik eingefügt, aktualisiert und gelöscht werden. In diesem Fall kann es zu ungültigen Daten kommen, wenn andere Anwendungen die Daten direkt ändern lassen.

Wenn Sie möchten, dass Entwickler Intent-Zugriff verwenden, müssen Sie dies gründlich dokumentieren. Erklären Sie, warum der Intent-Zugriff über die UI Ihrer Anwendung besser ist als der Versuch, die Daten mit dem eigenen 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 des Kalenderanbieters.