Contentanbieter erstellen

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 oder CursorLoader 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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 und android.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 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 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 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 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 und dataset2 überein, aber nicht mit Inhalts-URIs für table1 oder table3.
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 durch 6 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:

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/
  • 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 und android:writePermission des Elements <provider> an. Sie haben Vorrang vor der für android: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 Sie Context.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 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 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:

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.