Créer un fournisseur de contenu

Un fournisseur de contenu gère l'accès à un référentiel central de données. Vous implémentez un fournisseur sous la forme d'une ou plusieurs classes dans une application Android, avec les éléments du fichier manifeste. L'une de vos classes implémente une sous-classe de ContentProvider, qui constitue l'interface entre votre fournisseur et d'autres applications.

Bien que les fournisseurs de contenu soient conçus pour mettre des données à la disposition d'autres applications, certaines activités peuvent permettre à l'utilisateur d'interroger et de modifier les données gérées par votre fournisseur.

Cette page présente la procédure de base pour créer un fournisseur de contenu, ainsi qu'une liste des API à utiliser.

Avant de commencer à créer

Avant de commencer à créer un fournisseur, tenez compte des points suivants:

  • Déterminez si vous avez besoin d'un fournisseur de contenu. Vous devez créer un fournisseur de contenu si vous souhaitez fournir une ou plusieurs des fonctionnalités suivantes :
    • Vous souhaitez proposer des données ou des fichiers complexes à d'autres applications.
    • Vous souhaitez autoriser les utilisateurs à copier des données complexes de votre application vers d'autres.
    • Vous souhaitez fournir des suggestions de recherche personnalisées à l'aide du framework de recherche.
    • Vous souhaitez exposer les données de votre application à des widgets.
    • Vous souhaitez implémenter les classes AbstractThreadedSyncAdapter, CursorAdapter ou CursorLoader.

    Vous n'avez pas besoin d'un fournisseur pour utiliser des bases de données ou d'autres types de stockage persistant si l'utilisation relève entièrement de votre propre application et que vous n'avez besoin d'aucune des fonctionnalités énumérées ci-dessus. À la place, vous pouvez utiliser l'un des systèmes de stockage décrits dans la section Présentation du stockage des données et des fichiers.

  • Si vous ne l'avez pas déjà fait, consultez les Principes de base des fournisseurs de contenu pour en savoir plus sur les fournisseurs et leur fonctionnement.

Ensuite, procédez comme suit pour créer votre fournisseur:

  1. Concevez le stockage brut pour vos données. Un fournisseur de contenu propose des données de deux manières :
    Données de fichiers
    Données qui sont normalement incluses dans des fichiers, tels que des photos, de l'audio ou des vidéos. Stockez les fichiers dans l'espace privé de votre application. En réponse à une requête de fichier provenant d'une autre application, votre fournisseur peut proposer un handle vers le fichier.
    Données "structurées"
    Données qui sont normalement stockées dans une base de données, un tableau ou une structure similaire. Stockez les données dans un format compatible avec les tables de lignes et de colonnes. Une ligne représente une entité, telle qu'une personne ou un article de l'inventaire. Une colonne représente certaines données de l'entité, telles que le nom d'une personne ou le prix d'un article. Un moyen courant de stocker ce type de données consiste à utiliser une base de données SQLite, mais vous pouvez utiliser n'importe quel type de stockage persistant. Pour en savoir plus sur les types de stockage disponibles dans le système Android, consultez la section Concevoir le stockage de données.
  2. Définissez une implémentation concrète de la classe ContentProvider et des méthodes requises. Cette classe constitue l'interface entre vos données et le reste du système Android. Pour en savoir plus sur cette classe, consultez la section Implémenter la classe ContentProvider.
  3. Définit la chaîne d'autorité du fournisseur, les URI de contenu et les noms de colonnes. Si vous souhaitez que l'application du fournisseur gère les intents, définissez également des actions d'intent, des données supplémentaires et des options. Définissez également les autorisations dont vous avez besoin pour les applications qui souhaitent accéder à vos données. Envisagez de définir toutes ces valeurs en tant que constantes dans une classe de contrat distincte. Vous pourrez ensuite exposer cette classe à d'autres développeurs. Pour en savoir plus sur les URI de contenu, consultez la section Concevoir des URI de contenu. Pour en savoir plus sur les intents, consultez la section Intents et accès aux données.
  4. Ajoutez d'autres éléments facultatifs, tels que des échantillons de données ou une mise en œuvre de AbstractThreadedSyncAdapter pouvant synchroniser les données entre le fournisseur et les données cloud.

Concevoir le stockage de données

Un fournisseur de contenu est l'interface permettant d'accéder aux données enregistrées dans un format structuré. Avant de créer l'interface, décidez comment stocker les données. Vous pouvez stocker les données sous la forme de votre choix, puis concevoir l'interface pour qu'elle puisse lire et écrire les données selon vos besoins.

