Crea un fornitore di contenuti

Un fornitore di contenuti gestisce l'accesso a un repository centrale di dati. Implementi un come una o più classi in un'applicazione Android, insieme agli elementi in del file manifest. Una delle tue classi implementa una sottoclasse ContentProvider, l'interfaccia tra il tuo provider e altre applicazioni.

Sebbene i fornitori di contenuti abbiano lo scopo di rendere i dati disponibili applicazioni, puoi avere attività nell'applicazione che consentono all'utente e modificare i dati gestiti dal tuo provider.

Questa pagina contiene la procedura di base per creare un fornitore di contenuti e un elenco di API da utilizzare.

Prima di iniziare a creare

Prima di iniziare a creare un provider, considera quanto segue:

  • Stabilisci se hai bisogno di un fornitore di contenuti. Devi creare un'offerta di contenuti se vuoi fornire una o più delle seguenti funzionalità:
    • Vuoi offrire dati o file complessi ad altre applicazioni.
    • Vuoi consentire agli utenti di copiare dati complessi dalla tua app ad altre app.
    • Vuoi fornire suggerimenti di ricerca personalizzati utilizzando il framework di ricerca.
    • Vuoi esporre i dati della tua applicazione ai widget.
    • Vuoi implementare AbstractThreadedSyncAdapter, CursorAdapter, oppure CursorLoader .

    Non è necessario un provider per utilizzare database o altri tipi di spazio di archiviazione permanente se l'uso è interamente all'interno della tua applicazione e non hai bisogno delle funzionalità elencate sopra. Puoi invece usano uno dei sistemi di archiviazione descritti in Panoramica dell'archiviazione di dati e file.

  • Se non l'hai già fatto, consulta Nozioni di base sui fornitori di contenuti per scoprire di più sui fornitori e sul loro funzionamento.

A questo punto, segui questi passaggi per creare il tuo provider:

  1. Progetta l'archiviazione non elaborata per i tuoi dati. Un fornitore di contenuti offre i dati in due modi:
    Dati dei file
    I dati che normalmente vengono inseriti nei file, come foto, audio o video. Archivia i file nella posizione privata della tua applicazione spazio. In risposta a una richiesta di file da un'altra applicazione, il tuo provider può offrire un handle per il file.
    "Strutturato" dati
    Dati che normalmente vengono inseriti in un database, in un array o in una struttura simile. Archivia i dati in un formato compatibile con tabelle di righe e colonne. Riga A rappresenta un'entità, ad esempio una persona o un elemento nell'inventario. Una colonna rappresenta alcuni dati relativi all'entità, ad esempio il nome di una persona o il prezzo di un articolo. Un modo comune per questo tipo di dati è in un database SQLite, ma puoi usare qualsiasi tipo l'archiviazione permanente dei dati. Per saperne di più sui tipi di archiviazione disponibili nella per Android, consulta Progettare l'archiviazione dei dati.
  2. Definisci un'implementazione concreta della classe ContentProvider e i suoi metodi richiesti. Questa classe è l'interfaccia tra i tuoi dati e il resto dei Sistema Android. Per ulteriori informazioni su questo corso, vedi Implementa la sezione ContentProvider.
  3. Definisci la stringa di autorità del provider, gli URI dei contenuti e i nomi delle colonne. Se vuoi l'applicazione del provider per gestire gli intent, anche definire le azioni per intent, i dati aggiuntivi e flag. Definisci anche le autorizzazioni necessarie per le applicazioni che per accedere ai tuoi dati. Considera l'idea di definire tutti questi valori come costanti in un con una classe di contratto separata. In seguito, puoi esporre il corso ad altri sviluppatori. Per ulteriori informazioni informazioni sugli URI dei contenuti, consulta Progettare gli URI dei contenuti. Per ulteriori informazioni sugli intent, consulta Sezione Intent e accesso ai dati.
  4. Aggiungi altre informazioni facoltative, ad esempio dati di esempio o un'implementazione su AbstractThreadedSyncAdapter che possono sincronizzare dati tra il provider e i dati basati su cloud.

Archiviazione dei dati di progettazione

