Créer un fournisseur de contenu

Un fournisseur de contenu gère l'accès à un dépôt 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 est l'interface entre votre fournisseur et les autres applications.

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

Cette page présente le processus de base pour créer un fournisseur de contenu et la 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 offrir 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 applications.
    • 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 se fait entièrement dans votre propre application et si vous n'avez besoin d'aucune des fonctionnalités listées ci-dessus. À la place, vous pouvez utiliser l'un des systèmes de stockage décrits dans la section Présentation du stockage de données et de 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.

Procédez ensuite comme suit pour créer votre fournisseur:

  1. Concevez le stockage brut de vos données. Un fournisseur de contenu propose des données de deux manières :
    Données de fichiers
    Données qui sont normalement stockées dans des fichiers, comme des photos, des fichiers 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 pour ce fichier.
    Données "structurées"
    Données qui sont normalement placées dans une base de données, un tableau ou une structure similaire. Stockez les données sous un format compatible avec les tableaux 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 relatives à l'entité, telles que le nom d'une personne ou le prix d'un article. Une base de données SQLite est couramment utilisée pour stocker ce type de données, 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 est 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éfinissez 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 indicateurs. 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 exposer cette classe ultérieurement à 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 implémentation de AbstractThreadedSyncAdapter permettant de synchroniser les données entre le fournisseur et les données dans le cloud.

Concevoir le stockage des données

Un fournisseur de contenu est l'interface qui permet d'accéder aux données enregistrées dans un format structuré. Avant de créer l'interface, décidez de la manière dont vous souhaitez stocker les données. Vous pouvez stocker les données sous la forme de votre choix, puis concevoir l'interface de façon à pouvoir les lire et les écrire 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 de paires clé-valeur non relationnelle tel que LevelDB. Si vous travaillez avec des données non structurées, telles que des contenus audio, image ou vidéo, envisagez de les stocker sous forme de fichiers. Vous pouvez combiner plusieurs 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 de Room, qui permet d'accéder à l'API de base de données SQLite que les fournisseurs d'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 une exigence pour la mise en œuvre interne du fournisseur.

  • Pour stocker des données de fichiers, Android dispose de diverses API orientées fichiers. Pour en savoir plus sur le stockage de fichiers, consultez la section Présentation du stockage de fichiers et de données. Si vous concevez un fournisseur qui propose des données multimédias telles que de la musique ou des vidéos, vous pouvez avoir un fournisseur qui combine les données de table et les fichiers.
  • Dans de rares cas, il peut être utile d'implémenter 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 autre ensemble de données 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 les 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 d'entraîner le plantage du système lorsqu'il tente de réinstaller une application associée à un fournisseur de contenu incompatible.

Considérations liées à la conception des données

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

  • Les données de table doivent toujours comporter une colonne "clé primaire" que le fournisseur conserve 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, l'utilisation de BaseColumns._ID est le meilleur choix, car pour associer les résultats d'une requête du fournisseur à une ListView, l'une des colonnes récupérées doit porter le nom _ID.
  • Si vous souhaitez fournir des images bitmap ou d'autres données très volumineuses de données orientées fichiers, 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 ou de 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 de type MIME. Cela vous permet de stocker différents types de lignes dans la même table. La table de "données" 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 du fournisseur entier (son autorité) et un nom qui pointe vers une table ou un fichier (un chemin). La partie ID facultative 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 des fournisseurs de contenu.

Concevoir une autorité