Voici quelques-unes des technologies de stockage de données disponibles sur Android:

  • Si vous travaillez avec des données structurées, envisagez d'utiliser une base de données relationnelle telle que SQLite ou un datastore clé-valeur non relationnel tel que LevelDB. Si vous travaillez avec des données non structurées telles que des fichiers audio, image ou vidéo, envisagez de les stocker sous forme de fichiers. Vous pouvez combiner différents types de stockage et les exposer à l'aide d'un seul fournisseur de contenu si nécessaire.
  • Le système Android peut interagir avec la bibliothèque de persistance Room, qui donne accès à l'API de base de données SQLite que les propres fournisseurs Android utilisent pour stocker des données orientées table. Pour créer une base de données à l'aide de cette bibliothèque, instanciez une sous-classe de RoomDatabase, comme décrit dans la section Enregistrer des données dans une base de données locale à l'aide de Room.

    Vous n'avez pas besoin d'utiliser une base de données pour implémenter votre dépôt. Un fournisseur apparaît en externe sous la forme d'un ensemble de tables, semblable à une base de données relationnelle, mais ce n'est pas obligatoire pour l'implémentation interne du fournisseur.

  • Pour stocker les données de fichiers, Android dispose de diverses API orientées fichiers. Pour en savoir plus sur le stockage de fichiers, consultez la présentation du stockage des données et des fichiers. Si vous concevez un fournisseur qui propose des données multimédias telles que de la musique ou des vidéos, vous pouvez choisir un fournisseur qui combine des données de table et des fichiers.
  • Dans de rares cas, il peut être utile de mettre en œuvre plusieurs fournisseurs de contenu pour une même application. Par exemple, vous pouvez partager certaines données avec un widget à l'aide d'un fournisseur de contenu, et exposer un ensemble de données différent pour le partage avec d'autres applications.
  • Pour travailler avec des données basées sur le réseau, utilisez des classes dans java.net et android.net. Vous pouvez également synchroniser des données basées sur le réseau avec un datastore local tel qu'une base de données, puis proposer ces données sous forme de tables ou de fichiers.

Remarque: Si vous apportez à votre dépôt une modification qui n'est pas rétrocompatible, vous devez marquer le dépôt avec un nouveau numéro de version. Vous devez également augmenter le numéro de version de l'application qui implémente le nouveau fournisseur de contenu. Cette modification empêche le retour à une version antérieure du système de provoquer le plantage du système lorsqu'il tente de réinstaller une application dont le fournisseur de contenu est incompatible.

Considérations relatives à la conception des données

Voici quelques conseils pour concevoir la structure de données de votre fournisseur:

  • Les données d'une table doivent toujours comporter une colonne "clé primaire" gérée par le fournisseur sous la forme d'une valeur numérique unique pour chaque ligne. Vous pouvez utiliser cette valeur pour associer la ligne à des lignes associées dans d'autres tables (en l'utilisant comme "clé étrangère"). Bien que vous puissiez utiliser n'importe quel nom pour cette colonne, BaseColumns._ID est le meilleur choix, car associer les résultats d'une requête de fournisseur à un ListView nécessite que l'une des colonnes récupérées porte le nom _ID.
  • Si vous souhaitez fournir des images bitmap ou d'autres éléments très volumineux de données orientées fichier, stockez les données dans un fichier, puis fournissez-les indirectement au lieu de les stocker directement dans une table. Dans ce cas, vous devez indiquer aux utilisateurs de votre fournisseur qu'ils doivent utiliser une méthode de fichier ContentResolver pour accéder aux données.
  • Utilisez le type de données BLOB (Binary Large Object) pour stocker des données de taille variable ou présentant une structure variable. Par exemple, vous pouvez utiliser une colonne BLOB pour stocker un tampon de protocole ou une structure JSON.

    Vous pouvez également utiliser un BLOB pour implémenter une table indépendante du schéma. Dans ce type de table, vous définissez une colonne de clé primaire, une colonne de type MIME et une ou plusieurs colonnes génériques en tant que BLOB. La signification des données des colonnes BLOB est indiquée par la valeur figurant dans la colonne du type MIME. Cela vous permet de stocker différents types de lignes dans la même table. La table "data" ContactsContract.Data du fournisseur de contacts est un exemple de table indépendante du schéma.

