Partage de fichiers à l'aide d'un fournisseur de contenu

Plutôt que de stocker des fichiers volumineux dans votre fournisseur de contenu, vous devez les représenter dans une table sous la forme d'URI pleinement qualifiés pour des fichiers stockés ailleurs dans le système de fichiers. Pour inclure des fichiers dans votre table, incluez une colonne intitulée _data qui contiendra le chemin d'accès au fichier représenté par cet enregistrement. Cette colonne ne doit pas être utilisée directement par les applications clientes. Remplacez plutôt le gestionnaire openFile dans votre fournisseur de contenu pour fournir un ParcelFileDescriptor lorsque le résolveur de contenu demande un fichier associé à cet enregistrement en fournissant son chemin d'URI. Pour simplifier ce processus, Android inclut la méthode openFileHelper, qui interroge le fournisseur de contenu sur le chemin du fichier stocké dans la colonne _data et crée et retourne un descripteur de fichier de parcelle, comme indiqué dans le Listing 10-9.

LISTING 10-9 : Renvoi de fichiers d'un fournisseur de contenu

  1. @Nullable @Override
  2. public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
  3. return openFileHelper(uri, mode);
  4. }

 REMARQUE

Etant donné que les fichiers associés aux lignes de la base de données sont stockés dans le système de fichiers, et non dans la table de la base de données, il est important de prendre en compte l’effet que la suppression d’une ligne devrait avoir sur le fichier sous-jacent.  Le Storage Access Framework, introduit dans Android 4.4 KitKat (API niveau 19), constitue une meilleure approche pour le partage de fichiers entre applications. Storage Access Framework est décrit en détail au Chapitre 8, «Fichiers, enregistrement de l'état et préférences». 


Ajout de conditions d'autorisation aux fournisseurs de contenu

L'objectif principal des fournisseurs de contenu est de partager des données avec d'autres applications. Par défaut, toute application connaissant les URI appropriés peut utiliser le résolveur de contenu pour accéder à votre fournisseur de contenu et interroger ses données ou effectuer des transactions. Si vous n'avez pas l'intention de rendre votre fournisseur de contenu accessible à d'autres applications, définissez l'attribut android: exporté sur false, afin de limiter l'accès à votre application uniquement:

  1. < provider
  2. android:name=".MyHoardContentProvider"
  3. android:authorities="com.professionalandroid.provider.hoarder"
  4. android:exported="false">
  5. < /provider >

  Vous pouvez également limiter l'accès en lecture et / ou en écriture à vos fournisseurs à l'aide d'autorisations. Par exemple, les fournisseurs de contenu Android natifs qui incluent des informations sensibles, telles que les coordonnées et les journaux d'appels, nécessitent des autorisations de lecture et d'écriture, respectivement, pour accéder à leur contenu et le modifier (les fournisseurs de contenu natifs sont décrits plus en détail dans la section «Utilisation des fournisseurs de contenu Android natifs». ”). L'utilisation des autorisations empêche les applications malveillantes de corrompre les données, d'accéder aux informations sensibles ou de faire une utilisation excessive (ou non autorisée) de ressources matérielles ou de canaux de communication externes. L'utilisation la plus courante des autorisations avec les fournisseurs de contenu consiste à restreindre l'accès aux applications signées avec la même signature, c'est-à-dire aux autres applications que vous avez créées et publiées, afin qu'elles puissent fonctionner ensemble. Pour ce faire, vous devez d'abord définir une nouvelle autorisation dans le manifeste d'application des applications consommateur et fournisseur de contenu, indiquant un niveau de protection de la signature:

  1. < permission android:name="com.professionalandroid.provider.hoarder.ACCESS_PERMISSION"android:protectionLevel="signature">
  2. < /permission >
  3. Also add the corresponding uses-permission entry in each Manifest:
  4. < uses-permission
  5. android:name="com.professionalandroid.provider.hoarder.ACCESS_PERMISSION"
  6. />