Un fornitore di contenuti è l'interfaccia per i dati salvati in un formato strutturato. Prima di creare l'interfaccia, decidere come archiviare i dati. Puoi archiviare i dati in qualsiasi formato e poi progettare l'interfaccia per leggere e scrivere i dati in base alle esigenze.

Di seguito sono riportate alcune tecnologie di archiviazione dati disponibili su Android:

  • Se lavori con dati strutturati, prendi in considerazione un database relazionale come o un datastore di coppie chiave-valore non relazionale, come SQLite, LevelDB. Se stai lavorando con dati non strutturati come contenuti multimediali audio, immagini o video, quindi valuta la possibilità di archiviare i come file. Puoi combinare diversi tipi di spazio di archiviazione ed esporli rivolgendoti a un unico fornitore di contenuti, se necessario.
  • Il sistema Android può interagire con la libreria di persistenza della stanza, fornisce accesso all'API del database SQLite che i provider di Android per archiviare dati orientati alla tabella. Per creare un database utilizzando libreria, creare un'istanza per una sottoclasse RoomDatabase, come descritto in Salva i dati in un database locale utilizzando Stanza.

    Non è necessario utilizzare un database per implementare il repository. Un fornitore appare esternamente come un insieme di tabelle, simile a un database relazionale, ma non è un requisito per l'implementazione interna del provider.

  • Per l'archiviazione dei dati dei file, Android dispone di una serie di API orientate ai file. Per ulteriori informazioni sull'archiviazione dei file, leggi l'articolo Panoramica dell'archiviazione di dati e file. Se progettando un fornitore che offra dati relativi ai media, come musica o video, puoi un provider che combina dati e file delle tabelle.
  • In rari casi, potrebbe essere utile implementare più di un fornitore di contenuti per di una singola applicazione. Ad esempio, potresti voler condividere alcuni dati con un widget utilizzando un fornitore di contenuti ed esporre un set di dati diverso per la condivisione con altri diverse applicazioni.
  • Per lavorare con dati basati sulla rete, usa le classi in java.net e android.net. Puoi anche sincronizzare i dati basati sulla rete con un ad esempio in un database e poi li offre sotto forma di tabelle o file.

Nota: se apporti una modifica al repository che non è compatibile con le versioni precedenti, devi contrassegnare il repository con una nuova versione numero. Devi anche aumentare il numero di versione dell'app che implementa il nuovo fornitore di contenuti. Questa modifica impedisce al sistema esegui il downgrade per impedire l'arresto anomalo del sistema quando tenta di reinstallare un con un fornitore di contenuti non compatibile.

Considerazioni sulla progettazione dei dati

Ecco alcuni suggerimenti per progettare la struttura dei dati del provider:

  • I dati della tabella devono sempre avere una "chiave primaria" colonna gestita dal provider come valore numerico univoco per ogni riga. Puoi utilizzare questo valore per collegare la riga ai righe in altre tabelle (utilizzandola come "chiave esterna"). Sebbene sia possibile utilizzare qualsiasi nome per questa colonna, l'utilizzo di BaseColumns._ID è la scelta migliore perché collega i risultati di una query del provider a un ListView richiede che una delle colonne recuperate abbia il nome _ID.
  • Se vuoi fornire immagini bitmap o altri elementi molto grandi di dati orientati al file, i dati di un file per poi fornirli indirettamente anziché archiviarli direttamente in un . In questo caso, dovrai comunicare agli utenti del tuo provider che devono utilizzare una ContentResolver metodo di file per accedere ai dati.
  • Utilizza il tipo di dati BLOB (binario, oggetto di grandi dimensioni) per archiviare dati di dimensioni variabili o che hanno un struttura variabile. Ad esempio, puoi utilizzare una colonna BLOB per memorizzare un buffer di protocollo o Struttura di JSON.

    Puoi utilizzare un BLOB anche per implementare una tabella indipendente dallo schema. Nella questo tipo di tabella, definisci una colonna di chiave primaria, una colonna di tipo MIME e una più generiche come BLOB. Il significato dei dati nelle colonne BLOB viene indicato in base al valore nella colonna Tipo MIME. Ciò ti consente di archiviare diversi tipi di righe nella stessa tabella. I "dati" del provider di contatti tavola ContactsContract.Data è un esempio di indipendente dallo schema .

Progetta URI contenuti