Concevoir des URI de contenu

Un URI de contenu est un URI qui identifie les données d'un fournisseur. Les URI de contenu incluent le nom symbolique de l'ensemble du fournisseur (son autorité) et un nom qui pointe vers une table ou un fichier (un chemin d'accès). La partie facultative de l'ID pointe vers une ligne individuelle d'une table. Chaque méthode d'accès aux données de ContentProvider possède un URI de contenu comme argument. Cela vous permet de déterminer la table, la ligne ou le fichier auquel accéder.

Pour en savoir plus sur les URI de contenu, consultez la section Principes de base du fournisseur de contenu.

Concevoir une autorité

Un fournisseur dispose généralement d'une autorité unique, qui sert de nom interne à Android. Pour éviter les conflits avec d'autres fournisseurs, définissez la propriété du domaine Internet (à l'envers) comme base de votre autorité de fournisseur. Comme cette recommandation s'applique également aux noms de packages Android, vous pouvez définir votre autorité de fournisseur en tant qu'extension du nom du package contenant le fournisseur.

Par exemple, si le nom de votre package Android est com.example.<appname>, attribuez à votre fournisseur l'autorité com.example.<appname>.provider.

Concevoir une structure de tracé

Les développeurs créent généralement des URI de contenu à partir de l'autorité en ajoutant des chemins qui pointent vers des tables individuelles. Par exemple, si vous disposez de deux tables, table1 et table2, vous pouvez les combiner avec l'autorité de l'exemple précédent pour obtenir les URI de contenu com.example.<appname>.provider/table1 et com.example.<appname>.provider/table2. Les chemins d'accès ne se limitent pas à un seul segment, et il ne doit pas nécessairement y avoir de tableau pour chaque niveau du chemin.

Gérer les ID d'URI de contenu

Par convention, les fournisseurs permettent d'accéder à une seule ligne d'une table en acceptant un URI de contenu avec une valeur d'ID pour la ligne à la fin de l'URI. De plus, par convention, les fournisseurs mettent en correspondance la valeur de l'ID avec la colonne _ID de la table et exécutent l'accès demandé sur la ligne correspondante.

Cette convention facilite un modèle de conception courant pour les applications accédant à un fournisseur. L'application effectue une requête sur le fournisseur et affiche le résultat Cursor dans un ListView à l'aide d'un CursorAdapter. La définition de CursorAdapter nécessite que l'une des colonnes de Cursor soit _ID

L'utilisateur sélectionne ensuite l'une des lignes affichées dans l'interface utilisateur pour examiner ou modifier les données. L'application obtient la ligne correspondante à partir du Cursor qui sauvegarde ListView, obtient la valeur _ID pour cette ligne, l'ajoute à l'URI de contenu et envoie la requête d'accès au fournisseur. Le fournisseur peut ensuite interroger ou modifier la ligne exacte sélectionnée par l'utilisateur.

Formats d'URI de contenu

Pour vous aider à choisir l'action à effectuer pour un URI de contenu entrant, l'API du fournisseur inclut la classe pratique UriMatcher, qui mappe les modèles d'URI de contenu à des valeurs entières. Vous pouvez utiliser les valeurs entières dans une instruction switch qui choisit l'action souhaitée pour le ou les URI de contenu correspondant à un modèle particulier.

Un modèle d'URI de contenu met en correspondance les URI de contenu à l'aide de caractères génériques:

  • * correspond à une chaîne de n'importe quel caractère valide de n'importe quelle longueur.
  • # correspond à une chaîne de caractères numériques de n'importe quelle longueur.

Comme exemple de conception et de codage de la gestion d'URI de contenu, prenons l'exemple d'un fournisseur doté de l'autorité com.example.app.provider qui reconnaît les URI de contenu suivants pointant vers des tables:

  • content://com.example.app.provider/table1: une table appelée table1.
  • content://com.example.app.provider/table2/dataset1: une table appelée dataset1.
  • content://com.example.app.provider/table2/dataset2: une table appelée dataset2.
  • content://com.example.app.provider/table3: une table appelée table3.

Le fournisseur reconnaît également ces URI de contenu s'ils sont associés à un ID de ligne, tel que content://com.example.app.provider/table3/1 pour la ligne identifiée par 1 dans table3.

Les formats d'URI de contenu suivants sont possibles:

content://com.example.app.provider/*
Correspond à n'importe quel URI de contenu du fournisseur.
content://com.example.app.provider/table2/*
Correspond à un URI de contenu pour les tables dataset1 et dataset2, mais pas aux URI de contenu pour table1 ou table3.
content://com.example.app.provider/table3/#
Correspond à un URI de contenu pour des lignes uniques dans table3, par exemple content://com.example.app.provider/table3/6 pour la ligne identifiée par 6.

L'extrait de code suivant montre le fonctionnement des méthodes dans UriMatcher. Ce code gère les URI d'une table entière différemment des URI d'une seule ligne en utilisant le format d'URI de contenu content://<authority>/<path> pour les tables et content://<authority>/<path>/<id> pour les lignes uniques.

La méthode addURI() mappe une autorité et un chemin d'accès à une valeur entière. La méthode match() renvoie la valeur entière d'un URI. Une instruction switch choisit d'interroger la table entière ou d'interroger un seul enregistrement.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

Une autre classe, ContentUris, fournit des méthodes pratiques pour travailler avec la partie id des URI de contenu. Les classes Uri et Uri.Builder incluent des méthodes pratiques permettant d'analyser les objets Uri existants et d'en créer de nouveaux.

Implémenter la classe ContentProvider

L'instance ContentProvider gère l'accès à un ensemble structuré de données en traitant les requêtes provenant d'autres applications. Toutes les formes d'accès finissent par appeler ContentResolver, qui appelle ensuite une méthode concrète de ContentProvider pour obtenir l'accès.

Méthodes requises

La classe abstraite ContentProvider définit six méthodes abstraites que vous implémentez dans le cadre de votre sous-classe concrète. Toutes ces méthodes, à l'exception de onCreate(), sont appelées par une application cliente qui tente d'accéder à votre fournisseur de contenu.

query()
Récupérez les données de votre fournisseur. Utilisez les arguments pour sélectionner la table à interroger, les lignes et les colonnes à renvoyer, et l'ordre de tri du résultat. Renvoyez les données en tant qu'objet Cursor.
insert()
Insérez une nouvelle ligne dans votre fournisseur. Utilisez les arguments pour sélectionner la table de destination et obtenir les valeurs de colonne à utiliser. Renvoie un URI de contenu pour la ligne qui vient d'être insérée.
update()
Mettez à jour les lignes existantes dans votre fournisseur. Utilisez les arguments pour sélectionner la table et les lignes à mettre à jour, ainsi que pour obtenir les valeurs de colonne mises à jour. Renvoie le nombre de lignes mises à jour.
delete()
Supprimez des lignes de votre fournisseur. Utilisez les arguments pour sélectionner la table et les lignes à supprimer. Renvoie le nombre de lignes supprimées.
getType()
Renvoie le type MIME correspondant à un URI de contenu. Cette méthode est décrite plus en détail dans la section Implémenter les types MIME du fournisseur de contenu.
onCreate()
Initialisez votre fournisseur. Le système Android appelle cette méthode immédiatement après avoir créé votre fournisseur. Votre fournisseur n'est créé que lorsqu'un objet ContentResolver n'essaie pas d'y accéder.

Ces méthodes ont la même signature que les méthodes ContentResolver portant un nom identique.

Votre implémentation de ces méthodes doit tenir compte des éléments suivants:

  • Toutes ces méthodes, à l'exception de onCreate(), peuvent être appelées par plusieurs threads à la fois. Elles doivent donc être sécurisées. Pour en savoir plus sur l'utilisation de plusieurs threads, consultez la présentation des processus et des threads.
  • Évitez les opérations interminables dans onCreate(). Différez les tâches d'initialisation jusqu'à ce qu'elles soient réellement nécessaires. Pour en savoir plus, consultez la section Mettre en œuvre la méthode onCreate().
  • Bien que vous deviez implémenter ces méthodes, votre code n'a rien à faire, à part renvoyer le type de données attendu. Par exemple, vous pouvez empêcher d'autres applications d'insérer des données dans certaines tables en ignorant l'appel de insert() et en renvoyant 0.

Implémenter la méthode query()

La méthode ContentProvider.query() doit renvoyer un objet Cursor ou, en cas d'échec, générer une exception Exception. Si vous utilisez une base de données SQLite comme stockage de données, vous pouvez renvoyer le Cursor renvoyé par l'une des méthodes query() de la classe SQLiteDatabase.

Si la requête ne correspond à aucune ligne, renvoie une instance Cursor dont la méthode getCount() renvoie 0. Ne renvoie null que si une erreur interne s'est produite pendant le processus de requête.

Si vous n'utilisez pas de base de données SQLite comme stockage de données, utilisez l'une des sous-classes concrètes de Cursor. Par exemple, la classe MatrixCursor implémente un curseur dans lequel chaque ligne est un tableau d'instances Object. Avec cette classe, utilisez addRow() pour ajouter une ligne.

Le système Android doit pouvoir communiquer le Exception au-delà des limites des processus. Android peut le faire pour les exceptions suivantes, qui sont utiles pour gérer les erreurs de requête:

Implémenter la méthode insert()

La méthode insert() ajoute une ligne à la table appropriée, en utilisant les valeurs de l'argument ContentValues. Si un nom de colonne ne figure pas dans l'argument ContentValues, vous pouvez fournir une valeur par défaut pour cette colonne dans le code de votre fournisseur ou dans le schéma de votre base de données.

Cette méthode renvoie l'URI de contenu de la nouvelle ligne. Pour ce faire, ajoutez la clé primaire de la nouvelle ligne (généralement la valeur _ID) à l'URI de contenu de la table, à l'aide de withAppendedId().

Implémenter la méthode delete()

La méthode delete() ne doit pas nécessairement supprimer des lignes de votre espace de stockage de données. Si vous utilisez un adaptateur de synchronisation avec votre fournisseur, envisagez de marquer une ligne supprimée avec un indicateur "delete" plutôt que de la supprimer complètement. L'adaptateur de synchronisation peut rechercher les lignes supprimées et les retirer du serveur avant de les supprimer du fournisseur.

Implémenter la méthode update()

La méthode update() utilise le même argument ContentValues que celui utilisé par insert(), et les mêmes arguments selection et selectionArgs que ceux utilisés par delete() et ContentProvider.query(). Cela peut vous permettre de réutiliser du code entre ces méthodes.

Implémenter la méthode onCreate()

Le système Android appelle onCreate() lorsqu'il démarre le fournisseur. N'effectuez que des tâches d'initialisation à exécution rapide avec cette méthode, et différez la création de la base de données et le chargement des données jusqu'à ce que le fournisseur reçoive réellement une requête pour les données. Si vous effectuez des tâches longues dans onCreate(), vous ralentissez le démarrage du fournisseur. Cela ralentit donc la réponse du fournisseur à d'autres applications.

Les deux extraits de code suivants illustrent l'interaction entre ContentProvider.onCreate() et Room.databaseBuilder(). Le premier extrait montre l'implémentation de ContentProvider.onCreate(), où l'objet de base de données est compilé et les traitements associés aux objets d'accès aux données sont créés:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Implémenter les types MIME du ContentProvider

La classe ContentProvider comporte deux méthodes pour renvoyer les types MIME:

getType()
L'une des méthodes obligatoires que vous implémentez pour n'importe quel fournisseur.
getStreamTypes()
Méthode que vous devez implémenter si votre fournisseur propose des fichiers.

Types MIME pour les tables

La méthode getType() renvoie un String au format MIME qui décrit le type de données renvoyé par l'argument d'URI de contenu. L'argument Uri peut être un modèle plutôt qu'un URI spécifique. Dans ce cas, renvoyez le type de données associé aux URI de contenu qui correspondent au modèle.

Pour les types de données courants tels que du texte, HTML ou JPEG, getType() renvoie le type MIME standard pour ces données. La liste complète de ces types standards est disponible sur le site Web IANA MIME Media Types (Types de médias MIME de l'IANA).

Pour les URI de contenu qui pointent vers une ou plusieurs lignes de données de table, getType() renvoie un type MIME au format MIME spécifique au fournisseur d'Android:

  • Partie du type: vnd
  • Partie du sous-type :
    • Si le format d'URI correspond à une seule ligne: android.cursor.item/
    • Si le format d'URI correspond à plusieurs lignes: android.cursor.dir/
  • Partie spécifique au fournisseur: vnd.<name>.<type>

    Vous fournissez <name> et <type>. La valeur <name> est unique au niveau mondial, et la valeur <type> est unique au modèle d'URI correspondant. <name> est un bon choix pour le nom de votre entreprise ou une partie du nom du package Android de votre application. Un bon choix pour <type> est une chaîne qui identifie la table associée à l'URI.

Par exemple, si l'autorité d'un fournisseur est com.example.app.provider et qu'il expose une table nommée table1, le type MIME pour plusieurs lignes dans table1 est le suivant:

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

Pour une seule ligne de table1, le type MIME est le suivant:

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

Types MIME pour les fichiers

Si votre fournisseur propose des fichiers, implémentez getStreamTypes(). La méthode renvoie un tableau String de types MIME pour les fichiers que votre fournisseur peut renvoyer pour un URI de contenu donné. Filtrez les types MIME que vous proposez à l'aide de l'argument de filtre de type MIME, afin de ne renvoyer que les types MIME que le client souhaite gérer.

Prenons l'exemple d'un fournisseur qui propose des photos sous forme de fichiers au format JPG, PNG et GIF. Si une application appelle ContentResolver.getStreamTypes() avec la chaîne de filtre image/*, pour quelque chose qui est une "image", la méthode ContentProvider.getStreamTypes() renvoie le tableau:

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

Si l'application ne s'intéresse qu'aux fichiers JPG, elle peut appeler ContentResolver.getStreamTypes() avec la chaîne de filtre *\/jpeg, et getStreamTypes() renvoie :

{"image/jpeg"}

Si votre fournisseur ne propose aucun des types MIME demandés dans la chaîne de filtre, getStreamTypes() renvoie null.

Implémenter une classe de contrat

Une classe de contrat est une classe public final qui contient des définitions constantes pour les URI, les noms de colonne, les types MIME et d'autres métadonnées relatives au fournisseur. La classe établit un contrat entre le fournisseur et d'autres applications en garantissant l'accès correct au fournisseur, même en cas de modifications des valeurs réelles des URI, des noms de colonnes, etc.

Une classe de contrat aide également les développeurs, car elle porte généralement des noms mnémotechniques pour ses constantes. Les développeurs sont donc moins susceptibles d'utiliser des valeurs incorrectes pour les noms de colonnes ou les URI. S'agissant d'une classe, elle peut contenir de la documentation Javadoc. Les environnements de développement intégrés tels qu'Android Studio peuvent effectuer la saisie semi-automatique des noms de constantes à partir de la classe de contrat et afficher le Javadoc pour les constantes.

Les développeurs ne peuvent pas accéder au fichier de classe de la classe de contrat à partir de votre application, mais ils peuvent le compiler de manière statique dans leur application à partir d'un fichier JAR que vous fournissez.

La classe ContactsContract et ses classes imbriquées sont des exemples de classes contractuelles.

Implémenter des autorisations de fournisseur de contenu

Les autorisations et les accès pour tous les aspects du système Android sont décrits en détail dans la section Conseils de sécurité. La page Présentation du stockage des données et des fichiers décrit également les mécanismes de sécurité et les autorisations en vigueur pour les différents types de stockage. En résumé, les points importants sont les suivants:

  • Par défaut, les fichiers de données stockés dans la mémoire de stockage interne de l'appareil sont réservés à votre application et à votre fournisseur.
  • Les bases de données SQLiteDatabase que vous créez sont réservées à votre application et à votre fournisseur.
  • Par défaut, les fichiers de données que vous enregistrez sur un espace de stockage externe sont publics et lisibles à tous. Vous ne pouvez pas utiliser de fournisseur de contenu pour restreindre l'accès aux fichiers du stockage externe, car d'autres applications peuvent utiliser d'autres appels d'API pour les lire et les écrire.
  • Les appels de méthode pour ouvrir ou créer des fichiers ou des bases de données SQLite dans la mémoire de stockage interne de votre appareil peuvent potentiellement accorder un accès en lecture et en écriture à toutes les autres applications. Si vous utilisez un fichier interne ou une base de données comme dépôt du fournisseur et que vous lui accordez un accès "lisible à tous" ou "en écriture à l'échelle mondiale", les autorisations que vous définissez pour votre fournisseur dans son fichier manifeste ne protègent pas vos données. L'accès par défaut aux fichiers et aux bases de données dans la mémoire de stockage interne est "privé". Ne modifiez pas ce paramètre pour le dépôt de votre fournisseur.

Si vous souhaitez utiliser des autorisations de fournisseur de contenu pour contrôler l'accès à vos données, stockez-les dans des fichiers internes, dans des bases de données SQLite ou dans le cloud, par exemple sur un serveur distant, et conservez les fichiers et les bases de données privés pour votre application.

Implémenter des autorisations

Par défaut, toutes les applications peuvent lire ou écrire sur votre fournisseur, même si les données sous-jacentes sont privées, car par défaut votre fournisseur ne dispose d'aucune autorisation définie. Pour changer cela, définissez les autorisations de votre fournisseur dans votre fichier manifeste, à l'aide d'attributs ou d'éléments enfants de l'élément <provider>. Vous pouvez définir des autorisations qui s'appliquent à l'ensemble du fournisseur, à certaines tables, à certains enregistrements ou aux trois.

Vous définissez les autorisations pour votre fournisseur avec un ou plusieurs éléments <permission> dans votre fichier manifeste. Pour rendre l'autorisation unique pour votre fournisseur, utilisez un champ d'application de style Java pour l'attribut android:name. Par exemple, nommez l'autorisation de lecture com.example.app.provider.permission.READ_PROVIDER.

La liste suivante décrit le champ d'application des autorisations du fournisseur, en commençant par celles qui s'appliquent à l'ensemble du fournisseur, puis en passant à des autorisations plus précises. Les autorisations plus précises prévalent sur celles dont le champ d'application est plus étendu.

Autorisation unique au niveau du fournisseur en lecture/écriture
Une autorisation qui contrôle à la fois l'accès en lecture et en écriture à l'ensemble du fournisseur, spécifiée avec l'attribut android:permission de l'élément <provider>.
Séparer les autorisations de lecture et d'écriture au niveau du fournisseur
Une autorisation de lecture et une autorisation d'écriture pour l'ensemble du fournisseur. Vous pouvez les spécifier avec les attributs android:readPermission et android:writePermission de l'élément <provider>. Elles prévalent sur l'autorisation requise par android:permission.
Autorisation au niveau du chemin d'accès
Autorisation de lecture, d'écriture ou de lecture/écriture pour un URI de contenu de votre fournisseur. Spécifiez chaque URI que vous souhaitez contrôler avec un élément enfant <path-permission> de l'élément <provider>. Pour chaque URI de contenu que vous spécifiez, vous pouvez spécifier une autorisation de lecture/écriture, une autorisation de lecture, une autorisation d'écriture ou les trois. Les autorisations de lecture et d'écriture prévalent sur l'autorisation de lecture/écriture. De plus, l'autorisation au niveau du chemin d'accès est prioritaire sur les autorisations au niveau du fournisseur.
Autorisation temporaire
Niveau d'autorisation qui accorde un accès temporaire à une application, même si celle-ci ne dispose pas des autorisations normalement requises. La fonctionnalité d'accès temporaire réduit le nombre d'autorisations qu'une application doit demander dans son fichier manifeste. Lorsque vous activez des autorisations temporaires, les seules applications qui nécessitent des autorisations permanentes pour votre fournisseur sont celles qui accèdent en permanence à toutes vos données.

Par exemple, tenez compte des autorisations dont vous avez besoin si vous implémentez un fournisseur de messagerie et une application, et que vous souhaitez autoriser une application externe de visionneuse d'images à afficher des photos jointes de votre fournisseur. Pour accorder au lecteur d'images l'accès nécessaire sans nécessiter d'autorisations, vous pouvez configurer des autorisations temporaires pour les URI de contenu des photos.

Concevez votre application de messagerie de sorte que lorsque l'utilisateur souhaite afficher une photo, elle envoie à la visionneuse d'images un intent contenant l'URI de contenu de la photo et des indicateurs d'autorisation. La visionneuse d'images peut ensuite demander à votre fournisseur de messagerie de récupérer la photo, même s'il ne dispose pas de l'autorisation de lecture normale pour votre fournisseur.

Pour activer les autorisations temporaires, définissez l'attribut android:grantUriPermissions de l'élément <provider>, ou ajoutez un ou plusieurs éléments enfants <grant-uri-permission> à votre élément <provider>. Appelez Context.revokeUriPermission() chaque fois que vous supprimez la prise en charge d'un URI de contenu associé à une autorisation temporaire de la part de votre fournisseur.

La valeur de l'attribut détermine dans quelle mesure votre fournisseur est rendu accessible. Si l'attribut est défini sur "true", le système accorde une autorisation temporaire à l'ensemble de votre fournisseur, ignorant ainsi toutes les autres autorisations requises au niveau du fournisseur ou du chemin d'accès.

Si cet indicateur est défini sur "false", ajoutez des éléments enfants <grant-uri-permission> à votre élément <provider>. Chaque élément enfant spécifie le ou les URI de contenu pour lesquels un accès temporaire est accordé.

Pour déléguer un accès temporaire à une application, un intent doit contenir l'option FLAG_GRANT_READ_URI_PERMISSION, l'option FLAG_GRANT_WRITE_URI_PERMISSION ou les deux. Elles sont définies à l'aide de la méthode setFlags().

Si l'attribut android:grantUriPermissions n'est pas présent, il est considéré comme "false".

Élément <provider>

Comme les composants Activity et Service, une sous-classe de ContentProvider est définie dans le fichier manifeste de son application, à l'aide de l'élément <provider>. Le système Android obtient les informations suivantes à partir de l'élément:

Autorité (android:authorities)
Noms symboliques qui identifient l'ensemble du fournisseur dans le système. Cet attribut est décrit plus en détail dans la section URI de contenu de conception.
Nom de classe du fournisseur (android:name)
Classe qui implémente ContentProvider. Cette classe est décrite plus en détail dans la section Implémenter la classe ContentProvider.
Autorisations
Attributs spécifiant les autorisations dont les autres applications doivent disposer pour accéder aux données du fournisseur :

Les autorisations et les attributs correspondants sont décrits plus en détail dans la section Implémenter des autorisations de fournisseur de contenu.

Attributs de démarrage et de contrôle
Ces attributs déterminent comment et quand le système Android lance le fournisseur, les caractéristiques de processus du fournisseur et d'autres paramètres d'exécution :
  • android:enabled: option permettant au système de démarrer le fournisseur
  • android:exported: option permettant à d'autres applications d'utiliser ce fournisseur
  • android:initOrder: ordre dans lequel le fournisseur est démarré par rapport aux autres fournisseurs dans le même processus
  • android:multiProcess: option permettant au système de démarrer le fournisseur dans le même processus que le client appelant
  • android:process: nom du processus dans lequel le fournisseur exécute
  • android:syncable: indicateur indiquant que les données du fournisseur doivent être synchronisées avec les données d'un serveur

Ces attributs sont entièrement documentés dans le guide de l'élément <provider>.

Attributs informatifs
Icône et libellé facultatifs pour le fournisseur :
  • android:icon: ressource drawable contenant une icône pour le fournisseur. L'icône s'affiche à côté du libellé du fournisseur dans la liste des applications sous Paramètres > Applications > Toutes.
  • android:label: libellé d'information décrivant le fournisseur, ses données ou les deux. Le libellé apparaît dans la liste des applications sous Paramètres > Applications > Toutes.

Ces attributs sont entièrement documentés dans le guide de l'élément <provider>.

Remarque:Si vous ciblez Android 11 ou une version ultérieure, consultez la documentation sur la visibilité des packages pour toute configuration supplémentaire.

Intents et accès aux données

Les applications peuvent accéder indirectement à un fournisseur de contenu avec un Intent. L'application n'appelle aucune des méthodes de ContentResolver ou ContentProvider. Au lieu de cela, il envoie un intent qui démarre une activité, qui fait souvent partie de la propre application du fournisseur. L'activité de destination est chargée de récupérer et d'afficher les données dans son UI.

En fonction de l'action dans l'intent, l'activité de destination peut également inviter l'utilisateur à apporter des modifications aux données du fournisseur. Un intent peut également contenir des données "extras" que l'activité de destination affiche dans l'UI. L'utilisateur a ensuite la possibilité de modifier ces données avant de les utiliser pour modifier les données du fournisseur.

Vous pouvez utiliser l'accès aux intents pour favoriser l'intégrité des données. Votre fournisseur peut dépendre de l'insertion, de la mise à jour et de la suppression des données en fonction d'une logique métier strictement définie. Dans ce cas, le fait d'autoriser d'autres applications à modifier directement vos données peut entraîner des données non valides.

Si vous souhaitez que les développeurs utilisent l'accès aux intents, veillez à le documenter minutieusement. Expliquez pourquoi il est préférable d'accéder à l'intent à l'aide de l'interface utilisateur de votre application plutôt que d'essayer de modifier les données avec leur code.

Le traitement d'un intent entrant qui souhaite modifier les données de votre fournisseur n'est pas différent de celui des autres intents. Pour en savoir plus sur l'utilisation des intents, consultez la page Intents et filtres d'intents.

Pour en savoir plus, consultez Présentation du fournisseur d'agenda.