Présentation Des Fournisseurs De Contenu

Vous apprendrez à créer et à utiliser des fournisseurs de contenu comme moyen cohérent de partage et de consommation de données structurées au sein et entre des applications en fournissant une abstraction à partir de la technique de stockage de données sous-jacente.

Vous voyez comment interroger les fournisseurs de contenu de manière asynchrone à l'aide de Cursor Loaders, afin de garantir la réactivité de votre application lors de la récupération de données.

Sous Android, l'accès à une base de données est limité à l'application qui l'a créée. Les fournisseurs de contenu offrent donc une interface standard que vos applications peuvent utiliser pour partager des données avec d'autres applications et les utiliser, y compris de nombreuses bases de données natives.

Les fournisseurs de contenu sont largement utilisés par la structure Android, ce qui vous permet d'améliorer vos propres applications avec des fournisseurs de contenu natifs, notamment des contacts, un calendrier et le Media Store. Vous apprendrez à stocker et à récupérer les données de ces fournisseurs de contenu Android principaux pour offrir à vos utilisateurs une expérience utilisateur plus riche, plus cohérente et totalement intégrée.

Enfin, vous apprendrez à utiliser les fournisseurs de contenu pour offrir de riches options de recherche, notamment à fournir des suggestions de recherche en temps réel à l'aide de la vue Recherche.

 
Pourquoi Utiliser Les Fournisseurs De Contenu?

La principale raison d'utiliser un fournisseur de contenu est de faciliter le partage de données entre les applications. Les fournisseurs de contenu vous permettent de définir des restrictions d'accès granulaires aux données afin que d'autres applications puissent accéder et modifier en toute sécurité les données de votre application. Toute application disposant des autorisations appropriées peut potentiellement interroger, ajouter, supprimer ou mettre à jour des données fournies par une autre application via un fournisseur de contenu, y compris les fournisseurs de contenu Android natifs. En publiant vos propres fournisseurs de contenu, vous donnez la possibilité à vous (et éventuellement à d'autres développeurs) d'intégrer et d'étendre vos données dans d'autres applications. Les fournisseurs de contenu constituent également le mécanisme utilisé pour fournir des résultats de recherche pour la vue de recherche et pour générer des suggestions de recherche en temps réel. Les fournisseurs de contenu sont également utiles pour découpler les couches de votre application des couches de données sous-jacentes, en encapsulant et en résumant votre base de données sous-jacente. Cette approche est utile pour rendre vos applications indépendantes de la source de données, de sorte que votre mécanisme de stockage de données puisse être modifié ou remplacé sans affecter votre couche d'application. Si vous ne prévoyez pas de partager les données de votre application, vous n’avez pas besoin d’utiliser des fournisseurs de contenu; Cependant, de nombreux développeurs choisissent toujours de créer des fournisseurs de contenu au-dessus de leurs bases de données afin de pouvoir tirer parti d'une couche d'abstraction cohérente. Les fournisseurs de contenu vous permettent également de tirer parti de plusieurs classes pratiques pour l’accès aux données, en particulier le chargeur de curseur, comme décrit plus loin dans ce chapitre. Plusieurs fournisseurs de contenu natifs sont désormais accessibles à des applications tierces, notamment le gestionnaire de contacts, Media Store et l'agenda, comme décrit plus loin dans ce chapitre.


Création De Fournisseurs De Contenu

Les fournisseurs de contenu sont une couche d'abstraction sur une source de données sous-jacente, qui fournit une interface pour la publication de données qui seront utilisées à l'aide d'un résolveur de contenu, potentiellement au-delà des limites du processus. Les fournisseurs de contenu utilisent une interface pour la publication et la consommation de données basée sur un modèle d'adressage URI simple utilisant le schéma content: //. Les fournisseurs de contenu vous permettent de découpler les composants d'applications qui consomment des données à partir de leurs sources de données sous-jacentes, offrant ainsi un mécanisme générique permettant aux applications de partager leurs données ou de consommer des données fournies par d'autres. Ils vous permettent de partager l'accès aux données entre les processus d'applications et prennent en charge l'utilisation de restrictions d'accès granulaires afin que d'autres applications puissent accéder à vos données et les modifier en toute sécurité. Pour créer un nouveau fournisseur de contenu, étendez la classe abstraite ContentProvider comme indiqué dans le Listing 10-1. 