Outre le niveau de protection de la signature, qui limite l'accès aux applications signées avec la même signature, les autorisations peuvent être définies comme nécessitant des niveaux de protection normaux ou dangereux. Les autorisations normales sont affichées pour l'utilisateur au moment de l'installation, tandis que les autorisations dangereuses requièrent l'acceptation de l'utilisateur à l'exécution. Pour plus de détails sur la création et l'utilisation de vos propres autorisations, voir le chapitre 20, «Développement Android avancé». Une fois que vous avez défini une autorisation, vous pouvez l'appliquer en modifiant l'entrée du manifeste du fournisseur de contenu, en indiquant l'autorisation requise pour la lecture ou l'écriture pour le fournisseur. Vous pouvez spécifier différentes autorisations pour l'accès en lecture ou en écriture, ou demander des autorisations pour l'un ou l'autre:

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

 Il est également possible de fournir aux applications une autorisation temporaire, d’accéder à un enregistrement particulier ou de le modifier à l’aide de Intents. Cela fonctionne en faisant en sorte que l'application requérante envoie une intention à l'application hôte du fournisseur de contenu, qui renvoie ensuite une intention contenant les autorisations appropriées pour un URI spécifique, qui durera jusqu'à la fin de l'activité appelante. Pour prendre en charge les autorisations temporaires, commencez par définir l'attribut android: grantUriPermissions sur true dans l'entrée du manifeste de votre fournisseur:

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

Cela vous permettra d'accorder des autorisations temporaires à tout URI utilisé pour accéder à votre fournisseur.Vous pouvez également utiliser un nœud enfant grant-uri-permission dans votre nœud fournisseur pour définir un modèle de chemin ou un préfixe spécifique. Dans votre application, fournissez une fonctionnalité permettant d'écouter une intention d'une autre application (comme décrit au Chapitre 6, «Intentions et récepteurs de diffusion»). Lorsqu'une telle intention est reçue, affichez une interface utilisateur pour prendre en charge l'action demandée et renvoyez une intention avec un URI à l'enregistrement affecté / sélectionné, en définissant l'indicateur FLAG_GRANT_READ_URI_PERMISSION ou FLAG_GRANT_WRITE_URI_ PERMISSION comme applicable:

  1. protected void returnSelectedRecord(int rowId) {
  2. Uri selectedUri = ContentUris.withAppendedId(MyHoardContentProvider.CONTENT_URI,rowId);
  3. Intent result = new Intent(Intent.ACTION_PICK, selectedUri); result.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
  4. setResult(RESULT_OK, result);
  5. finish();
  6. }

 À l'aide de cette approche, votre application fournit l'intermédiation entre l'utilisateur de l'application tierce et votre fournisseur de contenu, par exemple en fournissant l'interface utilisateur qui permet à l'utilisateur de sélectionner un enregistrement ou de modifier des données. Cela limite les risques de fuite ou d'altération des données en limitant le nombre de données consultées et en garantissant que votre application, et par extension l'utilisateur, est en mesure d'annuler tout accès inapproprié ou toute modification. En conséquence, l'application requérante n'a pas besoin de demander d'autorisations spéciales lors de l'utilisation d'une intention pour interroger ou modifier des données. L'approche consistant à utiliser Intents pour accorder des autorisations temporaires est largement utilisée pour fournir un accès aux fournisseurs de contenu natifs, comme décrit à la section "Utilisation des fournisseurs de contenu natif.


Accès Aux Fournisseurs De Contenu Avec Des Résolutions De Contenu

Chaque application comprend une instance ContentResolver, accessible à l'aide de la méthode getContentResolver, comme suit:

  1. ContentResolver cr = getContentResolver();

Les résolveurs de contenu sont utilisés pour interroger et effectuer des transactions sur les fournisseurs de contenu. Le résolveur de contenu fournit des méthodes pour interroger et effectuer des transactions sur les fournisseurs de contenu, en prenant un URI indiquant le fournisseur de contenu avec lequel interagir. L'URI d'un fournisseur de contenu correspond à son autorité, définie par son entrée de manifeste, et est généralement publiée sous forme de constante statique dans l'implémentation du fournisseur de contenu. Comme décrit dans la section précédente, les fournisseurs de contenu acceptent généralement deux formes d'URI: une pour les requêtes portant sur toutes les données et une autre qui spécifie une seule ligne. Le formulaire de ce dernier ajoute l'identifiant de la ligne (sous la forme / <rowID>) à l'URI de base.

 
Interrogation des fournisseurs de contenu

Les requêtes du fournisseur de contenu prennent une forme très similaire à celle des requêtes de base de données SQLite. Les résultats de la requête sont renvoyés sous forme de curseurs sur un jeu de résultats et les valeurs extraites de la même manière que celle décrite dans le chapitre précédent décrivant les bases de données SQLite.

