Un fournisseur de contenu gère l'accès à un référentiel central de données. Un fournisseur fait partie d'une application Android, qui fournit souvent sa propre interface utilisateur pour travailler les données. Cependant, les fournisseurs de contenu sont principalement utilisés par d'autres applications qui accèdent au fournisseur à l'aide d'un objet client du fournisseur. Ensemble, les fournisseurs et les clients du fournisseur offrent une interface cohérente et standard pour les données, la communication inter-processus et l'accès aux données sécurisé.
Vous collaborez généralement avec des fournisseurs de contenu dans l'un des deux scénarios suivants : pour accéder à un fournisseur de contenu existant dans une autre application ou créer un nouveau fournisseur de contenu dans votre application pour partager des données avec d'autres applications.
Cette page aborde les principes de base de la collaboration avec les fournisseurs de contenu existants. Pour en savoir plus sur l'implémentation fournisseurs de contenu dans vos propres applications, consultez <ph type="x-smartling-placeholder"></ph> Créez un fournisseur de contenu.
Cet article décrit les éléments suivants:
- Fonctionnement des fournisseurs de contenu
- API que vous utilisez pour récupérer des données auprès d'un fournisseur de contenu.
- API que vous utilisez pour insérer, mettre à jour ou supprimer des données dans un fournisseur de contenu.
- Autres fonctionnalités d'API qui facilitent la collaboration avec les fournisseurs
Présentation
Un fournisseur de contenu présente les données aux applications externes sous la forme d'une ou de plusieurs tables semblables aux tableaux trouvés dans une base de données relationnelle. Une ligne représente une instance d'un certain type de données collectées par le fournisseur. Chaque colonne de la ligne représente un élément collectées pour une instance.
Un fournisseur de contenu coordonne l'accès à la couche de stockage des données dans votre application un grand nombre d'API et de composants. Comme le montre la figure 1, il s'agit des éléments suivants:
- Partager l'accès aux données de votre application avec d'autres applications
- Envoyer des données à un widget
- Affichage de suggestions de recherche personnalisées pour votre application via la recherche
framework à l'aide de
SearchRecentSuggestionsProvider
- Synchroniser les données d'application avec votre serveur à l'aide d'une implémentation de
AbstractThreadedSyncAdapter
- Charger des données dans votre interface utilisateur à l'aide d'un
CursorLoader
![Relation entre le fournisseur de contenu et les autres composants.](https://developer.android.com/static/guide/topics/providers/images/content-provider-tech-stack.png?authuser=9&hl=fr)
Figure 1 : Relation entre un fournisseur de contenu et d'autres composants.
Accéder à un fournisseur
Pour accéder aux données d'un fournisseur de contenu, utilisez
l'objet ContentResolver
dans le
Context
pour communiquer avec le fournisseur en tant que client. La
ContentResolver
communique avec l'objet fournisseur, un
instance d'une classe qui implémente ContentProvider
.
Le fournisseur
reçoit les demandes de données des clients, effectue l'action demandée et renvoie
résultats. Cet objet comporte des méthodes qui appellent des méthodes portant un nom identique dans l'objet fournisseur,
Une instance de l'une des sous-classes concrètes de ContentProvider
. La
Les méthodes ContentResolver
fournissent les autorisations de base
"CRUD" (créer, récupérer, mettre à jour et supprimer) du stockage persistant.
Un modèle courant pour accéder à un ContentProvider
à partir de votre interface utilisateur utilise un
CursorLoader
pour exécuter une requête asynchrone en arrière-plan. La
Activity
ou Fragment
dans votre interface utilisateur appelle un
CursorLoader
à la requête, ce qui permet d'obtenir
ContentProvider
à l'aide de ContentResolver
.
Ainsi, l'utilisateur peut continuer à accéder à l'interface utilisateur pendant l'exécution de la requête. Ce implique l'interaction d'un certain nombre d'objets différents, ainsi que l'interaction sous-jacente mécanisme de stockage, comme illustré dans la figure 2.
![Interaction entre ContentProvider, d'autres classes et stockage.](https://developer.android.com/static/guide/topics/providers/images/content-provider-interaction.png?authuser=9&hl=fr)
Figure 2. Interaction entre ContentProvider
, d'autres classes et le stockage.
Remarque:Pour accéder à un fournisseur, votre application doit généralement demander des données dans son fichier manifeste. Ce modèle de développement est décrit plus en détail dans la Section Autorisations du fournisseur de contenu.
L'un des fournisseurs intégrés de la plate-forme Android est le fournisseur de dictionnaire personnel, qui stocke les mots non standards que l'utilisateur souhaite conserver. Le tableau 1 illustre les données se présentent comme suit dans le tableau de ce fournisseur:
Tableau 1:exemple de table avec le dictionnaire personnel.
word | identifiant d'application | de publication | paramètres régionaux | _ID |
---|---|---|---|---|
mapreduce |
utilisateur1 | 100 | fr_FR | 1 |
precompiler |
utilisateur14 | 200 | fr_FR | 2 |
applet |
utilisateur2 | 225 | fr_CA | 3 |
const |
utilisateur1 | 255 | pt_BR | 4 |
int |
utilisateur5 | 100 | fr_FR | 5 |
Dans le tableau 1, chaque ligne représente une instance d'un mot non
que l'on trouve dans un dictionnaire standard. Chaque colonne représente une donnée pour ce mot, comme le
la région dans laquelle il a été rencontré pour la première fois. Les en-têtes de colonne sont
des noms de colonne qui sont stockés dans
le fournisseur. Pour faire référence aux paramètres régionaux d'une ligne, par exemple, vous devez faire référence à la colonne locale
correspondante. Pour
ce fournisseur, la colonne _ID
sert de colonne de clé primaire qui
qu'il gère automatiquement.
Pour obtenir la liste des mots et de leurs paramètres régionaux à partir du fournisseur de dictionnaire personnel,
vous appelez ContentResolver.query()
.
La méthode query()
appelle la méthode
Méthode ContentProvider.query()
définie par le
Fournisseur de dictionnaire personnel. Les lignes de code suivantes affichent
Appel 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
Le tableau 2 montre comment les arguments de
query(Uri,projection,selection,selectionArgs,sortOrder)
correspondent à une instruction SQL SELECT:
Tableau 2:comparaison de query()
avec une requête SQL.
Argument query() |
SELECT mot clé/paramètre | Notes |
---|---|---|
Uri |
FROM table_name |
Uri correspond à la table du fournisseur nommée table_name. |
projection |
col,col,col,... |
projection est un tableau de colonnes inclus pour chaque ligne
récupérées.
|
selection |
WHERE col = value |
selection spécifie les critères de sélection des lignes. |
selectionArgs |
Aucun équivalent exact. Les arguments de sélection remplacent les espaces réservés ? dans la
de sélection.
|
|
sortOrder |
ORDER BY col,col,... |
sortOrder spécifie l'ordre dans lequel les lignes apparaissent dans les
Cursor
|
URI de contenu
Un URI de contenu est un URI qui identifie les données d'un fournisseur. URI de contenu incluent le nom symbolique de l'ensemble du fournisseur (son autorité) et qui pointe vers une table : un chemin d'accès. Lorsque vous appelez une méthode client pour accéder à une table d'un fournisseur, l'URI de contenu de la table est l'un des éléments les arguments.
Dans les lignes de code précédentes, la constante
CONTENT_URI
contient l'URI de contenu de
la table Words
du fournisseur de dictionnaires utilisateur. ContentResolver
analyse l'autorité de l'URI et l'utilise pour résoudre le fournisseur en
en comparant l’autorité à un tableau
système de fournisseurs connus. La
ContentResolver
peut ensuite distribuer les arguments de la requête
un fournisseur de services agréé.
ContentProvider
utilise la partie "chemin" de l'URI de contenu pour choisir la
pour y accéder. Un fournisseur dispose généralement d'un chemin d'accès pour chaque table qu'il expose.
Dans les lignes de code précédentes, l'URI complet de la table Words
est le suivant:
content://user_dictionary/words
- La chaîne
content://
correspond au schéma, qui est toujours présent. et l'identifie en tant qu'URI de contenu. - La chaîne
user_dictionary
représente l'autorité du fournisseur. - La chaîne
words
correspond au chemin d'accès de la table.
De nombreux fournisseurs vous permettent d'accéder à une seule ligne d'une table en ajoutant une valeur d'ID
à la fin de l'URI. Par exemple, pour récupérer une ligne dont la valeur _ID
est
4
du fournisseur de dictionnaire personnel, vous pouvez utiliser cet URI de contenu:
Kotlin
val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)
Java
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
Vous utilisez souvent des valeurs d'ID lorsque vous récupérez un ensemble de lignes, puis souhaitez mettre à jour ou supprimer l'un d'entre eux.
Remarque:Les classes Uri
et Uri.Builder
contiennent des méthodes pratiques permettant de créer des objets d'URI bien formés à partir de chaînes. La
La classe ContentUris
contient des méthodes pratiques permettant d'ajouter des valeurs d'ID à
un URI. L'extrait précédent utilise withAppendedId()
pour ajouter un ID à l'URI de contenu du fournisseur de dictionnaire utilisateur.
Récupérer les données du fournisseur
Cette section explique comment récupérer des données auprès d'un fournisseur, en utilisant le fournisseur de dictionnaire utilisateur à titre d'exemple.
Par souci de clarté, les extraits de code de cette section appellent
ContentResolver.query()
sur le thread UI. Dans
du code réel, en revanche, effectuent des requêtes asynchrones sur un thread distinct. Vous pouvez
Utilisez la classe CursorLoader
, décrite
plus en détail dans le
sur les chargeurs. De plus, les lignes de code ne sont que des extraits. Elles n'affichent pas
application.
Pour récupérer des données auprès d'un fournisseur, procédez comme suit:
- Demande une autorisation d'accès en lecture pour le fournisseur.
- Définissez le code qui envoie une requête au fournisseur.
Demander une autorisation d'accès en lecture
Pour récupérer des données auprès d'un fournisseur, votre application doit disposer d'une autorisation d'accès en lecture pour le
un fournisseur de services agréé. Vous ne pouvez pas demander cette autorisation au moment de l'exécution. Au lieu de cela, vous devez spécifier que
vous avez besoin de cette autorisation dans votre fichier manifeste, à l'aide de la méthode
<uses-permission>
et le nom exact de l'autorisation défini par
un fournisseur de services agréé.
Lorsque vous spécifiez cet élément dans votre fichier manifeste, vous demandez ceci pour votre application. Lorsque les utilisateurs installent votre application, ils accordent implicitement cette demande.
Pour trouver le nom exact de l'autorisation d'accès en lecture pour le fournisseur que vous utilisez, comme les noms des autres autorisations d'accès utilisées par le fournisseur, consultez le fichier dans la documentation Google Cloud.
Le rôle des autorisations dans l'accès aux fournisseurs est décrit plus en détail dans le Section Autorisations du fournisseur de contenu.
Le fournisseur de dictionnaire personnel définit l'autorisation
android.permission.READ_USER_DICTIONARY
dans son fichier manifeste.
l'application qui souhaite lire les données
du fournisseur doit demander cette autorisation.
Construire la requête
L'étape suivante de la récupération de données auprès d'un fournisseur consiste à construire une requête. L'extrait suivant définit certaines variables permettant d'accéder au fournisseur de dictionnaire personnel:
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 = {""};
L'extrait suivant montre comment utiliser
ContentResolver.query()
, à l'aide du dictionnaire personnel
Fournisseur à titre d'exemple. Une requête client de fournisseur est semblable à une requête SQL et contient
un ensemble de colonnes à renvoyer, un ensemble de critères de sélection et un ordre de tri.
L'ensemble de colonnes renvoyé par la requête est appelé projection.
la variable est mProjection
.
L'expression qui spécifie les lignes à récupérer est divisée en une clause de sélection et
des arguments de sélection. La clause de sélection est une combinaison d'expressions logiques et booléennes,
les noms des colonnes
et les valeurs. La variable est mSelectionClause
. Si vous spécifiez le paramètre
paramètre remplaçable ?
au lieu d'une valeur, la méthode de requête récupère la valeur
du tableau des arguments de sélection, qui correspond à la variable mSelectionArgs
.
Dans l'extrait de code suivant, si l'utilisateur ne saisit pas de mot, la clause de sélection est définie
null
, et la requête renvoie tous les mots du fournisseur. Si l'utilisateur saisit
un mot, la clause de sélection est définie sur UserDictionary.Words.WORD + " = ?"
et
le premier élément du tableau d'arguments de sélection est défini sur le mot saisi par l'utilisateur.
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 }
Cette requête est analogue à l'instruction SQL suivante:
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
Dans cette instruction SQL, les noms de colonne réels sont utilisés à la place des constantes de classe de contrat.
Protéger contre les entrées malveillantes
Si les données gérées par le fournisseur de contenu se trouvent dans une base de données SQL, y compris les données externes non approuvées les données en instructions SQL brutes peut entraîner une injection SQL.
Prenons la clause de sélection suivante:
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;
Si vous procédez ainsi, vous permettez à l'utilisateur de potentiellement concaténer du code SQL malveillant dans votre instruction SQL.
Par exemple, l'utilisateur peut saisir "rien ; DROP TABLE *;" pour mUserInput
, qui
aboutit à la clause de sélection var = nothing; DROP TABLE *;
.
Depuis le est traitée comme une instruction SQL, cela peut amener le fournisseur à effacer toutes les tables de la base de données SQLite sous-jacente, sauf si le fournisseur est configuré pour récupérer Tentatives d'injection SQL.
Pour éviter ce problème, utilisez une clause de sélection qui utilise ?
comme élément remplaçable
et un tableau distinct d'arguments de sélection. De cette façon, l’entrée utilisateur
est directement lié à la requête au lieu d’être interprété comme faisant partie d’une instruction SQL.
Comme elle n'est pas traitée comme du code SQL, l'entrée utilisateur ne peut pas injecter du code SQL malveillant. Au lieu d'utiliser
concaténation pour inclure l'entrée utilisateur, utilisez cette clause de sélection:
Kotlin
// Constructs a selection clause with a replaceable parameter var selectionClause = "var = ?"
Java
// Constructs a selection clause with a replaceable parameter String selectionClause = "var = ?";
Configurez le tableau d'arguments de sélection comme suit:
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 = {""};
Placez une valeur dans le tableau des arguments de sélection comme ceci:
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;
Une clause de sélection qui utilise ?
comme paramètre remplaçable et un tableau de
Le tableau d'arguments de sélection est le moyen privilégié pour spécifier une sélection, même si le fournisseur n'est pas
à partir d'une base
de données SQL.
Afficher les résultats de la requête
La méthode cliente ContentResolver.query()
est toujours
renvoie un Cursor
contenant les colonnes spécifiées par la méthode
projection pour les lignes qui correspondent aux critères de sélection de la requête. A
L'objet Cursor
fournit un accès en lecture aléatoire aux lignes et aux colonnes qu'il
contient.
À l'aide des méthodes Cursor
, vous pouvez itérer les lignes du
les résultats, déterminer le type de données de chaque colonne, extraire les données d'une colonne et examiner d'autres
des résultats.
Certaines implémentations de Cursor
sont automatiques
mettre à jour l'objet lorsque les données du fournisseur changent, déclencher des méthodes dans un objet observateur
lorsque Cursor
change, ou les deux.
Remarque:Un fournisseur peut limiter l'accès aux colonnes en fonction de la nature à l'origine de la requête. Par exemple, Contacts Provider restreint l'accès de certaines colonnes aux adaptateurs de synchronisation, et ne les renvoie pas vers une activité ou un service.
Si aucune ligne ne correspond aux critères de sélection, le fournisseur
renvoie un objet Cursor
pour lequel
Cursor.getCount()
correspond à
0, c'est-à-dire un curseur vide.
Si une erreur interne se produit, les résultats de la requête dépendent du fournisseur concerné. Il pourrait
renvoyer null
ou générer une Exception
.
Étant donné qu'un Cursor
est une liste de lignes, un bon moyen d'afficher le
le contenu d'un Cursor
consiste à le lier à ListView
à l'aide d'un SimpleCursorAdapter
.
L'extrait de code suivant reprend le code de l'extrait précédent. Il crée un
Objet SimpleCursorAdapter
contenant le Cursor
récupéré par la requête, et définit cet objet en tant qu'adaptateur pour une
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);
Remarque:Pour sauvegarder un ListView
avec une
Cursor
, le curseur doit contenir une colonne nommée _ID
.
De ce fait, la requête précédente récupère la colonne _ID
pour le
Words
, même si ListView
ne l'affiche pas.
Cette restriction explique également pourquoi la plupart des fournisseurs disposent d'une colonne _ID
pour chacun des
leurs tableaux.
Obtenir des données à partir des résultats de requête
En plus d'afficher les résultats des requêtes, vous pouvez les utiliser pour d'autres tâches. Pour
Par exemple, vous pouvez récupérer les orthographes
auprès du fournisseur de dictionnaire personnel, puis les rechercher
avec d'autres fournisseurs. Pour ce faire, vous itérez les lignes de Cursor
, comme illustré dans l'exemple suivant:
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 }
Les implémentations Cursor
contiennent plusieurs méthodes "get" méthodes pour
récupérer différents types de données de l'objet. Par exemple, l'extrait précédent
utilise getString()
. Ils ont également un
getType()
qui renvoie une valeur indiquant
le type de données de la colonne.
Autorisations du fournisseur de contenu
L'application d'un fournisseur peut spécifier des autorisations que d'autres applications doivent accéder aux données du fournisseur. Ces autorisations permettent à l'utilisateur de savoir quelles données à laquelle une application tente d'accéder. Selon les exigences du fournisseur, d'autres applications demander les autorisations dont il a besoin pour accéder au fournisseur. Les utilisateurs finaux voient lorsqu'ils installent l'application.
Si l'application d'un fournisseur ne spécifie aucune autorisation, les autres applications n'ont pas aux données du fournisseur, sauf s'il est exporté. En outre, les composants dans l'application du fournisseur disposent toujours d'un accès complet en lecture et en écriture, les autorisations spécifiées.
L'API User Dictionary Provider exige que
Autorisation android.permission.READ_USER_DICTIONARY
pour récupérer des données.
Le fournisseur dispose d'un android.permission.WRITE_USER_DICTIONARY
distinct
l'autorisation d'insérer, de mettre à jour ou de supprimer des données.
Pour obtenir les autorisations nécessaires pour accéder à un fournisseur, une application les demande à l'aide d'un
<uses-permission>
dans son fichier manifeste. Lorsque le gestionnaire de packages Android installe l'application, l'utilisateur
doit approuver toutes les autorisations demandées par l'application. Si l'utilisateur
les approuve,
Le gestionnaire de paquets poursuit l'installation. Si l'utilisateur ne les approuve pas, le gestionnaire de packages
arrête l'installation.
L'exemple suivant
<uses-permission>
demande un accès en lecture au fournisseur de dictionnaire utilisateur:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
L'impact des autorisations sur l'accès du fournisseur est expliqué plus en détail dans Conseils de sécurité
Insérer, mettre à jour et supprimer des données
De la même manière que vous récupérez des données auprès d'un fournisseur, vous utilisez également l'interaction entre
un client de fournisseur et son ContentProvider
pour modifier les données.
Vous appelez une méthode de ContentResolver
avec des arguments transmis à
la méthode correspondante de ContentProvider
. Fournisseur et fournisseur
la sécurité et la communication inter-processus.
Insérer des données
Pour insérer des données dans un fournisseur, vous devez appeler la méthode
ContentResolver.insert()
. Cette méthode insère une ligne dans le fournisseur et renvoie un URI de contenu pour cette ligne.
L'extrait de code suivant montre comment insérer un nouveau mot dans le fournisseur de dictionnaire utilisateur:
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 );
Les données de la nouvelle ligne sont stockées dans un seul objet ContentValues
, qui
est similaire en forme à
un curseur à une ligne. Il n'est pas nécessaire que les colonnes de cet objet
le même type de données, et si vous ne souhaitez pas du tout spécifier de valeur, vous pouvez définir une colonne
à null
avec ContentValues.putNull()
.
L'extrait précédent n'ajoute pas la colonne _ID
, car celle-ci est conservée
automatiquement. Le fournisseur attribue une valeur unique de _ID
à chaque ligne
ajouté. Les fournisseurs utilisent généralement cette valeur comme clé primaire de la table.
L'URI de contenu renvoyé dans newUri
identifie la ligne qui vient d'être ajoutée avec
au format suivant:
content://user_dictionary/words/<id_value>
<id_value>
correspond au contenu de _ID
pour la nouvelle ligne.
La plupart des fournisseurs peuvent détecter automatiquement ce type d'URI de contenu, puis effectuer la requête
sur cette ligne.
Pour obtenir la valeur de _ID
à partir de l'élément Uri
renvoyé, appelez
ContentUris.parseId()
Mettre à jour des données
Pour mettre à jour une ligne, utilisez un objet ContentValues
avec la valeur
des valeurs, comme avec un critère d'insertion, et un critère de sélection, comme pour une requête.
La méthode client
que vous utilisez est
ContentResolver.update()
Il vous suffit d'ajouter
à l'objet ContentValues
pour les colonnes que vous mettez à jour. Si vous
Si vous souhaitez effacer le contenu d'une colonne, définissez la valeur sur null
.
L'extrait de code suivant remplace toutes les lignes dont les paramètres régionaux sont "en"
par une
les paramètres régionaux sont null
. La valeur renvoyée correspond au nombre de lignes qui ont été mises à jour.
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 );
Nettoyer les entrées utilisateur lorsque vous appelez
ContentResolver.update()
Pour en savoir plus sur
consultez la section Se protéger contre les entrées malveillantes.
Supprimer vos données
La suppression de lignes est semblable à la récupération des données de ligne. Vous spécifiez des critères de sélection pour les lignes
que vous souhaitez supprimer, et la méthode client renvoie le nombre de lignes supprimées.
L'extrait de code suivant supprime les lignes dont l'ID d'application correspond à "user"
. La méthode renvoie
le nombre de lignes supprimées.
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 );
Nettoyer les entrées utilisateur lorsque vous appelez
ContentResolver.delete()
Pour en savoir plus sur
consultez la section Se protéger contre les entrées malveillantes.
Types de données du fournisseur
Les fournisseurs de contenu peuvent proposer de nombreux types de données différents. Le fournisseur de dictionnaire personnel ne propose que mais les fournisseurs peuvent également proposer les formats suivants:
- Nombre entier
- Entier long (long)
- virgule flottante
- Longue virgule flottante (double)
Un autre type de données que les fournisseurs utilisent souvent est un BLOB (Binary Large Object) implémenté en tant que
Tableau d'octets de 64 Ko. Vous pouvez consulter les types de données disponibles en consultant les
Classe Cursor
: "get" méthodes.
Le type de données de chaque colonne d'un fournisseur est généralement indiqué dans sa documentation.
Les types de données du fournisseur de dictionnaire personnel sont répertoriés dans la documentation de référence.
pour sa classe de contrat, UserDictionary.Words
. Les classes de contrat sont
décrites dans la section Catégories de contrats.
Vous pouvez également déterminer le type de données en appelant Cursor.getType()
.
Les fournisseurs gèrent également les informations sur le type de données MIME pour chaque URI de contenu qu'ils définissent. Vous pouvez utiliser les informations du type MIME pour déterminer si votre application peut gérer les données que le ou choisir un type de traitement en fonction du type MIME. Vous avez généralement besoin MIME lorsque vous travaillez avec un fournisseur contenant des données les structures de données ou les fichiers.
Par exemple, ContactsContract.Data
du fournisseur de contacts utilise des types MIME pour étiqueter le type de données de contact stockées dans chaque
ligne. Pour obtenir le type MIME correspondant à un URI de contenu, appelez
ContentResolver.getType()
La section Référence des types MIME décrit le des types MIME standards et personnalisés.
Autres formes d'accès au fournisseur
Trois autres formes d'accès des fournisseurs sont importantes dans le développement d'applications:
-
Accès par lots: vous pouvez créer un lot d'appels d'accès à l'aide de méthodes dans
la classe
ContentProviderOperation
, puis appliquez-les avecContentResolver.applyBatch()
-
Requêtes asynchrones: effectuez des requêtes dans un thread distinct. Vous pouvez
Utilisez un objet
CursorLoader
. Les exemples de la section les chargeurs vous montrent comment procéder. - Accès aux données à l'aide d'intents, bien que vous ne puissiez pas envoyer d'intent directement à un fournisseur, vous pouvez envoyer un intent à son application, généralement les mieux équipés pour modifier les données du fournisseur.
Les sections suivantes décrivent l'accès et la modification par lot à l'aide d'intents.
Accès par lot
L'accès par lot à un fournisseur est utile pour insérer un grand nombre de lignes, lignes de plusieurs tables dans le même appel de méthode et, en général, pour effectuer un ensemble de au-delà des limites des processus sous la forme d'une transaction, appelée opération atomique.
Pour accéder à un fournisseur en mode de traitement par lot,
créer un tableau d'objets ContentProviderOperation
, puis
et les envoyer à un fournisseur de contenu
ContentResolver.applyBatch()
Vous transmettez la
l'autorité du fournisseur de contenu à cette méthode, plutôt qu'à un URI de contenu particulier.
Ainsi, chaque objet ContentProviderOperation
du tableau peut fonctionner
sur une autre table. Un appel à ContentResolver.applyBatch()
renvoie un tableau de résultats.
Description de la classe de contrat ContactsContract.RawContacts
inclut un extrait de code pour illustrer l'insertion groupée.
Accès aux données à l'aide d'intents
Les intents peuvent fournir un accès indirect à un fournisseur de contenu. Vous pouvez autoriser l'utilisateur à accéder des données chez un fournisseur, même si votre application ne dispose d'aucune autorisation d'accès en récupérant un intent de résultat depuis une application disposant d'autorisations ou en activant un une application qui dispose d'autorisations et de laisser l'utilisateur y travailler.
Obtenir un accès avec des autorisations temporaires
Vous pouvez accéder aux données d'un fournisseur de contenu, même si vous ne disposez pas de l'accès approprié les autorisations, en envoyant un intent à une application qui dispose des autorisations recevoir en retour un intent de résultat contenant des autorisations d'URI. Il s'agit d'autorisations pour un URI de contenu spécifique, valables jusqu'à ce que l'activité qui reçoit est terminée. L'application qui dispose d'autorisations permanentes accorde des autorisations les autorisations en définissant un indicateur dans l'intent de résultat:
-
Autorisation de lecture:
FLAG_GRANT_READ_URI_PERMISSION
-
Autorisation d'écriture:
FLAG_GRANT_WRITE_URI_PERMISSION
Remarque:Ces options n'accordent pas d'accès général en lecture ou en écriture au fournisseur dont l'autorité est contenue dans l'URI de contenu. L'accès ne concerne que l'URI lui-même.
Lorsque vous envoyez des URI de contenu à une autre application, incluez au moins l'un de ces options. Les indicateurs fournissent les fonctionnalités suivantes à toute application qui reçoit un intent et cible Android 11 (niveau d'API 30) ou version ultérieure:
- Lire ou écrire dans les données représentées par l'URI de contenu en fonction de l'indicateur inclus dans l'intent.
- Remporter un package visibilité sur l'application contenant le fournisseur de contenu correspondant au autorité URI. L'application qui envoie l'intent et l'application qui qui contient le fournisseur de contenu peuvent être deux applications différentes.
Un fournisseur définit les autorisations d'URI pour les URI de contenu dans son fichier manifeste à l'aide de la méthode
android:grantUriPermissions
de l'attribut
<provider>
ainsi que l'élément
<grant-uri-permission>
l'élément enfant de
<provider>
. Le mécanisme des autorisations d'URI est expliqué plus en détail dans la
Guide Autorisations sur Android.
Par exemple, vous pouvez récupérer les données d'un contact dans Contacts Provider, même si vous n'utilisez pas
disposer de l'autorisation READ_CONTACTS
. Vous voudrez peut-être faire
dans une application qui envoie des e-mails d'accueil
à un contact le jour de son anniversaire. Au lieu de
demandant READ_CONTACTS
, ce qui vous donne accès à toutes
les contacts de l'utilisateur et toutes ses informations, lui permettent de contrôler quels
contacts utilisés par votre application. Pour ce faire, procédez comme suit:
-
Dans votre application, envoyez un intent contenant l'action
ACTION_PICK
et les "contacts" Type MIMECONTENT_ITEM_TYPE
, à l'aide de la méthodestartActivityForResult()
. - Comme cet intent correspond au filtre d'intent "Sélection" de l'application Contacts l'activité passe au premier plan.
-
Dans l'activité de sélection, l'utilisateur sélectionne un
contact à mettre à jour. Dans ce cas, l'activité de sélection appelle
setResult(resultcode, intent)
pour configurer un intent à restituer à votre application. L'intent contient l'URI de contenu du contact sélectionné par l'utilisateur et les "extras" indicateursFLAG_GRANT_READ_URI_PERMISSION
Ces options accordent un URI autorisation à votre application de lire les données du contact indiqué par le l'URI de contenu. L'activité de sélection appelle ensuitefinish()
pour à votre application. -
Votre activité revient au premier plan, et le système appelle la méthode
onActivityResult()
. Cette méthode reçoit l'intent de résultat créé par l'activité de sélection dans l'application Contacts. - Avec l'URI de contenu de l'intent de résultat, vous pouvez lire les données du contact à partir du fournisseur de contacts, même si vous n'avez pas demandé d'autorisation d'accès en lecture permanente au fournisseur dans votre fichier manifeste. Vous pouvez ensuite obtenir la date de naissance du contact ou votre adresse e-mail, puis envoyez le message d'accueil.
Utiliser une autre application
Vous pouvez également permettre à l'utilisateur de modifier des données pour lesquelles vous ne disposez pas des autorisations d'accès. activer une application qui a des autorisations et laisser l'utilisateur faire le travail là.
Par exemple, l'application Agenda accepte une
L'intent ACTION_INSERT
qui vous permet d'activer l'intent
l'interface utilisateur d'insertion de l'application. Vous pouvez ajouter des "extras" de données de cet intent, que l'application
utilise pour préremplir l'UI. Les événements périodiques ayant une syntaxe complexe, il est préférable
méthode d'insertion d'événements dans le fournisseur d'agendas consiste à activer l'application Agenda avec une
ACTION_INSERT
, puis laissez l'utilisateur insérer l'événement à cet endroit.
Afficher des données à l'aide d'une application d'assistance
Si votre application dispose d'autorisations d'accès, vous pouvez tout de même utiliser un
pour afficher des données
dans une autre application. Par exemple, l'application Agenda accepte une
Intent ACTION_VIEW
qui affiche une date ou un événement particulier.
Cela vous permet d'afficher des informations d'agenda sans avoir à créer votre propre interface utilisateur.
Pour en savoir plus sur cette fonctionnalité, consultez les
Présentation du fournisseur d'agenda
L'application à laquelle vous envoyez l'intent ne doit pas nécessairement être l'application
associées au fournisseur. Par exemple, vous pouvez récupérer un contact
Contacter le fournisseur, puis envoyer un intent ACTION_VIEW
contenant l'URI de contenu de l'image du contact dans une visionneuse d'images.
Catégories de contrats
Une classe de contrat définit des constantes qui aident les applications à fonctionner avec les URI de contenu,
les noms, actions d'intent et autres fonctionnalités d'un fournisseur de contenu. Les classes contractuelles ne sont pas
inclus automatiquement
à un fournisseur. Le développeur du fournisseur
doit les définir, puis
les mettre à la disposition
d'autres développeurs. De nombreux fournisseurs inclus dans l'application Android
plate-forme ont des classes de contrat correspondantes dans le package android.provider
.
Par exemple, le fournisseur de dictionnaires personnels a une classe de contrat
UserDictionary
contenant les constantes de l'URI de contenu et du nom de colonne. La
l'URI de contenu de la table Words
est défini dans la constante
UserDictionary.Words.CONTENT_URI
La classe UserDictionary.Words
contient également des constantes de nom de colonne,
utilisés dans les exemples d'extraits de ce guide. Par exemple, une projection de requête peut être
défini comme suit:
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 };
Une autre classe de contrat est ContactsContract
pour Contacts Provider.
La documentation de référence de cette classe inclut des exemples d'extraits de code. L'une de ses
sous-classes, ContactsContract.Intents.Insert
, est un contrat
qui contient des constantes pour les intents et les données d'intent.
Documentation de référence sur le type MIME
Les fournisseurs de contenu peuvent renvoyer des types de médias MIME standards, des chaînes de types MIME personnalisées, ou les deux.
Les types MIME ont le format suivant:
type/subtype
Par exemple, le type MIME bien connu text/html
est de type text
et
sous-type html
. Si le fournisseur renvoie ce type pour un URI, cela signifie qu'un
utilisant cet URI, elle renvoie un texte contenant des balises HTML.
Les chaînes de types MIME personnalisées, également appelées types MIME propres au fournisseur, disposent les valeurs type et subtype complexes. Dans le cas de plusieurs lignes, la valeur du type est toujours la suivante:
vnd.android.cursor.dir
Pour une seule ligne, la valeur de type est toujours la suivante:
vnd.android.cursor.item
Le subtype est spécifique au fournisseur. Les fournisseurs Android intégrés ont généralement un simple sous-type d'instance. Par exemple, lorsque l'application Contacts crée une ligne pour un numéro de téléphone, le type MIME suivant est défini sur la ligne:
vnd.android.cursor.item/phone_v2
La valeur du sous-type est phone_v2
.
Les développeurs d'autres fournisseurs peuvent créer leur propre modèle de sous-types en fonction de la configuration
l’autorité et les noms
de table. Prenons l'exemple d'un fournisseur qui contient les horaires des trains.
L'autorité du fournisseur est com.example.trains
, et elle contient les tables
Line1, Line2 et Line3. En réponse à l'URI de contenu suivant pour la table Line1:
content://com.example.trains/Line1
Le fournisseur renvoie le type MIME suivant:
vnd.android.cursor.dir/vnd.example.line1
En réponse à l'URI de contenu suivant pour la ligne 5 de la table Line2:
content://com.example.trains/Line2/5
Le fournisseur renvoie le type MIME suivant:
vnd.android.cursor.item/vnd.example.line2
La plupart des fournisseurs de contenu définissent des constantes de classe de contrat pour les types MIME qu'ils utilisent. La
Classe de contrat Contacts Provider ContactsContract.RawContacts
,
par exemple, définit la constante
CONTENT_ITEM_TYPE
pour le type MIME de
une seule ligne de contact brute.
Les URI de contenu pour des lignes uniques sont décrits dans la section URI de contenu.