Un URI dei contenuti è un URI che identifica i dati in un fornitore. Gli URI dei contenuti includono il nome simbolico dell'intero provider (la sua autorità) e un nome che rimanda a una tabella o a un file (un percorso). La parte ID facoltativa rimanda a una singola riga in una tabella. Ogni metodo di accesso ai dati ContentProvider ha un URI dei contenuti come argomento. Questo consente di determinare la tabella, la riga o il file a cui accedere.

Per informazioni sugli URI dei contenuti, consulta Nozioni di base sul fornitore di contenuti.

Progetta un'autorità

Di solito un provider ha un'unica autorità, che funge da nome interno ad Android. A evitare conflitti con altri provider, utilizzare la proprietà del dominio internet (al contrario) quale autorità del fornitore. Perché questo consiglio vale anche per Android dei pacchetti, puoi definire l'autorità del provider come estensione del nome del pacchetto contenente il provider.

Ad esempio, se il nome del pacchetto Android è com.example.<appname>, concedi al tuo fornitore l'autorità com.example.<appname>.provider.

Progettare la struttura di un percorso

Gli sviluppatori di solito creano URI dei contenuti dall'autorità aggiungendo percorsi che puntano a singole tabelle. Ad esempio, se hai due tabelle, table1 e table2, puoi combinarle con l'autorità dell'esempio precedente per restituire il valore URI contenuti com.example.<appname>.provider/table1 e com.example.<appname>.provider/table2. I percorsi non sono limitati a un singolo segmento e non deve essere presente una tabella per ogni livello del percorso.

Gestire gli ID URI dei contenuti

Per convenzione, i fornitori offrono l'accesso a una singola riga in una tabella accettando un URI dei contenuti con un valore ID per la riga alla fine dell'URI. Sempre per convenzione, i provider corrispondono ID alla colonna _ID della tabella ed eseguire l'accesso richiesto riga corrispondente.

Questa convenzione agevola un pattern di progettazione comune per le app che accedono a un fornitore. L'app esegue una query sul provider e visualizza il risultato Cursor in un ListView con un CursorAdapter. La definizione di CursorAdapter richiede una delle colonne in Cursor per essere _ID

L'utente sceglie quindi una delle righe visualizzate nell'interfaccia utente per esaminare o modificare e i dati di Google Cloud. L'app riceve la riga corrispondente da Cursor che supporta la ListView, ottiene il valore _ID per questa riga, lo aggiunge a l'URI dei contenuti e invia la richiesta di accesso al provider. Il provider può quindi eseguire le della query o della modifica rispetto alla riga esatta scelta dall'utente.

Pattern URI dei contenuti

Per aiutarti a scegliere quale azione intraprendere per un URI dei contenuti in entrata, l'API del provider include la classe di convenienza UriMatcher, che mappa i pattern URI dei contenuti a valori interi. Puoi utilizzare i valori interi in un'istruzione switch che sceglie l'azione desiderata per l'URI dei contenuti o per gli URI che corrispondono a un determinato pattern.

Un pattern URI dei contenuti corrisponde agli URI dei contenuti utilizzando caratteri jolly:

  • * corrisponde a una stringa di qualsiasi carattere valido di qualsiasi lunghezza.
  • # corrisponde a una stringa di caratteri numerici di qualsiasi lunghezza.

Come esempio di progettazione e programmazione della gestione degli URI dei contenuti, considera un provider con il l'autorità com.example.app.provider che riconosce i seguenti URI dei contenuti che punta a tabelle:

  • content://com.example.app.provider/table1: una tabella denominata table1.
  • content://com.example.app.provider/table2/dataset1: una tabella denominata dataset1.
  • content://com.example.app.provider/table2/dataset2: una tabella denominata dataset2.
  • content://com.example.app.provider/table3: una tabella denominata table3.

Il provider riconosce anche questi URI dei contenuti se hanno un ID riga aggiunto, ad esempio content://com.example.app.provider/table3/1 per la riga identificata da 1 a table3.

Sono possibili i seguenti pattern URI dei contenuti:

content://com.example.app.provider/*
Corrisponde a qualsiasi URI di contenuti nel provider.
content://com.example.app.provider/table2/*
Corrisponde a un URI dei contenuti per le tabelle dataset1 e dataset2, ma non corrisponde agli URI dei contenuti per table1 o table3.
content://com.example.app.provider/table3/#
Corrisponde a un URI dei contenuti per le singole righe in table3, ad esempio content://com.example.app.provider/table3/6 per la riga identificata da 6.

Il seguente snippet di codice mostra come funzionano i metodi in UriMatcher. Questo codice gestisce gli URI di un'intera tabella in modo diverso dagli URI di un riga singola utilizzando il pattern URI dei contenuti content://<authority>/<path> per tabelle e content://<authority>/<path>/<id> per le righe singole.

Il metodo addURI() mappa una autorità e percorso a un valore intero. Il metodo match() restituisce il valore intero per un URI. Un'istruzione switch sceglie tra eseguire query sull'intera tabella e su un singolo record.

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
    }

Un'altra classe, ContentUris, offre metodi pratici per lavorare con la parte id degli URI dei contenuti. I corsi Uri e Uri.Builder include metodi pratici per analizzare i dati esistenti Uri oggetti per crearne di nuovi.

Implementare la classe ContentProvider

L'istanza ContentProvider gestisce l'accesso a un set strutturato di dati gestendo le richieste da altre applicazioni. Tutti i moduli di accesso rapido chiama ContentResolver, il che a sua volta chiama una richiesta di ContentProvider per ottenere l'accesso.

Metodi richiesti

La classe astratta ContentProvider definisce sei metodi astratti che implementi come parte della tua sottoclasse concreta. Tutti questi metodi tranne onCreate() vengono chiamate da un'applicazione client che sta tentando di accedere al tuo fornitore di contenuti.

query()
Recupera i dati dal tuo provider. Utilizza gli argomenti per selezionare la tabella la query, le righe e le colonne da restituire e l'ordinamento dei risultati. Restituisce i dati come oggetto Cursor.
insert()
Inserisci una nuova riga nel provider. Utilizza gli argomenti per selezionare di destinazione e per ottenere i valori della colonna da utilizzare. Restituisci un URI dei contenuti per riga appena inserita.
update()
Aggiorna le righe esistenti nel provider. Utilizza gli argomenti per selezionare la tabella e le righe per aggiornare e ottenere i valori della colonna aggiornati. Restituisce il numero di righe aggiornate.
delete()
Elimina le righe dal provider. Utilizza gli argomenti per selezionare la tabella e le righe da eliminare. Restituisce il numero di righe eliminate.
getType()
Restituisce il tipo MIME corrispondente a un URI dei contenuti. Questo metodo è descritto in maggiore dettaglio nella sezione Implementare i tipi MIME del fornitore di contenuti.
onCreate()
Inizializza il tuo provider. Il sistema Android chiama questo metodo subito dopo crea il tuo provider. Il provider viene creato solo dopo ContentResolver oggetto tenta di accedervi.

Questi metodi hanno la stessa firma dei metodi con nome identico ContentResolver metodi.

L'implementazione di questi metodi deve tenere conto dei seguenti fattori:

  • Tutti questi metodi eccetto onCreate() possono essere chiamate da più thread contemporaneamente, quindi devono essere sicure per i thread. Per ulteriori informazioni ulteriori informazioni su più thread, consulta Panoramica dei processi e dei thread.
  • Evita di eseguire operazioni lunghe in onCreate(). Rimanda le attività di inizializzazione finché non sono effettivamente necessarie. La sezione sull'implementazione del metodo onCreate() parla di questo aspetto in modo più dettagliato.
  • Anche se devi implementare questi metodi, il tuo codice non deve fare altro che per restituire il tipo di dati previsto. Ad esempio, puoi impedire che altre applicazioni di inserire dati in alcune tabelle ignorando la chiamata a insert() e ritorno 0.

Implementare il metodo query()

La Il metodo ContentProvider.query() deve restituire un oggetto Cursor o, se non riesce, genera un Exception. Se utilizzi un database SQLite come dati di archiviazione, puoi restituire l'errore Cursor restituito da uno dei metodi query() della classe SQLiteDatabase.

Se la query non corrisponde ad alcuna riga, restituisci un Cursor istanza il cui metodo getCount() restituisce 0. Restituisci null solo se si è verificato un errore interno durante il processo di query.