LISTING 10-1 : Creating a new Content Provider

 

  1. public class MyHoardContentProvider extends ContentProvider {
  2. @Override
  3. public boolean onCreate() {
  4. return false; }
  5. @Nullable @Override
  6. public Cursor query(@NonNull Uri uri,
  7. @Nullable String[] projection,
  8. @Nullable String selection,
  9. @Nullable String[] selectionArgs,
  10. @Nullable String sortOrder) {
  11. // TODO: Perform a query and return Cursor.
  12. return null; }
  13. @Nullable @Override
  14. public String getType(@NonNull Uri uri) {
  15. // TODO: Return the mime-type of a query.
  16. return null; }
  17. @Nullable
  18. @Override
  19. public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
  20. // TODO: Insert the Content Values and return a URI to the record.
  21. return null; }
  22. @Override
  23. public int delete(@NonNull Uri uri,
  24. @Nullable String selection,
  25. @Nullable String[] selectionArgs) {
  26. // TODO: Delete the matching records and return the number
  27. // of records deleted.
  28. return 0; }
  29. @Override
  30. public int update(@NonNull Uri uri,
  31. @Nullable ContentValues values,
  32. @Nullable String selection,
  33. @Nullable String[] selectionArgs) {
  34. // TODO: Update the matching records with the provided
  35. // Content Values, returning the number of records updated.
  36. return 0;
  37. }
  38. }

 

Dans les sections suivantes, vous apprendrez à implémenter le gestionnaire onCreate pour initialiser la source de données sous-jacente et à mettre à jour les méthodes query, getType, insertion, mise à jour et suppression afin d'implémenter l'interface utilisée par le résolveur de contenu pour interagir avec les données.

Création de la base de données du fournisseur de contenu

Pour initialiser la source de données à laquelle vous prévoyez d'accéder via le fournisseur de contenu, substituez la méthode onCreate, comme indiqué dans le Listing 10-2. Si vous utilisez une base de données SQLite, vous pouvez gérer cela à l'aide d'une implémentation SQLite Open Helper, comme décrit dans le chapitre précédent.

LISTING 10-2: Creating the Content Provider’s database private HoardDB.HoardDBOpenHelper mHoardDBOpenHelper;

 

  1. @Override
  2. public boolean onCreate() {
  3. // Construct the underlying database.
  4. // Defer opening the database until you need to perform
  5. // a query or transaction.
  6. mHoardDBOpenHelper = new HoardDB.HoardDBOpenHelper(getContext(),
  7. HoardDB.HoardDBOpenHelper.DATABASE_NAME,null,
  8. HoardDB.HoardDBOpenHelper.DATABASE_VERSION);
  9. return true;
  10. }

Nous continuerons d'utiliser une base de données SQLite pour tous nos exemples de bases de données sous-jacentes de ce chapitre, mais souvenez-vous que l'implémentation de la base de données que vous choisissez est arbitraire: vous pouvez utiliser une base de données basée sur un nuage, une base de données entièrement en mémoire ou une alternative. Bibliothèque de base de données SQL ou non-SQL. Dans la suite de ce chapitre, nous allons créer un fournisseur de contenu sur une base de données de salle afin de fournir des résultats de recherche pour l'exemple de tremblement de terre en cours.
Enregistrement des fournisseurs de contenu

Comme les activités et les services, les fournisseurs de contenu sont des composants d’application qui doivent être enregistrés dans votre manifeste d’application avant que le résolveur de contenu puisse les découvrir et les utiliser. Pour ce faire, vous utilisez une balise de fournisseur qui inclut un attribut de nom, décrivant le nom de la classe du fournisseur, et une balise autorités. Utilisez la balise autorités pour définir l'URI de base du fournisseur. L’autorité du fournisseur de contenu est utilisée par le résolveur de contenu comme adresse pour rechercher la base de données avec laquelle interagir. Chaque autorité de fournisseur de contenu doit être unique. Il est donc recommandé de baser le chemin d’URI sur le nom de votre package. La forme générale permettant de définir l’autorité d’un fournisseur de contenu est la suivante:

  1. com.<CompanyName>.provider.<ApplicationName>