A l'aide de la méthode de requête sur l'objet ContentResolver, transmettez ce qui suit: Ø  Un URI au contenu du fournisseur de contenu que vous souhaitez interroger.  Ø  Projection Une projection qui répertorie les colonnes à inclure dans le jeu de résultats.  Ø  Une clause where qui définit les lignes à renvoyer. Vous devriez inclure? les caractères génériques qui seront remplacés par les valeurs passées dans le paramètre d'argument de sélection.  Ø  Un tableau de chaînes d'argument de sélection qui remplacera le? caractères génériques dans la clause where.  String Une chaîne décrivant l'ordre des lignes renvoyées.  Le Listing 10-10 montre comment utiliser un résolveur de contenu pour appliquer une requête à un fournisseur de contenu.

 

LISTING 10-10 : Interrogation d'un fournisseur de contenu avec le résolveur de contenu

  1. // Get the Content Resolver.
  2. ContentResolver cr = getContentResolver();
  3. // Specify the result column projection. Return the minimum set // of columns required to satisfy your requirements.
  4. String[] result_columns = new String[] {
  5. HoardDB.HoardContract.KEY_ID,
  6. HoardDB.HoardContract.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN,
  7. HoardDB.HoardContract.KEY_GOLD_HOARDED_COLUMN };
  8. // Specify the where clause that will limit your results.
  9. String where = HoardDB.HoardContract.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + "=?";
  10.  
  11. String[] whereArgs = {"1"};
  12. // Replace with valid SQL ordering statement as necessary.
  13. String order = null;
  14. // Return the specified rows.
  15. Cursor resultCursor = cr.query(MyHoardContentProvider.CONTENT_URI,result_columns, where, whereArgs, order);

Dans cet exemple, la requête est effectuée à l'aide des noms de colonne fournis en tant que constantes statiques de la classe HoardContract et de CONTENT_URI disponible à partir de la classe MyHoardContentProvider; Toutefois, il convient de noter qu’une application tierce peut exécuter la même requête à condition de connaître l’URI du contenu et les noms de colonne et d’avoir les autorisations appropriées. La plupart des fournisseurs de contenu incluent également un modèle d'URI de raccourci qui vous permet d'adresser une ligne particulière en ajoutant un ID de ligne à l'URI de contenu. Vous pouvez utiliser la méthode static withAppendedId de la classe ContentUris pour simplifier cette opération, comme indiqué dans le listing 10-11.

LISTING 10-11 : Interroger un fournisseur de contenu pour une ligne particulière

  1. private Cursor queryRow(int rowId) {
  2. // Get the Content Resolver.
  3. ContentResolver cr = getContentResolver();
  4. // Specify the result column projection. Return the minimum set // of columns required to satisfy your requirements.
  5. String[] result_columns = new String[] {
  6. HoardDB.HoardContract.KEY_ID,
  7. HoardDB.HoardContract.KEY_GOLD_HOARD_NAME_COLUMN,
  8. HoardDB.HoardContract.KEY_GOLD_HOARDED_COLUMN };
  9. // Append a row ID to the URI to address a specific row.
  10. Uri rowAddress = ContentUris.withAppendedId(MyHoardContentProvider.CONTENT_URI,rowId);
  11. // These are null as we are requesting a single row.
  12. String where = null;
  13. String[] whereArgs = null;
  14. String order = null;
  15. // Return the specified row.
  16. return cr.query(rowAddress, result_columns, where, whereArgs, order); }

Pour extraire des valeurs d'un curseur de résultat, utilisez les mêmes techniques que celles décrites dans le chapitre précédent, en combinant les méthodes moveTo <location> avec les méthodes get <type> pour extraire les valeurs de la ligne et de la colonne spécifiées. Le Listing 10-12 étend le code du Listing 10-11, en itérant sur un curseur de résultat et en affichant le nom du plus grand trésor.

LISTING 10-12 : Extraction de valeurs d'un résultat de fournisseur de contenu Cursor

  1. float largestHoard = 0f; String largestHoardName = "No Hoards";
  2. // Find the index to the column(s) being used. int GOLD_HOARDED_COLUMN_INDEX = resultCursor.getColumnIndexOrThrow(
  3. HoardDB.HoardContract.KEY_GOLD_HOARDED_COLUMN);
  4. int HOARD_NAME_COLUMN_INDEX = resultCursor.getColumnIndexOrThrow( HoardDB.HoardContract.KEY_GOLD_HOARD_NAME_COLUMN);
  5. // Iterate over the cursors rows.
  6. // The Cursor is initialized at before first, so we can
  7. // check only if there is a "next" row available.
  8. If the
  9. // result Cursor is empty, this will return false.
  10. while (resultCursor.moveToNext()) {
  11. float hoard = resultCursor.getFloat(GOLD_HOARDED_COLUMN_INDEX);
  12. if (hoard > largestHoard) {
  13. largestHoard = hoard;
  14. largestHoardName = resultCursor.getString(HOARD_NAME_COLUMN_INDEX);
  15. }
  16. }
  17. // Close the Cursor when you've finished with it. resultCursor.close();

