Principes de base des fournisseurs de contenu

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.

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&#39;autres classes et stockage.

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:

  1. Demande une autorisation d'accès en lecture pour le fournisseur.
  2. 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.

Libérer les ressources de résultats de requête

Les objets Cursor doivent être fermés s'ils ne sont plus nécessaires, afin que les ressources qui leur sont associées soient libérées plus vite. Pour cela, vous pouvez appeler close(), ou en utilisant une instruction try-with-resources en langage de programmation Java, ou la use() en langage de programmation Kotlin.

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:

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:

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:

  1. Dans votre application, envoyez un intent contenant l'action ACTION_PICK et les "contacts" Type MIME CONTENT_ITEM_TYPE, à l'aide de la méthode startActivityForResult().
  2. Comme cet intent correspond au filtre d'intent "Sélection" de l'application Contacts l'activité passe au premier plan.
  3. 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" indicateurs FLAG_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 ensuite finish() pour à votre application.
  4. 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.
  5. 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.