La balise de fournisseur complétée doit suivre le format indiqué dans l'extrait 10-3.

LISTING 10-3: Registering a new Content Provider in the application manifest

  1. < provider android:name=".MyHoardContentProvider"android:authorities="com.professionalandroid.provider.hoarder"/>


Publication de l'adresse URI de votre fournisseur de contenu

Par convention, chaque fournisseur de contenu doit exposer son autorité à l'aide d'une propriété CONTENT_URI statique publique qui inclut un chemin de données vers le contenu principal, comme indiqué dans le Listing 10-4.

LISTING 10-4: Publishing your Content Provider authority

  1. public static final Uri CONTENT_URI = Uri.parse("content://com.professionalandroid.provider.hoarder/lairs");

Ces URI de contenu seront utilisés lors de l'accès à votre fournisseur de contenu à l'aide d'un résolveur de contenu, comme indiqué dans les sections suivantes. Une requête effectuée à l'aide de ce formulaire représente une requête pour toutes les lignes, alors qu'un suffixe / <final>, comme le montre l'extrait suivant, représente une requête pour un seul enregistrement spécifique: content: //com.professionalandroid.provider.hoarder/lairs/5Il est recommandé de prendre en charge l’accès à votre fournisseur pour ces deux formulaires. La méthode la plus simple consiste à ajouter un UriMatcher à l’implémentation de votre fournisseur de contenu pour analyser les URI, déterminer leurs formes et extraire les détails fournis. Le Listing 10-5 montre le modèle d'implémentation permettant de définir un Uri Matcher qui analyse la forme d'un URI - déterminant de manière spécifique si un URI est une requête pour toutes les données ou pour une seule ligne. 

LISTING 10-5 : Defining a Uri Matcher

  1. // Create the constants used to differentiate between the different URI
  2. // requests.
  3. private static final int ALLROWS = 1;
  4. private static final int SINGLE_ROW = 2;
  5. private static final UriMatcher uriMatcher;
  6. // Populate the UriMatcher object, where a URI ending in
  7. // 'elements' will correspond to a request for all items,
  8. // and 'elements/[rowID]' represents a single row. static {
  9. uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  10. uriMatcher.addURI("com.professionalandroid.provider.hoarder","lairs", ALLROWS);
  11. uriMatcher.addURI("com.professionalandroid.provider.hoarder","lairs/#", SINGLE_ROW);
  12. }

Vous pouvez utiliser la même technique pour exposer d'autres URI dans le même fournisseur de contenu qui représentent différents sous-ensembles de données ou différentes tables dans votre base de données.


Implémentation de requêtes de fournisseur de contenu

Pour prendre en charge les requêtes avec votre fournisseur de contenu, vous devez redéfinir les méthodes query et getType. Le résolveur de contenu utilise ces méthodes pour accéder aux données sous-jacentes, sans connaître sa structure ni son implémentation. Ces méthodes permettent aux applications de partager des données entre plusieurs applications sans avoir à publier une interface spécifique pour chaque source de données. Dans ce chapitre, nous montrons comment utiliser un fournisseur de contenu pour fournir un accès à une base de données SQLite, mais vous pouvez accéder à toutes les sources de données (y compris Room, fichiers, variables d’instance d’application ou bases de données en nuage). Après avoir utilisé Uri Matcher pour faire la distinction entre les requêtes de table complètes et les requêtes sur une seule ligne, vous pouvez affiner les requêtes et utiliser la classe SQLiteQueryBuilder pour appliquer facilement des conditions de sélection supplémentaires à une requête. Android 4.1 Jelly Bean (API niveau 16) a étendu la méthode de requête pour prendre en charge un paramètre CancellationSignal:

 

  1.  
  2. CancellationSignal mCancellationSignal = new CancellationSignal();

En utilisant un signal d’annulation, vous pouvez informer le fournisseur de contenu que vous souhaitez annuler la requête en cours en appelant la méthode d’annulation du signal d’annulation:

  1. mCancellationSignal.cancel();

