Un fornitore di contenuti gestisce l'accesso a un repository centrale di dati. Un fornitore fa parte di un'applicazione Android, che spesso fornisce una propria UI per l'utilizzo i dati. Tuttavia, i fornitori di contenuti vengono utilizzati principalmente da altri che accedono al provider utilizzando un oggetto client provider. Insieme, i fornitori e i client provider offrono un'interfaccia coerente e standard ai dati che gestisce elaborare le comunicazioni e proteggere l'accesso ai dati.
In genere collabori con i fornitori di contenuti in uno dei due seguenti scenari: implementazione di accesso a un fornitore di contenuti esistente in un'altra applicazione o creando a un nuovo fornitore di contenuti nella tua applicazione per condividere dati con altre applicazioni.
Questa pagina illustra le nozioni di base della collaborazione con i fornitori di contenuti esistenti. Per scoprire di più sull'implementazione i fornitori di contenuti nelle tue applicazioni, vedi Crea un fornitore di contenuti.
In questo argomento vengono trattati i seguenti argomenti:
- Come funzionano i fornitori di contenuti.
- L'API che utilizzi per recuperare i dati da un fornitore di contenuti.
- L'API che utilizzi per inserire, aggiornare o eliminare i dati in un fornitore di contenuti.
- Altre funzionalità delle API che facilitano la collaborazione con i fornitori.
Panoramica
Un fornitore di contenuti presenta i dati alle applicazioni esterne sotto forma di una o più tabelle in modo simile alle tabelle di un database relazionale. Una riga rappresenta un'istanza di qualche tipo di dati raccolti dal provider e ogni colonna della riga rappresenta una singola raccolti per un'istanza.
Un fornitore di contenuti coordina l'accesso al livello di archiviazione dei dati nella tua applicazione per di API e componenti. Come illustrato nella Figura 1, sono inclusi:
- Condividere l'accesso ai dati delle tue applicazioni con altre applicazioni
- Invio di dati a un widget
- Restituzione di suggerimenti di ricerca personalizzati per la tua applicazione tramite la ricerca
utilizzando
SearchRecentSuggestionsProvider
- Sincronizzare i dati delle applicazioni con il server utilizzando un'implementazione
AbstractThreadedSyncAdapter
- Caricamento di dati nell'interfaccia utente tramite
CursorLoader
Accedi a un provider
Quando vuoi accedere ai dati di un fornitore di contenuti, utilizza la
ContentResolver
oggetto nel
Context
per comunicare con il fornitore in qualità di client. La
L'oggetto ContentResolver
comunica con l'oggetto provider, un
istanza di una classe che implementa ContentProvider
.
Il fornitore
riceve richieste di dati dai client, esegue l'azione richiesta e restituisce
che consentono di analizzare i dati
e visualizzare i risultati. Questo oggetto prevede metodi che chiamano metodi con nomi identici nell'oggetto provider,
un'istanza di una delle sottoclassi concrete di ContentProvider
. La
I metodi ContentResolver
offrono la base
"CRUD" (creazione, recupero, aggiornamento ed eliminazione) delle funzioni di archiviazione permanente.
Un pattern comune per accedere a un ContentProvider
dalla tua UI utilizza un
CursorLoader
per eseguire una query asincrona in background. La
Activity
o Fragment
nella tua UI chiama un
CursorLoader
alla query, che a sua volta riceve il valore
ContentProvider
tramite ContentResolver
.
In questo modo l'interfaccia utente può continuare a essere disponibile per l'utente mentre è in esecuzione la query. Questo implica l'interazione di una serie di oggetti diversi, così come la meccanismo di memorizzazione, come illustrato in Figura 2.
Nota: per accedere a un provider, in genere la tua applicazione deve richiedere specifiche autorizzazioni nel proprio file manifest. Questo modello di sviluppo è descritto più dettagliatamente nel Autorizzazioni del fornitore di contenuti.
Uno dei provider integrati nella piattaforma Android è il Provider di dizionario utente, che Archivia le parole non standard che l'utente vuole conservare. La tabella 1 illustra cosa i dati potrebbero essere simili a quelli indicati nella tabella di questo provider:
parola | id app | di pubblicazione | impostazioni internazionali | ID |
---|---|---|---|---|
mapreduce |
utente1 | 100 | it_IT | 1 |
precompiler |
utente14 | 200 | fr_FR | 2 |
applet |
utente2 | 225 | fr_CA | 3 |
const |
utente1 | 255 | pt_BR | 4 |
int |
utente5 | 100 | it_IT | 5 |
Nella tabella 1, ogni riga rappresenta un'istanza di una parola non
presenti in un dizionario standard. Ogni colonna rappresenta un dato relativo a quella parola, ad esempio la
le impostazioni internazionali in cui è stato rilevato per la prima volta. Le intestazioni di colonna sono nomi di colonna memorizzati in
il fornitore. Ad esempio, per fare riferimento alle impostazioni internazionali di una riga, devi fare riferimento alla relativa colonna locale
. Per
questo provider, la colonna _ID
funge da colonna chiave principale che
che il provider gestisce automaticamente.
Per ottenere un elenco di parole e le relative impostazioni internazionali dal Provider di dizionario utenti,
che chiami ContentResolver.query()
.
Il metodo query()
chiama
ContentProvider.query()
definito dal metodo
Provider del dizionario utente. Le seguenti righe di codice mostrano
Chiamata ContentResolver.query()
:
Kotlin
// Queries the UserDictionary and returns results cursor = contentResolver.query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Selection criteria selectionArgs.toTypedArray(), // Selection criteria sortOrder // The sort order for the returned rows )
Java
// Queries the UserDictionary and returns results cursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Selection criteria selectionArgs, // Selection criteria sortOrder); // The sort order for the returned rows
La tabella 2 mostra come gli argomenti
query(Uri,projection,selection,selectionArgs,sortOrder)
corrispondono a un'istruzione SQL SELECT:
query() argomento |
SELECT parola chiave/parametro | Note |
---|---|---|
Uri |
FROM table_name |
Uri viene mappato alla tabella nel provider denominato table_name. |
projection |
col,col,col,... |
projection è un array di colonne incluso per ogni riga
recuperato.
|
selection |
WHERE col = value |
selection specifica i criteri per la selezione delle righe. |
selectionArgs |
Nessun equivalente esatto. Gli argomenti di selezione sostituiscono ? segnaposto in
una clausola di selezione.
|
|
sortOrder |
ORDER BY col,col,... |
sortOrder specifica l'ordine in cui le righe vengono visualizzate nella
Cursor .
|
URI contenuti
Un URI dei contenuti è un URI che identifica i dati in un fornitore. URI contenuti includi il nome simbolico dell'intero provider (la sua autorità) e un nome che punta a una tabella, un percorso. Quando chiami un metodo client per accedere a una tabella in un provider, l'URI dei contenuti della tabella è uno di gli argomenti.
Nelle righe di codice precedenti, la costante
CONTENT_URI
contiene l'URI dei contenuti di
della tabella Words
del fornitore del dizionario utente. ContentResolver
analizza l'autorità dell'URI e la utilizza per risolvere il provider
Confrontando l'autorità con una tabella di sistema dei fornitori noti. La
ContentResolver
può quindi inviare gli argomenti della query all'elenco
o il provider di servizi di terze parti.
ContentProvider
utilizza la parte del percorso dell'URI dei contenuti per scegliere
a cui accedere. In genere un provider ha un percorso per ogni tabella che espone.
Nelle righe di codice precedenti, l'URI completo della tabella Words
è:
content://user_dictionary/words
- La stringa
content://
è lo schema, che è sempre presente e lo identifica come URI dei contenuti. - La stringa
user_dictionary
indica l'autorità del provider. - La stringa
words
è il percorso della tabella.
Molti provider ti consentono di accedere a una singola riga in una tabella aggiungendo un valore ID
alla fine dell'URI. Ad esempio, per recuperare una riga il cui _ID
è
4
del fornitore di dizionario utenti, puoi utilizzare questo URI di contenuti:
Kotlin
val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)
Java
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
Spesso utilizzi i valori ID quando recuperi un insieme di righe e poi vuoi aggiornarlo o eliminarlo uno di questi.
Nota: i corsi Uri
e Uri.Builder
contengono metodi pratici per la costruzione di oggetti URI ben strutturati dalle stringhe. La
La classe ContentUris
contiene metodi pratici per aggiungere valori ID a
un URI. Lo snippet precedente utilizza withAppendedId()
per aggiungere un ID all'URI dei contenuti del fornitore del dizionario utente.
Recupera i dati dal provider
Questa sezione descrive come recuperare i dati da un provider utilizzando il Provider di dizionario utente come esempio.
Per maggiore chiarezza, gli snippet di codice di questa sezione richiamano
ContentResolver.query()
nel thread dell'interfaccia utente. Nella
nel codice vero e proprio, invece, eseguono query in modo asincrono su un thread separato. Puoi
utilizzare la classe CursorLoader
, descritta
in modo più dettagliato nel
Guida per i caricatori. Inoltre, le righe di codice sono solo snippet. Non mostrano un numero completo
un'applicazione.
Per recuperare i dati da un provider, segui questi passaggi di base:
- Richiedi l'autorizzazione di accesso in lettura per il provider.
- Definisci il codice che invia una query al provider.
Richiedi autorizzazione di accesso in lettura
Per recuperare i dati da un provider, la tua applicazione deve disporre dell'autorizzazione di accesso in lettura per il
o il provider di servizi di terze parti. Non puoi richiedere questa autorizzazione in fase di runtime. Devi invece specificare
devi disporre di questa autorizzazione nel file manifest, utilizzando
<uses-permission>
e l'esatto nome dell'autorizzazione definito dall'oggetto
o il provider di servizi di terze parti.
Se specifichi questo elemento nel file manifest, lo richiedi l'autorizzazione per la tua applicazione. Quando gli utenti installano la tua applicazione, concedono implicitamente questa richiesta.
Per trovare il nome esatto dell'autorizzazione di accesso in lettura per il provider che stai utilizzando, nonché come nomi per le altre autorizzazioni di accesso utilizzate dal provider, cerca documentazione.
Il ruolo delle autorizzazioni per accedere ai fornitori è descritto più dettagliatamente nella Autorizzazioni del fornitore di contenuti.
Il provider del dizionario utente definisce l'autorizzazione
android.permission.READ_USER_DICTIONARY
nel file manifest, quindi un
un'applicazione che desidera leggere dal provider deve richiedere questa autorizzazione.
Crea la query
Il passaggio successivo nel recupero dei dati da un provider è creare una query. Il seguente snippet definisce alcune variabili per accedere al provider di dizionario utente:
Kotlin
// A "projection" defines the columns that are returned for each row private val mProjection: Array<String> = arrayOf( UserDictionary.Words._ID, // Contract class constant for the _ID column name UserDictionary.Words.WORD, // Contract class constant for the word column name UserDictionary.Words.LOCALE // Contract class constant for the locale column name ) // Defines a string to contain the selection clause private var selectionClause: String? = null // Declares an array to contain selection arguments private lateinit var selectionArgs: Array<String>
Java
// A "projection" defines the columns that are returned for each row String[] mProjection = { UserDictionary.Words._ID, // Contract class constant for the _ID column name UserDictionary.Words.WORD, // Contract class constant for the word column name UserDictionary.Words.LOCALE // Contract class constant for the locale column name }; // Defines a string to contain the selection clause String selectionClause = null; // Initializes an array to contain selection arguments String[] selectionArgs = {""};
Lo snippet successivo mostra come utilizzare
ContentResolver.query()
, con il dizionario utente
Provider come esempio. Una query client del provider è simile a una query SQL e contiene un
un insieme di colonne da restituire, un insieme di criteri di selezione e un ordinamento.
L'insieme di colonne restituito dalla query è chiamato proiezione e
la variabile è mProjection
.
L'espressione che specifica le righe da recuperare viene suddivisa in una clausola di selezione e
argomenti di selezione. La clausola di selezione è una combinazione di espressioni logiche e booleane,
i nomi delle colonne e i valori. La variabile è mSelectionClause
. Se specifichi
il parametro sostituibile ?
anziché un valore, il metodo di query recupera il valore
dall'array degli argomenti di selezione, che è la variabile mSelectionArgs
.
Nello snippet successivo, se l'utente non inserisce una parola su cui è impostata la clausola di selezione
null
e la query restituisce tutte le parole nel provider. Se l'utente inserisce
una parola, la clausola di selezione è impostata su UserDictionary.Words.WORD + " = ?"
il primo elemento dell'array di argomenti di selezione è impostato sulla parola inserita dall'utente.
Kotlin
/* * This declares a String array to contain the selection arguments. */ private lateinit var selectionArgs: Array<String> // Gets a word from the UI searchString = searchWord.text.toString() // Insert code here to check for invalid or malicious input // If the word is the empty string, gets everything selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let { selectionClause = "${UserDictionary.Words.WORD} = ?" arrayOf(it) } ?: run { selectionClause = null emptyArray<String>() } // Does a query against the table and returns a Cursor object mCursor = contentResolver.query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Either null or the word the user entered selectionArgs, // Either empty or the string the user entered sortOrder // The sort order for the returned rows ) // Some providers return null if an error occurs, others throw an exception when (mCursor?.count) { null -> { /* * Insert code here to handle the error. Be sure not to use the cursor! * You might want to call android.util.Log.e() to log this error. */ } 0 -> { /* * Insert code here to notify the user that the search is unsuccessful. This isn't * necessarily an error. You might want to offer the user the option to insert a new * row, or re-type the search term. */ } else -> { // Insert code here to do something with the results } }
Java
/* * This defines a one-element String array to contain the selection argument. */ String[] selectionArgs = {""}; // Gets a word from the UI searchString = searchWord.getText().toString(); // Remember to insert code here to check for invalid or malicious input // If the word is the empty string, gets everything if (TextUtils.isEmpty(searchString)) { // Setting the selection clause to null returns all words selectionClause = null; selectionArgs[0] = ""; } else { // Constructs a selection clause that matches the word that the user entered selectionClause = UserDictionary.Words.WORD + " = ?"; // Moves the user's input string to the selection arguments selectionArgs[0] = searchString; } // Does a query against the table and returns a Cursor object mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Either null or the word the user entered selectionArgs, // Either empty or the string the user entered sortOrder); // The sort order for the returned rows // Some providers return null if an error occurs, others throw an exception if (null == mCursor) { /* * Insert code here to handle the error. Be sure not to use the cursor! You can * call android.util.Log.e() to log this error. * */ // If the Cursor is empty, the provider found no matches } else if (mCursor.getCount() < 1) { /* * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily * an error. You can offer the user the option to insert a new row, or re-type the * search term. */ } else { // Insert code here to do something with the results }
Questa query è analoga alla seguente istruzione SQL:
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
In questa istruzione SQL, vengono utilizzati i nomi effettivi delle colonne al posto delle costanti delle classi contrattuali.
Proteggiti da input dannosi
Se i dati gestiti dal fornitore di contenuti si trovano in un database SQL, inclusi quelli non attendibili esterni in istruzioni SQL non elaborate possono portare a SQL injection.
Prendi in considerazione la seguente clausola di selezione:
Kotlin
// Constructs a selection clause by concatenating the user's input to the column name var selectionClause = "var = $mUserInput"
Java
// Constructs a selection clause by concatenating the user's input to the column name String selectionClause = "var = " + userInput;
Se lo fai, puoi consentire all'utente di concatenare potenzialmente SQL dannoso nella tua istruzione SQL.
Ad esempio, l'utente può inserire "niente; DROP TABLE *;" per mUserInput
, che
determina la clausola di selezione var = nothing; DROP TABLE *;
.
Poiché il la clausola di selezione viene trattata come un'istruzione SQL, questo potrebbe far sì che il provider le tabelle nel database SQLite sottostante, a meno che il provider non sia configurato per rilevare Tentativi di iniezione SQL.
Per evitare questo problema, utilizza una clausola di selezione che ricorre a ?
come sostituibile
e un array separato di argomenti di selezione. In questo modo, l'input dell'utente
viene associato direttamente alla query anziché essere interpretato come parte di un'istruzione SQL.
Poiché non viene trattato come SQL, l'input dell'utente non può inserire codice SQL dannoso. Invece di utilizzare
concatenazione per includere l'input utente, utilizza questa clausola di selezione:
Kotlin
// Constructs a selection clause with a replaceable parameter var selectionClause = "var = ?"
Java
// Constructs a selection clause with a replaceable parameter String selectionClause = "var = ?";
Imposta l'array di argomenti di selezione in questo modo:
Kotlin
// Defines a mutable list to contain the selection arguments var selectionArgs: MutableList<String> = mutableListOf()
Java
// Defines an array to contain the selection arguments String[] selectionArgs = {""};
Inserisci un valore nell'array di argomenti di selezione in questo modo:
Kotlin
// Adds the user's input to the selection argument selectionArgs += userInput
Java
// Sets the selection argument to the user's input selectionArgs[0] = userInput;
Una clausola di selezione che utilizza ?
come parametro sostituibile e un array di
argomento di selezione è il modo migliore per specificare una selezione, anche se il provider non è
in base a un database SQL.
Visualizza i risultati della query
Sempre il metodo client ContentResolver.query()
restituisce un valore Cursor
contenente le colonne specificate dal codice
per le righe che corrispondono ai criteri di selezione della query. R
L'oggetto Cursor
fornisce accesso in lettura casuale alle righe e alle colonne che lo contiene
contiene.
Con i metodi Cursor
, puoi eseguire l'iterazione delle righe nella
determinare il tipo di dati di ogni colonna, estrarre i dati da una colonna ed esaminare
proprietà dei risultati.
Alcune implementazioni di Cursor
automaticamente
aggiorna l'oggetto quando i dati del provider cambiano, metodi di attivazione in un oggetto osservatore
quando cambia Cursor
, o entrambi.
Nota: un provider può limitare l'accesso alle colonne in base alla natura del dell'oggetto che esegue la query. Ad esempio, il provider di contatti limita l'accesso per alcune colonne a di sincronizzazione degli adattatori, in modo da non restituirli a un'attività o a un servizio.
Se nessuna riga corrisponde ai criteri di selezione, il provider
restituisce un oggetto Cursor
per il quale
Cursor.getCount()
è
0, ovvero un cursore vuoto.
Se si verifica un errore interno, i risultati della query dipendono dal provider specifico. Potrebbe
restituisce null
o può generare un Exception
.
Poiché Cursor
è un elenco di righe, un buon modo per visualizzare
i contenuti di un Cursor
consiste nel collegarlo a un ListView
utilizzando un SimpleCursorAdapter
.
Il seguente snippet continua con il codice dello snippet precedente. Crea
Oggetto SimpleCursorAdapter
contenente l'elemento Cursor
recuperate dalla query e imposta questo oggetto come adattatore per
ListView
.
Kotlin
// Defines a list of columns to retrieve from the Cursor and load into an output row val wordListColumns : Array<String> = arrayOf( UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name ) // Defines a list of View IDs that receive the Cursor columns for each row val wordListItems = intArrayOf(R.id.dictWord, R.id.locale) // Creates a new SimpleCursorAdapter cursorAdapter = SimpleCursorAdapter( applicationContext, // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0 // Flags (usually none are needed) ) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter)
Java
// Defines a list of columns to retrieve from the Cursor and load into an output row String[] wordListColumns = { UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name }; // Defines a list of View IDs that receive the Cursor columns for each row int[] wordListItems = { R.id.dictWord, R.id.locale}; // Creates a new SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter( getApplicationContext(), // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0); // Flags (usually none are needed) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter);
Nota: per eseguire il backup di ListView
con un
Cursor
, il cursore deve contenere una colonna denominata _ID
.
Per questo motivo, la query mostrata in precedenza recupera la colonna _ID
per
Words
, anche se non viene visualizzata in ListView
.
Questa limitazione spiega anche perché la maggior parte dei provider ha una colonna _ID
per ciascuno dei
le rispettive tabelle.
Ottieni dati dai risultati delle query
Oltre a visualizzare i risultati delle query, puoi utilizzarli per altre attività. Per
Ad esempio, puoi recuperare le ortografie da User Dictionary Provider e quindi cercarle in
da altri fornitori. A questo scopo, devi ripetere l'iterazione delle righe in Cursor
, come mostrato nell'esempio seguente:
Kotlin
/* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers might throw an Exception instead of returning null. */ mCursor?.apply { // Determine the column index of the column named "word" val index: Int = getColumnIndex(UserDictionary.Words.WORD) /* * Moves to the next row in the cursor. Before the first movement in the cursor, the * "row pointer" is -1, and if you try to retrieve data at that position you get an * exception. */ while (moveToNext()) { // Gets the value from the column newWord = getString(index) // Insert code here to process the retrieved word ... // End of while loop } }
Java
// Determine the column index of the column named "word" int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); /* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers might throw an Exception instead of returning null. */ if (mCursor != null) { /* * Moves to the next row in the cursor. Before the first movement in the cursor, the * "row pointer" is -1, and if you try to retrieve data at that position you get an * exception. */ while (mCursor.moveToNext()) { // Gets the value from the column newWord = mCursor.getString(index); // Insert code here to process the retrieved word ... // End of while loop } } else { // Insert code here to report an error if the cursor is null or the provider threw an exception }
Cursor
implementazioni contengono diversi "get" per
recuperando diversi tipi di dati dall'oggetto. Ad esempio, lo snippet precedente
usa getString()
. Hanno anche un
Metodo getType()
che restituisce un valore che indica
il tipo di dati della colonna.
Rilascia risorse risultati query
Cursor
oggetti devono essere
chiuso se non sono più necessari, in modo che le risorse associate vengano rilasciate
per prima cosa. Questa operazione può essere eseguita chiamando
close()
o utilizzando
un'istruzione try-with-resources
nel linguaggio di programmazione Java
use()
nel linguaggio di programmazione Kotlin.
Autorizzazioni del fornitore di contenuti
L'applicazione di un provider può specificare le autorizzazioni che altre applicazioni devono avere per accedere ai dati del provider. Queste autorizzazioni consentono all'utente di sapere quali dati un'applicazione tenta di accedere. In base ai requisiti del fornitore, altre applicazioni e richiedere le autorizzazioni necessarie per accedere al provider. Gli utenti finali visualizzano la richiesta autorizzazioni al momento dell'installazione dell'applicazione.
Se l'applicazione di un provider non specifica alcuna autorizzazione, le altre applicazioni non avranno l'accesso ai dati del provider, a meno che il provider non venga esportato. Inoltre, i componenti dell'applicazione del provider hanno sempre accesso completo in lettura e scrittura, indipendentemente dal autorizzazioni specificate.
Il Provider di dizionario utente richiede
Autorizzazione android.permission.READ_USER_DICTIONARY
a recuperare i dati.
Il provider ha un android.permission.WRITE_USER_DICTIONARY
separato
l'autorizzazione per inserire, aggiornare o eliminare i dati.
Per ottenere le autorizzazioni necessarie per accedere a un provider, un'applicazione le richiede con un
<uses-permission>
nel proprio file manifest. Quando il gestore di pacchetti Android installa l'applicazione, l'utente
devono approvare tutte le autorizzazioni richieste dall'applicazione. Se l'utente li approva,
Il gestore di pacchetti prosegue l'installazione. Se l'utente non li approva, Package Manager
interrompe l'installazione.
Il seguente esempio
<uses-permission>
elemento richiede l'accesso in lettura al provider di dizionario utente:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
L'impatto delle autorizzazioni sull'accesso del provider è spiegato più dettagliatamente in Suggerimenti per la sicurezza.
Inserire, aggiornare ed eliminare i dati
Allo stesso modo in cui recuperi i dati da un provider, utilizzi anche l'interazione tra
un client del provider e ContentProvider
del provider per modificare i dati.
Chiamerai un metodo di ContentResolver
con argomenti che vengono passati a
il metodo corrispondente di ContentProvider
. Il fornitore e il fornitore
e il client gestisce automaticamente la sicurezza e le comunicazioni tra i processi.
Inserisci i dati
Per inserire dati in un fornitore, chiama il metodo
ContentResolver.insert()
. Questo metodo inserisce una nuova riga nel provider e restituisce un URI dei contenuti per quella riga.
Il seguente snippet mostra come inserire una nuova parola in User Dictionary Provider:
Kotlin
// Defines a new Uri object that receives the result of the insertion lateinit var newUri: Uri ... // Defines an object to contain the new values to insert val newValues = ContentValues().apply { /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value". */ put(UserDictionary.Words.APP_ID, "example.user") put(UserDictionary.Words.LOCALE, "en_US") put(UserDictionary.Words.WORD, "insert") put(UserDictionary.Words.FREQUENCY, "100") } newUri = contentResolver.insert( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI newValues // The values to insert )
Java
// Defines a new Uri object that receives the result of the insertion Uri newUri; ... // Defines an object to contain the new values to insert ContentValues newValues = new ContentValues(); /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value". */ newValues.put(UserDictionary.Words.APP_ID, "example.user"); newValues.put(UserDictionary.Words.LOCALE, "en_US"); newValues.put(UserDictionary.Words.WORD, "insert"); newValues.put(UserDictionary.Words.FREQUENCY, "100"); newUri = getContentResolver().insert( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI newValues // The values to insert );
I dati per la nuova riga vanno in un singolo oggetto ContentValues
, che
ha una forma simile a quella di un cursore di una riga. Le colonne in questo oggetto non devono avere
lo stesso tipo di dati e, se non vuoi specificare alcun valore, puoi impostare una colonna
a null
utilizzando ContentValues.putNull()
.
Lo snippet precedente non aggiunge la colonna _ID
perché questa colonna è mantenuta
automaticamente. Il provider assegna un valore univoco di _ID
a ogni riga
aggiunto. In genere i provider utilizzano questo valore come chiave primaria della tabella.
L'URI dei contenuti restituito in newUri
identifica la riga appena aggiunta con
nel seguente formato:
content://user_dictionary/words/<id_value>
<id_value>
rappresenta i contenuti di _ID
per la nuova riga.
La maggior parte dei provider può rilevare automaticamente questo tipo di URI dei contenuti ed eseguire le operazioni richieste
su quella determinata riga.
Per ottenere il valore di _ID
dal valore Uri
restituito, chiama
ContentUris.parseId()
.
Aggiorna dati
Per aggiornare una riga, utilizza un oggetto ContentValues
con lo stato aggiornato
come con un inserimento e criteri di selezione, proprio come con una query.
Il metodo client che usi è
ContentResolver.update()
. Devi solo aggiungere
all'oggetto ContentValues
per le colonne che stai aggiornando. Se
Se vuoi cancellare i contenuti di una colonna, imposta il valore su null
.
Lo snippet seguente modifica tutte le righe le cui impostazioni internazionali hanno la lingua "en"
in un
avere le impostazioni internazionali null
. Il valore restituito è il numero di righe che sono state aggiornate.
Kotlin
// Defines an object to contain the updated values val updateValues = ContentValues().apply { /* * Sets the updated value and updates the selected words. */ putNull(UserDictionary.Words.LOCALE) } // Defines selection criteria for the rows you want to update val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?" val selectionArgs: Array<String> = arrayOf("en_%") // Defines a variable to contain the number of updated rows var rowsUpdated: Int = 0 ... rowsUpdated = contentResolver.update( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI updateValues, // The columns to update selectionClause, // The column to select on selectionArgs // The value to compare to )
Java
// Defines an object to contain the updated values ContentValues updateValues = new ContentValues(); // Defines selection criteria for the rows you want to update String selectionClause = UserDictionary.Words.LOCALE + " LIKE ?"; String[] selectionArgs = {"en_%"}; // Defines a variable to contain the number of updated rows int rowsUpdated = 0; ... /* * Sets the updated value and updates the selected words. */ updateValues.putNull(UserDictionary.Words.LOCALE); rowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI updateValues, // The columns to update selectionClause, // The column to select on selectionArgs // The value to compare to );
Pulisci l'input utente quando chiami
ContentResolver.update()
. Per scoprire di più su
leggi la sezione Proteggerti da attacchi dannosi.
Elimina i dati
L'eliminazione delle righe è simile al recupero dei dati delle righe. Devi specificare i criteri di selezione per le righe
che vuoi eliminare e il metodo client restituisce il numero di righe eliminate.
Il seguente snippet elimina le righe il cui ID app corrisponde a "user"
. Il metodo restituisce
di righe eliminate.
Kotlin
// Defines selection criteria for the rows you want to delete val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?" val selectionArgs: Array<String> = arrayOf("user") // Defines a variable to contain the number of rows deleted var rowsDeleted: Int = 0 ... // Deletes the words that match the selection criteria rowsDeleted = contentResolver.delete( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI selectionClause, // The column to select on selectionArgs // The value to compare to )
Java
// Defines selection criteria for the rows you want to delete String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] selectionArgs = {"user"}; // Defines a variable to contain the number of rows deleted int rowsDeleted = 0; ... // Deletes the words that match the selection criteria rowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI selectionClause, // The column to select on selectionArgs // The value to compare to );
Pulisci l'input utente quando chiami
ContentResolver.delete()
. Per scoprire di più su
leggi la sezione Protezione contro attacchi dannosi.
Tipi di dati del fornitore
I fornitori di contenuti possono offrire molti tipi di dati diversi. Il fornitore di dizionari utente offre solo testo, ma i fornitori possono anche offrire i seguenti formati:
- Numero intero
- numero intero lungo (lungo)
- virgola mobile
- rappresentazione in virgola mobile lunga (doppio)
Un altro tipo di dati che i provider spesso usano è un BLOB (binario di grandi dimensioni) implementato come
un array di byte da 64 kB. Puoi vedere i tipi di dati disponibili osservando la
Cursor
corso "get" di machine learning.
Il tipo di dati per ogni colonna di un provider è generalmente elencato nella relativa documentazione.
I tipi di dati per il provider del dizionario utente sono elencati nella documentazione di riferimento
per la relativa classe di contratto, UserDictionary.Words
. Le classi di contratto sono
descritto nella sezione Classi contrattuali.
Puoi anche determinare il tipo di dati chiamando Cursor.getType()
.
I provider gestiscono anche le informazioni sul tipo di dati MIME per ogni URI dei contenuti che definiscono. Puoi utilizzare le informazioni di tipo MIME per scoprire se l'applicazione è in grado di gestire i dati o di scegliere un tipo di gestione basato sul tipo MIME. Di solito è necessario Tipo MIME quando si lavora con un provider che contiene elementi strutture di dati o file.
Ad esempio, ContactsContract.Data
del provider di contatti utilizza i tipi MIME per etichettare il tipo di dati di contatto archiviati in ogni
riga di comando. Per ottenere il tipo MIME corrispondente a un URI dei contenuti, chiama
ContentResolver.getType()
.
La sezione Riferimento tipo MIME descrive le la sintassi dei tipi MIME standard e personalizzati.
Forme alternative di accesso del fornitore
Nello sviluppo di applicazioni sono importanti tre forme alternative di accesso del provider:
-
Accesso in batch: puoi creare un gruppo di chiamate di accesso con metodi in
la classe
ContentProviderOperation
e applicarle conContentResolver.applyBatch()
. -
Query asincrone: eseguono le query in un thread separato. Puoi
utilizza un oggetto
CursorLoader
. Gli esempi nella Presentazione della guida ai caricatori come fare. - Accesso ai dati tramite gli intent: anche se non è possibile inviare un intent direttamente a un provider, puoi inviare un intent all'applicazione del provider, è generalmente il più adatto a modificare i dati del fornitore.
L'accesso e la modifica in gruppo tramite gli intent sono descritti nelle sezioni seguenti.
Accesso in batch
L'accesso in batch a un provider è utile per inserire un numero elevato di righe, per inserire di righe in più tabelle nella stessa chiamata al metodo e in generale per l'esecuzione di un insieme operazioni oltre i confini del processo come una transazione, chiamata operazione atomica.
Per accedere a un provider in modalità batch,
crea un array di ContentProviderOperation
oggetti e poi
e inviarli a un fornitore di contenuti
ContentResolver.applyBatch()
. Passi il
l'autorità del fornitore di contenuti rispetto a questo metodo, anziché a un determinato URI di contenuti.
In questo modo, ogni oggetto ContentProviderOperation
nell'array funziona
in un'altra tabella. Una chiamata a ContentResolver.applyBatch()
restituisce un array di risultati.
La descrizione della classe di contratto ContactsContract.RawContacts
include uno snippet di codice che illustra l'inserimento in gruppo.
Accesso ai dati tramite intent
Gli intent possono fornire l'accesso indiretto a un fornitore di contenuti. Puoi consentire all'utente di accedere dati in un provider anche se la tua applicazione non dispone delle autorizzazioni di accesso ottenere un intent di risultato da un'applicazione che dispone di autorizzazioni o attivando un un'applicazione che dispone di autorizzazioni e permette all'utente di eseguire operazioni al suo interno.
Ottenere l'accesso con autorizzazioni temporanee
Puoi accedere ai dati di un fornitore di contenuti, anche se non disponi dell'accesso corretto autorizzazioni, inviando un intent a un'applicazione che dispone delle autorizzazioni ricevere un intent di risultato contenente autorizzazioni URI. Queste sono le autorizzazioni per uno specifico URI di contenuti che durano fino all'attività che riceve le abbiamo completate. L'applicazione con autorizzazioni permanenti concede temporaneamente autorizzazioni impostando un flag nell'intent del risultato:
-
Autorizzazione di lettura:
FLAG_GRANT_READ_URI_PERMISSION
-
Autorizzazione di scrittura:
FLAG_GRANT_WRITE_URI_PERMISSION
Nota: questi flag non danno accesso generale in lettura o scrittura al provider la cui autorità è contenuta nell'URI dei contenuti. L'accesso è valido solo per l'URI stesso.
Quando invii gli URI dei contenuti a un'altra app, includi almeno uno di questi e i flag facoltativi. I flag forniscono le seguenti funzionalità a qualsiasi app che riceve un intent e sceglie come target Android 11 (livello API 30) o versioni successive:
- Leggono o scrivono in base ai dati rappresentati dall'URI dei contenuti, a seconda del flag incluso nell'intent.
- Guadagna pacchetto visibilità nell'app contenente il fornitore di contenuti che corrisponde alle URI. L'app che invia l'intent e l'app che il fornitore di contenuti potrebbe essere costituito da due app diverse.
Un provider definisce le autorizzazioni URI per gli URI dei contenuti nel file manifest, utilizzando il metodo
android:grantUriPermissions
attributo del
<provider>
nonché l'elemento
<grant-uri-permission>
elemento secondario di
<provider>
. Il meccanismo delle autorizzazioni URI è spiegato più dettagliatamente nella
Guida alle autorizzazioni su Android.
Ad esempio, puoi recuperare i dati di un contatto nel Provider di contatti, anche se non
dispongono dell'autorizzazione READ_CONTACTS
. Potresti voler fare
questo in un'applicazione che invia saluti elettronici a un contatto il giorno del suo compleanno. Invece di
richiedendo READ_CONTACTS
, che ti consente di accedere a tutti
i contatti dell'utente e tutte le sue informazioni, consentono all'utente di stabilire quali
contatti utilizzati dalla tua applicazione. Per farlo, segui questa procedura:
-
Nella tua applicazione, invia un intent contenente l'azione
ACTION_PICK
e i "contatti" Tipo MIMECONTENT_ITEM_TYPE
, utilizzando metodostartActivityForResult()
. - Poiché questo intent corrisponde al filtro per intent per "Selezione" dell'app Persone attività, l'attività viene in primo piano.
-
Nell'attività di selezione, l'utente seleziona una
per eseguire l'aggiornamento. In questo caso, l'attività di selezione chiama
setResult(resultcode, intent)
per impostare un'intenzione di donazione alla tua applicazione. L'intent contiene l'URI dei contenuti del contatto selezionato dall'utente e degli "extra" flagFLAG_GRANT_READ_URI_PERMISSION
. Questi flag concedono l'URI l'autorizzazione alla lettura dei dati del contatto a cui punta l'URI contenuto. L'attività di selezione chiama quindifinish()
a e restituire il controllo all'applicazione. -
La tua attività torna in primo piano e il sistema chiama
onActivityResult()
. Questo metodo riceve l'intent del risultato creato dall'attività di selezione in l'app Persone. - Con l'URI dei contenuti dell'intent del risultato, puoi leggere i dati del contatto dal provider di contatti, anche se non hai richiesto l'autorizzazione di accesso in lettura permanente al provider nel tuo manifest. Potrai quindi vedere le informazioni sulla data di nascita del contatto o l'indirizzo email e inviare il messaggio di benvenuto.
Utilizza un'altra applicazione
Un altro modo per consentire all'utente di modificare i dati a cui non disponi delle autorizzazioni di accesso consiste nel attivare un'applicazione che dispone delle autorizzazioni e consentire all'utente di eseguire il lavoro lì.
Ad esempio, l'applicazione Calendar accetta
ACTION_INSERT
per intent che ti consente di attivare
l'interfaccia utente di inserimento dell'applicazione. Puoi passare "extra" per questo intent, che l'applicazione
per precompilare la UI. Poiché gli eventi ricorrenti hanno una sintassi complessa,
di inserire eventi in Calendar Provider è attivare l'app Calendar con un
ACTION_INSERT
e consenti all'utente di inserire l'evento lì.
Visualizza i dati con un'app helper
Se la tua applicazione dispone di autorizzazioni di accesso, puoi comunque utilizzare una
di mostrare dati in un'altra applicazione. Ad esempio, l'applicazione Calendar accetta
intent ACTION_VIEW
che mostra una data o un evento specifico.
In questo modo puoi visualizzare le informazioni del calendario senza dover creare una tua UI.
Per saperne di più su questa funzionalità, consulta
Panoramica del fornitore di Calendar.
L'applicazione a cui invii l'intent non deve necessariamente essere l'applicazione
associati al provider. Ad esempio, puoi recuperare un contatto
Contatta il provider, poi invia un intent ACTION_VIEW
che contenga l'URI dei contenuti dell'immagine del contatto a un visualizzatore di immagini.
Classi contrattuali
Una classe contratto definisce le costanti che consentono alle applicazioni di funzionare con gli URI dei contenuti, colonna
nomi, azioni intent e altre funzionalità di un fornitore di contenuti. Le classi di contratti non sono
incluse automaticamente con un provider. Lo sviluppatore del provider deve definirli e poi
e rendile disponibili ad altri sviluppatori. Molti dei provider inclusi con Android
della piattaforma hanno classi di contratto corrispondenti nel pacchetto android.provider
.
Ad esempio, il provider di dizionario utente ha una classe di contratto
UserDictionary
con costanti URI contenuti e nome colonna. La
l'URI dei contenuti per la tabella Words
è definito nella costante
UserDictionary.Words.CONTENT_URI
.
La classe UserDictionary.Words
contiene anche le costanti dei nomi di colonna,
utilizzati negli snippet di esempio di questa guida. Ad esempio, la proiezione di una query
definiti come segue:
Kotlin
val projection : Array<String> = arrayOf( UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE )
Java
String[] projection = { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
Un'altra classe di contratto è ContactsContract
per il provider di contatti.
La documentazione di riferimento per questa classe include esempi di snippet di codice. Uno dei suoi
di una sottoclasse, ContactsContract.Intents.Insert
, è un contratto
che contenga costanti per intent e dati di intent.
Riferimento tipo MIME
I fornitori di contenuti possono restituire tipi multimediali MIME standard, stringhe di tipo MIME personalizzate o entrambi.
I tipi MIME hanno il seguente formato:
type/subtype
Ad esempio, il noto tipo MIME text/html
ha il tipo text
e
il sottotipo html
. Se il provider restituisce questo tipo per un URI, significa che un
che utilizza questo URI restituisce il testo contenente tag HTML.
Le stringhe di tipo MIME personalizzate, chiamate anche tipi MIME specifici del fornitore, contengono valori type e subtype complessi. Per più righe, il valore del tipo è sempre il seguente:
vnd.android.cursor.dir
Per una singola riga, il valore del tipo è sempre il seguente:
vnd.android.cursor.item
Il parametro subtype è specifico del provider. I provider Android integrati di solito hanno una semplice . Ad esempio, quando l'applicazione Contatti crea una riga per un numero di telefono, imposta il seguente tipo MIME nella riga:
vnd.android.cursor.item/phone_v2
Il valore del sottotipo è phone_v2
.
Gli altri sviluppatori di provider possono creare il proprio pattern di sottotipi in base
autorità e nomi delle tabelle. Prendiamo come esempio un fornitore che contiene gli orari dei treni.
L'autorità del provider è com.example.trains
e contiene le tabelle
Linea1, Linea2 e Linea3. In risposta al seguente URI di contenuti per la tabella Line1:
content://com.example.trains/Line1
il provider restituisce il seguente tipo MIME:
vnd.android.cursor.dir/vnd.example.line1
In risposta al seguente URI di contenuti per la riga 5 della tabella Line2:
content://com.example.trains/Line2/5
il provider restituisce il seguente tipo MIME:
vnd.android.cursor.item/vnd.example.line2
La maggior parte dei fornitori di contenuti definisce le costanti delle classi contrattuali per i tipi MIME utilizzati. La
Classe del contratto del fornitore di contatti ContactsContract.RawContacts
,
Ad esempio, definisce la costante
CONTENT_ITEM_TYPE
per il tipo MIME di
una singola riga di contatto non elaborata.
Gli URI dei contenuti per le singole righe sono descritti nel URI dei contenuti.