Se non utilizzi un database SQLite come archiviazione dei dati, usa una delle sottoclassi concrete di Cursor. Ad esempio, la classe MatrixCursor implementano un cursore in cui ogni riga è un array di istanze Object. Con questo corso, usa addRow() per aggiungere una nuova riga.

Il sistema Android deve essere in grado di comunicare il Exception oltre i confini dei processi. Android può farlo per le seguenti eccezioni utili nella gestione degli errori di query:

Implementare il metodo insert()

Il metodo insert() aggiunge un parametro nuova riga alla tabella appropriata, utilizzando i valori nell'elemento ContentValues . Se il nome di una colonna non è nell'argomento ContentValues, potresti voler fornire un valore predefinito nel codice del provider o nel tuo database .

Questo metodo restituisce l'URI dei contenuti per la nuova riga. Per crearlo, aggiungi il nuovo chiave primaria della riga, di solito il valore _ID, all'URI dei contenuti della tabella, utilizzando withAppendedId().

Implementare il metodo delete()

Il metodo delete() non deve eliminare righe dallo spazio di archiviazione dati. Se usi un adattatore di sincronizzazione con il provider, valuta la possibilità di contrassegnare una riga eliminata con un comando "delete" anziché rimuovere completamente la riga. L'adattatore di sincronizzazione può verifica la presenza di righe eliminate e rimuovile dal server prima di eliminarle dal provider.

Implementare il metodo update()

Il metodo update() utilizza lo stesso argomento ContentValues utilizzato da insert() e gli stessi argomenti selection e selectionArgs usati da delete() e ContentProvider.query(). Ciò potrebbe consentirti di riutilizzare il codice tra questi metodi.

Implementare il metodo onCreate()

Il sistema Android chiama onCreate() avvia il provider. Esegui solo l'inizializzazione rapida le attività in questo metodo e rinviare la creazione del database e il caricamento dei dati fino a quando il provider riceve una richiesta di dati. Se esegui attività lunghe in onCreate(), diminuisci il ritmo all'avvio del provider. A sua volta, questo rallenta la risposta dal provider ad altri diverse applicazioni.

I due snippet seguenti mostrano l'interazione tra ContentProvider.onCreate() e Room.databaseBuilder(). Il primo mostra l'implementazione ContentProvider.onCreate() in cui viene creato un oggetto di database e gestisce gli oggetti di accesso ai dati creati:

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.
    }
}

Implementare tipi MIME ContentProvider

La classe ContentProvider prevede due metodi per restituire i tipi MIME:

getType()
Uno dei metodi richiesti che implementi per qualsiasi provider.
getStreamTypes()
Metodo che dovresti implementare se il tuo provider offre file.

Tipi MIME per le tabelle

Il metodo getType() restituisce un String in formato MIME che descrive il tipo di dati restituiti dai contenuti URI. L'argomento Uri può essere un pattern anziché un URI specifico. In questo caso, restituisce il tipo di dati associati agli URI dei contenuti che corrispondono ai pattern.

Per tipi comuni di dati come testo, HTML o JPEG, getType() restituisce lo standard tipo MIME per i dati. Un elenco completo di questi tipi di standard è disponibile nella Tipi di contenuti multimediali MIME IANA sito web.

Per gli URI dei contenuti che puntano a una o più righe di dati della tabella: Resi a getType() un tipo MIME nel formato MIME specifico del fornitore di Android:

  • Digita il pezzo: vnd
  • Sottotipo di parte:
    • Se il pattern URI è per una singola riga: android.cursor.item/
    • Se il pattern URI è per più di una riga: android.cursor.dir/
  • Componente specifica del fornitore: vnd.<name>.<type>

    Tu fornisci <name> e <type>. Il valore <name> è univoco a livello globale, e il valore <type> è univoco per l'URI corrispondente pattern. Una buona scelta per <name> è il nome della tua azienda o parte del nome del pacchetto Android della tua applicazione. Un'ottima scelta per <type> è una stringa che identifica la tabella associata al URI.

Ad esempio, se l'autorità di un fornitore è com.example.app.provider ed espone una tabella denominata table1, il tipo MIME per più righe in table1 è:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Per una singola riga di table1, il tipo MIME è:

vnd.android.cursor.item/vnd.com.example.provider.table1

Tipi MIME per i file

Se il tuo fornitore offre file, implementa getStreamTypes(). Il metodo restituisce un array String di tipi MIME per i file del provider che può restituire per un determinato URI di contenuti. Filtra i tipi MIME offerti in base al tipo MIME in modo da restituire solo i tipi MIME che il client vuole gestire.

Ad esempio, consideriamo un fornitore che offre immagini di foto come file in formato JPG, PNG e GIF. Se un'applicazione chiama ContentResolver.getStreamTypes() con la stringa di filtro image/*, per qualcosa che è un'"immagine", il metodo ContentProvider.getStreamTypes() restituisce l'array:

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

Se all'app interessano solo i file JPG, può chiamare ContentResolver.getStreamTypes() con la stringa di filtro *\/jpeg e Resi di getStreamTypes():

{"image/jpeg"}

Se il tuo provider non offre nessuno dei tipi MIME richiesti nella stringa del filtro, getStreamTypes() restituisce null.

Implementare una classe di contratto

Una classe contratto è una classe public final che contiene definizioni costanti per il URI, nomi di colonna, tipi MIME e altri metadati relativi al provider. Il corso stabilisce un contratto tra il fornitore e altre applicazioni, garantendo che il fornitore è possibile accedere correttamente anche in caso di modifiche ai valori effettivi di URI, nomi di colonne e così via.

Una classe contratto aiuta anche gli sviluppatori perché di solito ha nomi mnemonici per le sue costanti. in modo che gli sviluppatori siano meno propensi a utilizzare valori errati per i nomi delle colonne o gli URI. Poiché si tratta di un , può contenere la documentazione Javadoc. Ambienti di sviluppo integrati come Android Studio può completare automaticamente i nomi delle costanti dalla classe del contratto e visualizzare Javadoc per le costanti.

Gli sviluppatori non possono accedere al file del corso del corso del contratto dalla tua applicazione, ma possono di compilarlo in modo statico nell'applicazione a partire da un file JAR da te fornito.

La classe ContactsContract e le sue classi nidificate sono esempi di classi contrattuali.

Implementazione delle autorizzazioni del fornitore di contenuti

Le autorizzazioni e l'accesso per tutti gli aspetti del sistema Android sono descritti dettagliatamente in Suggerimenti per la sicurezza. Nella Panoramica dell'archiviazione di file e dati descrive la sicurezza e le autorizzazioni in vigore per i vari tipi di archiviazione. In breve, i punti importanti sono i seguenti:

  • Per impostazione predefinita, i file di dati memorizzati nella memoria interna del dispositivo sono privati l'applicazione e il provider.
  • SQLiteDatabase database che crei sono privati per i tuoi l'applicazione e il provider.
  • Per impostazione predefinita, i file di dati salvati nella memoria esterna sono pubblici e leggibili in tutto il mondo. Non puoi utilizzare un fornitore di contenuti per limitare l'accesso ai file in archiviazione esterna, perché altre applicazioni possono utilizzare altre chiamate API per leggerle e scriverle.
  • Il metodo richiede l'apertura o la creazione di file o database SQLite spazio di archiviazione può potenzialmente concedere l'accesso in lettura e in scrittura a tutte le altre applicazioni. Se utilizzare un file o un database interno come repository del provider e gli assegni "leggibile in tutto il mondo" o "scrivibile a livello mondiale" le autorizzazioni impostate per il provider il relativo file manifest non protegge i tuoi dati. L'accesso predefinito per file e database in la memoria interna è "privata"; non devi modificarlo per il repository del tuo provider.

Se vuoi utilizzare le autorizzazioni del fornitore di contenuti per controllare l'accesso ai tuoi dati, archiviare i dati in file interni, database SQLite o nel cloud, ad esempio su un server remoto e mantenere privati i file e i database per la tua applicazione.

Implementare le autorizzazioni

Per impostazione predefinita, tutte le applicazioni possono leggere o scrivere sul provider, anche se i dati sottostanti sono perché per impostazione predefinita il tuo provider non ha autorizzazioni impostate. Per cambiare questa impostazione, Impostare le autorizzazioni per il provider nel file manifest, utilizzando gli attributi o dell'elemento <provider>. Puoi impostare autorizzazioni da applicare all'intero provider, a determinate tabelle, a determinati record o a tutti e tre.