Pour des raisons de compatibilité ascendante, Android requiert que vous implémentiez également la méthode de requête qui n'inclut pas le paramètre Signal d'annulation, comme indiqué dans le Listing 10-6. Le Listing 10-6 montre le code squelette pour l'implémentation de requêtes dans un fournisseur de contenu à l'aide d'une base de données SQLite sous-jacente, à l'aide d'un générateur de requêtes SQLite pour transmettre chacun des paramètres de requête, y compris le signal d'annulation, dans une requête adressée à la base de données SQLite sous-jacente.

 

LISTING 10-6: Implementing queries within a Content Provider

 

  1. @Nullable @Override
  2. public Cursor query(@NonNull Uri uri,
  3. @Nullable String[] projection,
  4. @Nullable String selection,
  5. @Nullable String[] selectionArgs,
  6. @Nullable String sortOrder) {
  7. return query(uri, projection, selection, selectionArgs, sortOrder, null); }
  8. @Nullable @Override
  9. public Cursor query(@NonNull Uri uri,
  10. @Nullable String[] projection,
  11. @Nullable String selection,
  12. @Nullable String[] selectionArgs,
  13. @Nullable String sortOrder,
  14. @Nullable CancellationSignal cancellationSignal) {
  15. // Open the database.
  16. SQLiteDatabase db;
  17. try {
  18. db = mHoardDBOpenHelper.getWritableDatabase();
  19. } catch (SQLiteException ex) {
  20. db = mHoardDBOpenHelper.getReadableDatabase(); }
  21. // Replace these with valid SQL statements if necessary.
  22. String groupBy = null;
  23. String having = null;
  24. // Use an SQLite Query Builder to simplify constructing the // database query.
  25. SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
  26. // If this is a row query, limit the result set to the passed in row.
  27. switch (uriMatcher.match(uri)) {
  28. case SINGLE_ROW :
  29. String rowID = uri.getLastPathSegment();
  30. queryBuilder.appendWhere(HoardDB.HoardContract.KEY_ID + "=" + rowID);
  31. default: break; }
  32. // Specify the table on which to perform the query. This can
  33. // be a specific table or a join as required. queryBuilder.setTables(HoardDB.HoardDBOpenHelper.DATABASE_TABLE);
  34. // Specify a limit to the number of returned results, if any.
  35. String limit = null;
  36. // Execute the query.
  37. Cursor cursor = queryBuilder.query(db, projection,selection,selectionArgs,groupBy, having, ortOrder,limit,cancellationSignal);
  38. // Return the result Cursor.
  39. return cursor; }
  40. If a running query is canceled, SQLite will throw an OperationCanceledException.
  41. If your Content Provider doesn’t use an SQLite database, you will need to listen for cancellation signals using an onCancelListener handler, and handle them yourself:
  42. cancellationSignal.setOnCancelListener(
  43. new CancellationSignal.OnCancelListener() {
  44. @Override
  45. public void onCancel() {
  46. // TODO React when my query is cancelled.
  47. }
  48. }
  49. ) ;

 

Une fois les requêtes implémentées, vous devez également spécifier un type MIME pour indiquer le type de données renvoyées. Remplacez la méthode getType pour renvoyer une chaîne décrivant de manière unique votre type de données. Le type renvoyé doit inclure deux formulaires, un pour une seule entrée et un autre pour toutes les entrées, à la suite de ces formulaires:

  • Single item: vnd.android.cursor.item/vnd.<companyname>.<contenttype>
  • All items: vnd.android.cursor.dir/vnd.<companyname>.<contenttype>

 

Le Listing 10-7 montre comment redéfinir la méthode getType pour renvoyer le type MIME correct en fonction de l'URI transmis.

LISTING 10-7: Returning a Content Provider MIME type

 

  1. @Nullable
  2. @Override
  3. public String getType(@NonNull Uri uri) {
  4. // Return a string that identifies the MIME type
  5. // for a Content Provider URI switch (uriMatcher.match(uri)) {
  6. case ALLROWS:
  7. return "vnd.android.cursor.dir/vnd.professionalandroid.lairs";
  8. case SINGLE_ROW:
  9. return "vnd.android.cursor.item/vnd.professionalandroid.lairs";
  10. default:
  11. throw new IllegalArgumentException("Unsupported URI: " + uri);
  12. }
  13. }

Transactions du fournisseur de contenu