Un fournisseur dispose généralement d'une seule autorité, qui sert de nom interne à Android. Pour éviter les conflits avec d'autres fournisseurs, utilisez la propriété du domaine Internet (à l'envers) comme base de votre autorité de fournisseur. Étant donné que 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 avez deux tables, table1 et table2, vous pouvez les combiner avec l'autorité de l'exemple précédent pour générer les URI de contenu com.example.<appname>.provider/table1 et com.example.<appname>.provider/table2. Les chemins ne sont pas limités à un seul segment, et il n'est pas nécessaire d'avoir un tableau pour chaque niveau du chemin.

Gérer les ID 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 associent la valeur de l'ID à la colonne _ID de la table et effectuent l'accès demandé sur la ligne correspondante.

Cette convention facilite un modèle de conception commun pour les applications accédant à un fournisseur. L'application effectue une requête sur le fournisseur et affiche le Cursor obtenu 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 choisit ensuite l'une des lignes affichées dans l'interface utilisateur pour consulter ou modifier les données. L'application obtient la ligne correspondante à partir du Cursor qui sauvegarde ListView, obtient la valeur _ID de cette ligne, l'ajoute à l'URI de contenu et envoie la demande d'accès au fournisseur. Le fournisseur peut ensuite effectuer la requête ou la modification par rapport à la ligne exacte sélectionnée par l'utilisateur.

Modèles 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 formats d'URI de contenu sur des valeurs entières. Vous pouvez utiliser des valeurs entières dans une instruction switch qui choisit l'action souhaitée pour l'URI de contenu ou les URI correspondant à un modèle particulier.

Un modèle d'URI de contenu établit une correspondance avec 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.

Pour illustrer la conception et le codage de la gestion des URI de contenu, prenons l'exemple d'un fournisseur disposant de l'autorité com.example.app.provider et reconnaissant 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 ne correspond pas aux URI de contenu des tables table1 ou table3.
content://com.example.app.provider/table3/#
Correspond à un URI de contenu pour des lignes uniques dans table3, tel que content://com.example.app.provider/table3/6 pour la ligne identifiée par 6.

L'extrait de code suivant montre comment fonctionnent les 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 individuelles.

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 entre interroger l'ensemble de la table et 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 utiliser 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 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 auprès 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. Renvoyez 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 et 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éé qu'après qu'un objet ContentResolver tente d'y accéder.

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

La mise en œuvre 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 les threads multiples, consultez la présentation des processus et des threads.
  • Évitez d'effectuer des opérations longues dans onCreate(). Différez les tâches d'initialisation jusqu'à ce qu'elles soient réellement nécessaires. La section Mettre en œuvre la méthode onCreate() explique ce point plus en détail.
  • Bien que vous deviez implémenter ces méthodes, votre code n'a rien à faire, sauf 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, renvoyer une erreur Exception. Si vous utilisez une base de données SQLite pour stocker vos 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, renvoyez une instance Cursor dont la méthode getCount() renvoie 0. Ne renvoyez 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 pour le 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 être en mesure de communiquer le Exception au-delà des limites des processus. Android peut effectuer cette opération 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 en fournir une valeur par défaut 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 pour 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 avec 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, pensez à marquer une ligne supprimée avec un indicateur "delete" (supprimer) au lieu 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 utilisé par insert(), ainsi que les mêmes arguments selection et selectionArgs 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() au démarrage du fournisseur. N'effectuez que des tâches d'initialisation rapides 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 de données. Si vous effectuez des tâches de longue durée dans onCreate(), vous ralentissez le démarrage de votre fournisseur. Cela ralentit la réponse du fournisseur aux autres applications.

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

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 ContentProvider

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

getType()
L'une des méthodes que vous devez implémenter, quel que soit le 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 correspondant au format.

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

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:

  • Pièce du type: vnd
  • Partie du sous-type :
    • Si le format d'URI concerne une seule ligne: android.cursor.item/
    • Si le format d'URI concerne plusieurs lignes: android.cursor.dir/
  • Pièce propre au fournisseur: vnd.<name>.<type>

    Vous fournissez <name> et <type>. La valeur <name> est unique, tandis que la valeur <type> est unique au modèle d'URI correspondant. Un bon choix pour <name> est 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 de 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 des 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 en fonction 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 aux formats JPG, PNG et GIF. Si une application appelle ContentResolver.getStreamTypes() avec la chaîne de filtre image/* pour un élément 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 n'offre 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 des URI, des noms de colonne, des types MIME et d'autres métadonnées liées au fournisseur. La classe établit un contrat entre le fournisseur et les autres applications en garantissant un 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 est également utile aux développeurs, car elle utilise 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 colonne ou les URI. Comme il s'agit d'une classe, elle peut contenir de la documentation Javadoc. Les environnements de développement intégrés tels qu'Android Studio peuvent effectuer une saisie semi-automatique des noms de constante de la classe de contrat et afficher Javadoc pour les constantes.

Les développeurs ne peuvent pas accéder au fichier de classe de la classe de contrat depuis 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 de contrat.

Implémenter des autorisations de fournisseur de contenu

Les autorisations et l'accès pour tous les aspects du système Android sont décrits en détail dans la section Conseils de sécurité. La présentation du stockage des données et des fichiers décrit également la sécurité et les autorisations appliquées aux différents types de stockage. En bref, 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 privées et réservées à votre application et à votre fournisseur.
  • Par défaut, les fichiers de données que vous enregistrez dans l'espace de stockage externe sont publics et lisibles dans le monde entier. Vous ne pouvez pas utiliser de fournisseur de contenu pour restreindre l'accès aux fichiers de la mémoire de stockage externe, car d'autres applications peuvent utiliser d'autres appels d'API pour les lire et les écrire.
  • Les appels de méthode permettant d'ouvrir ou de créer des fichiers ou des bases de données SQLite dans la mémoire de stockage interne de votre appareil peuvent donner un accès en lecture et en écriture à toutes les autres applications. Si vous utilisez un fichier ou une base de données interne comme dépôt de votre fournisseur et que vous lui accordez un accès "accessible en lecture et écriture à tous les utilisateurs", 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 de la mémoire de stockage interne est "privé". Ne modifiez pas cet accès pour le dépôt de votre fournisseur.

Si vous souhaitez utiliser les 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 la confidentialité des fichiers et des bases de données 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 n'a pas d'autorisations définies. Pour modifier ce paramètre, définissez des autorisations pour 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 des autorisations pour votre fournisseur avec un ou plusieurs éléments <permission> dans votre fichier manifeste. Pour rendre l'autorisation unique à 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, pour ensuite les rendre plus précises. Les autorisations plus précises prévalent sur celles dont le champ d'application est plus vaste.

Autorisation unique en lecture/écriture au niveau du fournisseur
Une autorisation qui contrôle 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éparation des autorisations en lecture et en écriture au niveau du fournisseur
Autorisation de lecture et autorisation d'écriture pour l'ensemble du fournisseur. Vous les spécifiez avec les attributs android:readPermission et android:writePermission de l'élément <provider>. Elles sont prioritaires sur l'autorisation requise par android:permission.
Autorisation au niveau du chemin d'accès
Autorisation de lecture, d'écriture ou d'écriture pour un URI de contenu de votre fournisseur. Vous 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 sont prioritaires sur l'autorisation de lecture/écriture. En outre, l'autorisation au niveau du chemin 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 les autorisations temporaires, les seules applications nécessitant des autorisations permanentes pour votre fournisseur sont celles qui accèdent en permanence à toutes vos données.

Par exemple, réfléchissez aux autorisations dont vous avez besoin si vous mettez en œuvre un fournisseur de messagerie et une application, et que vous souhaitez autoriser une application externe de visualisation d'images à afficher les photos jointes de votre fournisseur. Pour accorder à la visionneuse d'images l'accès nécessaire sans demander 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 un intent contenant l'URI de contenu de la photo et les indicateurs d'autorisation à la visionneuse d'images. La visionneuse d'images peut ensuite demander à votre fournisseur de messagerie de récupérer la photo, même si elle ne dispose pas de l'autorisation de lecture normale de 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> à l'é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 votre fournisseur.

La valeur de l'attribut détermine la proportion de votre fournisseur 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 par vos autorisations au niveau du fournisseur ou du chemin d'accès.

Si cet indicateur est défini sur "false", ajoutez les é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. Ces éléments sont définis à 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 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 Concevoir des URI de contenu.
Nom de la 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 qui spécifient 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 Mettre en œuvre 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 démarre le fournisseur, les caractéristiques de processus du fournisseur et d'autres paramètres d'exécution :
  • android:enabled: indicateur permettant au système de démarrer le fournisseur.
  • android:exported: indicateur autorisant d'autres applications à utiliser ce fournisseur
  • android:initOrder: ordre de démarrage de ce fournisseur par rapport aux autres fournisseurs du même processus
  • android:multiProcess: indicateur 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 s'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
Une icône et un libellé facultatifs pour le fournisseur :
  • android:icon: ressource drawable contenant une icône pour le fournisseur. L'icône apparaît à côté du libellé du fournisseur dans la liste des applications sous Settings > Apps > All (Paramètres > Applications > Toutes).
  • android:label: libellé informatif 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>.

Intents et accès aux données

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

Selon l'action de 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'interface utilisateur. 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 renforcer l'intégrité des données. Votre fournisseur peut dépendre de l'insertion, de la mise à jour et de la suppression de données conformément à une logique métier strictement définie. Dans ce cas, autoriser d'autres applications à modifier directement vos données peut générer des données non valides.

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

La gestion d'un intent entrant qui souhaite modifier les données de votre fournisseur est semblable à celle 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 la présentation du fournisseur d'agendas.