Puoi definire le autorizzazioni per il tuo provider con uno o più Elementi <permission> nel file manifest. Per fare in modo che autorizzazione univoca per il provider, utilizza la definizione dell'ambito in stile Java per Attributo android:name. Ad esempio, assegna un nome all'autorizzazione di lettura com.example.app.provider.permission.READ_PROVIDER.

Il seguente elenco descrive l'ambito delle autorizzazioni del provider, a partire dal autorizzazioni applicabili all'intero provider, per poi diventare più granulari. Le autorizzazioni più granulari hanno la precedenza su quelle con ambito più ampio.

Singola autorizzazione a livello di provider di lettura-scrittura
Un'autorizzazione che controlla l'accesso in lettura e in scrittura all'intero provider, specificata con l'attributo android:permission del Elemento <provider>.
Autorizzazioni separate a livello di provider per lettura e scrittura
Un'autorizzazione di lettura e un'autorizzazione di scrittura per l'intero provider. Tu le specifichi con android:readPermission e Attributi android:writePermission del Elemento <provider>. Hanno la precedenza sull'autorizzazione richiesta dalle android:permission.
Autorizzazione a livello di percorso
Autorizzazione di lettura, scrittura o lettura/scrittura per un URI di contenuti nel tuo provider. Tu specifichi per ogni URI che vuoi controllare <path-permission> elemento secondario di Elemento <provider>. Per ogni URI dei contenuti specificato, puoi specificare autorizzazione di lettura/scrittura, autorizzazione di lettura, autorizzazione di scrittura o tutte e tre le autorizzazioni. Le funzionalità di lettura le autorizzazioni di scrittura hanno la precedenza su quelle di lettura/scrittura. Inoltre, a livello di percorso ha la precedenza sulle autorizzazioni a livello di provider.
Autorizzazione temporanea
Un livello di autorizzazione che concede l'accesso temporaneo a un'applicazione, anche se quest'ultima non dispone delle autorizzazioni normalmente richieste. L'impostazione temporanea di accesso riduce il numero di autorizzazioni che un'applicazione deve richiedere relativo al suo manifest. Quando attivi le autorizzazioni temporanee, le uniche applicazioni che devono le autorizzazioni permanenti per il provider sono quelle che accedono continuamente a tutti i tuoi dati.

Ad esempio, considera le autorizzazioni di cui hai bisogno se implementi un provider email e un'app e vuoi consentire a un'applicazione di visualizzazione di immagini esterna di visualizzare le foto allegate dal tuo o il provider di servizi di terze parti. Per concedere al visualizzatore immagini l'accesso necessario senza richiedere le autorizzazioni, puoi impostare autorizzazioni temporanee per gli URI dei contenuti per le foto.

Progetta la tua app email in modo quando l'utente vuole mostrare una foto, l'app invia un intent contenente l'URI dei contenuti e i flag di autorizzazione della foto per il visualizzatore di immagini. Il visualizzatore di immagini può quindi chiedi al tuo provider email di recuperare la foto, anche se il visualizzatore dispongono della normale autorizzazione di lettura per il provider.

Per attivare le autorizzazioni temporanee, imposta il Attributo android:grantUriPermissions di <provider> elemento o aggiungi uno o più <grant-uri-permission> elementi secondari al tuo Elemento <provider>. Chiama Context.revokeUriPermission() ogni volta che rimuovi il supporto di un URI dei contenuti associato a un'autorizzazione temporanea dal tuo o il provider di servizi di terze parti.

Il valore dell'attributo determina in che misura il tuo provider è reso accessibile. Se l'attributo è impostato su "true", il sistema concede per l'intero provider, che sostituisce qualsiasi altra autorizzazione richiesta a livello di provider o di percorso.

Se questo flag è impostato su "false", aggiungi <grant-uri-permission> elementi secondari al tuo Elemento <provider>. Ogni elemento figlio specifica l'URI dei contenuti URI per i quali viene concesso l'accesso temporaneo.