Pour prendre en charge les transactions de suppression, d'insertion et de mise à jour sur votre fournisseur de contenu, remplacez les méthodes de suppression, d'insertion et de mise à jour correspondantes. Comme les requêtes, ces méthodes sont utilisées par les résolveurs de contenu pour effectuer des transactions sur les données sous-jacentes sans connaître leur implémentation. Lorsque vous effectuez des transactions qui modifient l’ensemble de données, il est recommandé d’appeler le service de contenu.Méthode notifyChange du résolveur. Cela avertira tous les observateurs de contenu, enregistrés pour un curseur donné à l'aide de la méthode Cursor.registerContentObserver, que la table sous-jacente (ou une ligne particulière) a été supprimée, ajoutée ou mise à jour.

 

Le Listing 10-8 montre le code squelette pour l'implémentation de transactions dans un fournisseur de contenu sur une base de données SQLite sous-jacente.

 

LISTING 10-8: Content Provider insert, update, and delete implementations

 

  1. @Override
  2. public int delete(@NonNull Uri uri,
  3. @Nullable String selection,
  4. @Nullable String[] selectionArgs) {
  5. // Open a read / write database to support the transaction.
  6. SQLiteDatabase db = mHoardDBOpenHelper.getWritableDatabase();
  7. // If this is a row URI, limit the deletion to the specified row.
  8. switch (uriMatcher.match(uri)) {
  9. case SINGLE_ROW : String rowID = uri.getLastPathSegment();
  10. selection = KEY_ID + "=" + rowID + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
  11. default: break; }
  12. // To return the number of deleted items you must specify a where
  13. // clause. To delete all rows and return a value pass in "1".
  14. if (selection == null)
  15. selection = "1";
  16. // Perform the deletion. int deleteCount = db.delete(HoardDB.HoardDBOpenHelper.DATABASE_TABLE,selection, selectionArgs);
  17. // Notify any observers of the change in the data set. getContext().getContentResolver().notifyChange(uri, null);
  18. // Return the number of deleted items.
  19. return deleteCount; }
  20. @Nullable @Override public Uri insert(@NonNull Uri uri,@Nullable ContentValues values) {
  21. // Open a read / write database to support the transaction.
  22. SQLiteDatabase db = mHoardDBOpenHelper.getWritableDatabase();
  23. // To add empty rows to your database by passing in an empty
  24. // Content Values object you must use the null column hack
  25. // parameter to specify the name of a column that can be
  26. // explicitly set to null.
  27. String nullColumnHack = null;
  28. // Insert the values into the table
  29. long id = db.insert(HoardDB.HoardDBOpenHelper.DATABASE_TABLE,nullColumnHack,values);
  30. // Construct and return the URI of the newly inserted row.
  31. if (id > -1) {
  32. // Construct and return the URI of the newly inserted row.
  33. Uri insertedId = ContentUris.withAppendedId(CONTENT_URI,id);
  34. // Notify any observers of the change in the data set.
  35. getContext().getContentResolver().notifyChange(insertedId,null);
  36. return insertedId;
  37. } else
  38. return null; }
  39. @Override
  40. public int update(@NonNull Uri uri,
  41. @Nullable ContentValues values,
  42. @Nullable String selection,
  43. @Nullable String[] selectionArgs) {
  44. // Open a read / write database to support the transaction.
  45. SQLiteDatabase db = mHoardDBOpenHelper.getWritableDatabase();
  46. // If this is a row URI, limit the deletion to the specified row.
  47. switch (uriMatcher.match(uri)) {
  48. case SINGLE_ROW : String rowID = uri.getLastPathSegment();
  49. selection = KEY_ID + "=" + rowID + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
  50. default: break;
  51. }
  52. // Perform the update.
  53. int updateCount = db.update(HoardDB.HoardDBOpenHelper.DATABASE_TABLE,values, selection, selectionArgs);
  54. // Notify any observers of the change in the data set. getContext().getContentResolver().notifyChange(uri, null);
  55. return updateCount;
  56. }

REMARQUE Lorsque vous utilisez des URI de contenu, la classe ContentUris inclut la méthode pratique withAppendedId pour ajouter facilement un ID de ligne spécifique à CONTENT_URI d'un fournisseur de contenu. Ceci est utilisé dans le Listing 10-8 pour construire l'URI des nouvelles lignes insérées.