Lorsque vous avez fini d’utiliser votre résultat, il est important de le fermer pour éviter les fuites de mémoire et réduire la charge des ressources de votre application:

  1. resultCursor.close();

Vous trouverez plus d’exemples d’interrogation de contenu ultérieurement dans ce chapitre lorsque la version native d'AndroidLes fournisseurs de contenu sont présentés dans la section «Utilisation des fournisseurs de contenu natifs Android».

AVERTISSEMENT L'exécution des requêtes de base de données peut prendre beaucoup de temps. Par défaut, le résolveur de contenu exécute les requêtes, ainsi que toutes les autres transactions, sur le thread principal de l'application.

 

Annulation de requêtes

Android 4.1 Jelly Bean (API, niveau 16) a étendu la méthode de requête du résolveur de contenu pour prendre en charge un paramètre CancellationSignal:

  1. CancellationSignal mCancellationSignal = new CancellationSignal();

La bibliothèque de support Android comprend une classe ContentResolverCompat qui vous permet de prendre en charge l'annulation de requête de manière rétrocompatible:

  1. Cursor resultCursor = ContentResolverCompat.query(cr, MyHoardContentProvider.CONTENT_URI,result_columns, where, whereArgs, order,mCancellationSignal) ;

Par sa méthode d'annulation, un signal d'annulation vous permet d'informer un fournisseur de contenu que vous souhaitez interrompre une requête:

  1. mCancellationSignal.cancel();

Si la requête est annulée en cours d'exécution, une exception OperationCanceledException sera levée.

Interrogation du contenu de manière asynchrone avec un chargeur de curseur

Les opérations de base de données peuvent prendre beaucoup de temps, il est donc particulièrement important qu'aucune requête ou transaction de base de données ou de fournisseur de contenu ne soit effectuée sur le thread d'application principal. Pour simplifier le processus de gestion des curseurs, de synchroniser correctement avec le fil de l'interface utilisateur et de garantir que toutes les requêtes se déroulent sur un fil de fond, Android fournit la classe Loader. Loaders et Loader Manager permettent de simplifier le chargement de données en arrière-plan asynchrone. Les chargeurs créent un fil d’arrière-plan dans lequel vos requêtes et transactions de base de données sont effectuées, avant la synchronisation avec le fil d’interface utilisateur et le retour des données traitées via des gestionnaires de rappel. Loader Manager comprend une mise en cache simple, garantissant que les chargeurs ne sont pas interrompus par l'activité redémarrée en raison de modifications de la configuration du périphérique, et qu'ils soient informés des événements de cycle de vie d'activité et de fragmentation. Cela garantit que les chargeurs sont supprimés lorsque l'activité ou le fragment parent est définitivement détruit. La classe AsyncTaskLoader peut être étendue pour charger tout type de données à partir de n'importe quelle source de données. La classe CursorLoader est particulièrement intéressante. Cursor Loader est spécialement conçu pour prendre en charge les requêtes asynchrones sur les fournisseurs de contenu, renvoyer un curseur de résultat et les notifications de toute mise à jour du fournisseur sous-jacent.

 

REMARQUE

Pour conserver un code concis et encapsulé, tous les exemples de ce chapitre ne montrent pas explicitement l'utilisation d'un Cursor Loader lors de la création d'une requête de fournisseur de contenu (ce qui est mauvais et nous le mettons mal à l'aise). Pour vos applications, il est important de toujours utiliser un chargeur de curseur, ou une autre technique de threading d'arrière-plan, lors de l'exécution de requêtes ou de transactions sur des fournisseurs de contenu ou des bases de données. Le chargeur de curseur gère toutes les tâches de gestion requises pour utiliser un curseur, y compris la gestion du cycle de vie du curseur pour s'assurer que les curseurs sont fermés à la fin de l'activité. Requête du fournisseur de contenu - ce qui est mauvais et nous nous en sentons mal. Pour vos applications, il est important de toujours utiliser un chargeur de curseur, ou une autre technique de threading d'arrière-plan, lors de l'exécution de requêtes ou de transactions sur des fournisseurs de contenu ou des bases de données.

Les chargeurs de curseurs observent également les changements dans la requête sous-jacente, vous n’avez donc pas besoin de mettre en œuvre vos propres observateurs de contenu.