Per delegare l'accesso temporaneo a un'applicazione, un intent deve contenere il flag FLAG_GRANT_READ_URI_PERMISSION, Flag FLAG_GRANT_WRITE_URI_PERMISSION o entrambi. Questi sono impostate con il metodo setFlags().

Se l'attributo android:grantUriPermissions non è presente, si presume che sia "false".

Il parametro <provider> elemento

Come i componenti Activity e Service, una sottoclasse di ContentProvider viene definito nel file manifest della relativa applicazione, utilizzando Elemento <provider>. Il sistema Android riceve le seguenti informazioni l'elemento:

Autorità (android:authorities)
Nomi simbolici che identificano l'intero provider all'interno del sistema. Questo è descritto più dettagliatamente nel Progettare gli URI dei contenuti.
Nome classe provider (android:name)
La classe che implementa ContentProvider. Questo corso è descritti più dettagliatamente nel Implementa la sezione ContentProvider.
Autorizzazioni
Attributi che specificano le autorizzazioni necessarie per le altre applicazioni per accedere dati del provider:

Le autorizzazioni e i relativi attributi sono descritti in maggiore nel dettaglio Sezione Implementa le autorizzazioni del fornitore di contenuti.

Attributi di avvio e controllo
Questi attributi determinano come e quando il sistema Android avvia il provider, caratteristiche di processo del provider e altre impostazioni di runtime:
  • android:enabled: flag che consente al sistema di avviare il provider
  • android:exported: flag che consente ad altre applicazioni di utilizzare questo provider
  • android:initOrder: l'ordine di avvio del provider, rispetto ad altri fornitori nella stessa procedura
  • android:multiProcess: flag che consente al sistema di avviare il provider nello stesso processo del client chiamante
  • android:process: il nome del processo in cui viene eseguito il provider
  • android:syncable: flag che indica che i dati del provider devono essere sincronizzati con i dati su un server

Questi attributi sono completamente documentati nella guida al Elemento <provider>.

Attributi informativi
Icona ed etichetta facoltative per il fornitore:
  • android:icon: una risorsa drawable contenente un'icona per il provider. L'icona viene visualizzata accanto all'etichetta del fornitore nell'elenco di app in Impostazioni > App > Tutte.
  • android:label: un'etichetta informativa che descrive il fornitore, le sue dati, o entrambi. L'etichetta viene visualizzata nell'elenco di app in Impostazioni > App > Tutte.

Questi attributi sono completamente documentati nella guida al Elemento <provider>.

Nota: se hai scelto come target Android 11 o versioni successive, consulta la documentazione sulla visibilità dei pacchetti per ulteriori esigenze di configurazione.

Accesso a intent e dati

Le applicazioni possono accedere indirettamente a un fornitore di contenuti con un Intent. L'applicazione non chiama nessuno dei metodi di ContentResolver o ContentProvider. ma invia un intent che avvia un'attività che spesso fa parte dell'applicazione del fornitore. L'attività della destinazione si occupa di: il recupero e la visualizzazione dei dati nell'interfaccia utente.

A seconda dell'azione nell'intento, l'attività della destinazione può anche richiedere all'utente di apportare modifiche ai dati del provider. Un intent può anche contenere "extra" Dati visualizzati nell'attività della destinazione nell'interfaccia utente. L'utente ha quindi la possibilità di cambiare questi dati prima di utilizzarli per modificare il solo i dati del provider.

Puoi utilizzare l'accesso per intent per migliorare l'integrità dei dati. Il tuo provider potrebbe dipendere sull'inserimento, l'aggiornamento e l'eliminazione dei dati secondo una logica di business rigorosamente definita. Se è il caso, se consenti ad altre applicazioni di modificare direttamente i tuoi dati può dati non validi.

Se vuoi che gli sviluppatori utilizzino l'accesso per intent, assicurati di documentarlo accuratamente. Spiega perché è meglio utilizzare l'accesso per intent utilizzando l'UI della tua applicazione rispetto a quando modificare i dati con il loro codice.

La gestione di un intent in entrata che vuole modificare i dati del tuo provider non è diversa da gestire altri intent. Per scoprire di più sull'utilizzo degli intent, leggi Filtri per intent e intent.

Per ulteriori informazioni correlate, consulta Panoramica del fornitore di Calendar.