Allez directement au contenu principal

Stockage scoping sur Android dans les applications Capacitor

Stockage Android scoping dans les applications Capacitor : ce qui a changé dans Android 10 à 14, les permissions qui fonctionnent toujours, MediaStore, SAF et les répertoires sécurisés.

Crédits de l'article

Martin Donadieu

Auteur

Valeria

Relecteur

Jordan

Éditeur

Stockage Android scoping dans les applications Capacitor

Android scoped storage limits what a Capacitor app can read and write outside its own folders. Since Android 10, and fully since Android 11, an app can freely use its private directories, can add files to shared collections through MediaStore, and needs the user to pick anything else through the Storage Access Framework. This guide explains what changed per Android version, which Capacitor Directory les valeurs restent fonctionnelles, et comment sauvegarder, importer et exporter des fichiers de manière prise en charge sur Capacitor 8.

Quels changements, version par version

Android API Changez cela qui compte pour les applications Capacitor.
10 29 L'accès aux fichiers est désormais scoping. requestLegacyExternalStorage="true" while ciblant API 29
11 30 Scoped storage enforced for apps targeting 30+. requestLegacyExternalStorage ignoré. WRITE_EXTERNAL_STORAGE ne concède plus rien. MANAGE_EXTERNAL_STORAGE introduced. Tree picker cannot select Download root or volume root
12 31-32 Aucune modification majeure de stockage
13 33 READ_EXTERNAL_STORAGE remplacé par READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO. Aucun droit ne donne accès aux documents d'autres applications
14 34 Accès photo partiel avec READ_MEDIA_VISUAL_USER_SELECTED
15 et 16 35-36 Pas de nouveau modèle de stockage, politique de Play sur les autorisations photo et vidéo appliquée

Capacitor 8 cibles API 36, votre application fonctionne donc toujours avec le stockage étendu complet sur les appareils modernes. Il n'y a pas de flag pour obtenir le comportement ancien.

What broke in older Capacitor code

Code conçu pour Capacitor 2 ou 3, ou porté de Cordova, échoue souvent en silence sur les nouveaux appareils :

  • Filesystem.writeFile({ directory: Directory.Documents }) ou Directory.ExternalStorage to “save to the phone”. On Android 11+ these paths point to shared storage, and writes fail or the file cannot be read back after reinstall.
  • Lecture de chemins arbitraires comme /storage/emulated/0/Download/file.pdf à partir d'un chemin saisi dans un paramètre ou reçu d'un serveur.
  • Requesting READ_EXTERNAL_STORAGE Lister les documents de l'utilisateur. Sur Android 13 et ultérieur, cette permission n'a aucun effet pour les fichiers non multimédias.
  • Utilisation Filesystem.requestPermissions() et en supposant granted peut écrire n'importe où.
  • Construire des chemins de fichiers à partir d'un content:// URI retourné par un sélecteur.

Ce qui fonctionne toujours sans aucune autorisation

Les dossiers de votre application ne sont pas affectés par le stockage étendu et n'ont pas besoin d'autorisation.

Capacitor Directory chemin d'accès Android Visible pour l'utilisateur Supprimé à la désinstallation
Data, Library, LibraryNoCloud /data/data/<package>/files Non Oui
Cache /data/data/<package>/cache Non Oui
External /storage/emulated/0/Android/data/<package>/files Only via USB or some file managers Oui
ExternalCache /storage/emulated/0/Android/data/<package>/cache Même Oui
Documents Partagé Documents dossier Oui Non
ExternalStorage racine de stockage partagé Oui Non

Les quatre premières lignes sont sûres. Documents et ExternalStorage sont les lignes legacy : depuis Android 11, votre application ne peut voir que les fichiers qu'elle a créés elle-même, et même après un réinstall, elle perd même l'accès à ceux-là. Traitez-les comme non pris en charge pour les nouveaux code.

External est utile pour les gros fichiers (vidéo hors ligne, cartes) car il peut vivre sur une partition plus grande, et les fichiers qui s'y trouvent ne font pas partie de l'Auto Backup. Depuis Android 11, les autres applications ne peuvent pas lire Android/datadonc il est toujours privé en pratique.

Saving files the user can find: MediaStore

When the user taps “Download” on an invoice, they expect it in the Downloads folder. On Android 10+, apps can insert files into the shared Downloads, Pictures, Movies, Music et Documents Accès aux collections via MediaStore sans aucune autorisation.

@capgo/capacitor-file-sharer MediaStore est un save():

bun add @capgo/capacitor-file-sharer
bunx cap sync
import { FileSharer } from '@capgo/capacitor-file-sharer';
import { Filesystem, Directory } from '@capacitor/filesystem';

const { uri } = await Filesystem.getUri({ path: 'exports/invoice-1042.pdf', directory: Directory.Data });

const result = await FileSharer.save({
  filename: 'invoice-1042.pdf',
  path: uri,
  contentType: 'application/pdf',
  android: {
    saveDirectory: 'downloads',     // 'downloads' | 'pictures' | 'movies' | 'music' | 'documents'
    relativePath: 'MyApp/Invoices', // subfolder, Android 10+
  },
});

console.log(result.uri); // content:// URI of the saved file

On iOS, la même appelle ouvre la feuille de partage, où l'utilisateur choisit « Enregistrer dans les fichiers », donc un code chemin couvre les deux plateformes.

On Android 9 et inférieur, l'écriture dans le stockage partagé nécessite encore WRITE_EXTERNAL_STORAGE. Déclarez-l’uniquement pour ces versions :

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

Capacitor 8 prend en charge Android 7 (API 24) et supérieur, donc si vous continuez à livrer à des appareils Android 7 à 9, conservez cette ligne.

Les photos et les vidéos

Préférez les flux de plateforme pour les médias plutôt que d'écrire les fichiers vous-même. @capacitor/camera écrit dans la galerie avec saveToGallery: trueet nécessite uniquement les permissions de stockage legacy sur les anciennes versions d'Android. Consultez notre guide pour la prise et l'édition de photos dans Capacitor.

Reading user files: pickers instead of permissions

Le stockage scoping attend que l'utilisateur choisisse les fichiers. Le sélecteur Android retourne une content:// URI que votre application peut lire même si elle n'a pas de permission de stockage.

import { CapgoFilePicker } from '@capgo/capacitor-file-picker';
import { Filesystem, Directory } from '@capacitor/filesystem';

const { files } = await CapgoFilePicker.pickFiles({ types: ['application/pdf'], limit: 1 });
const picked = files[0];

// Copy into app storage right away: picker grants can expire
await Filesystem.copy({
  from: picked.path!,
  to: `imports/${picked.name}`,
  toDirectory: Directory.Data,
});

Pour les photos et les vidéos, @capgo/capacitor-file-picker's pickMedia et @capacitor/camera's chooseFromGallery utilisez le sélectionneur de photos Android sur Android 13+, qui nécessite aucune autorisation du tout.

Évitez les permissions READ_MEDIA_*

La politique de permissions de photos et de vidéos de Google Play ne permet que READ_MEDIA_IMAGES et READ_MEDIA_VIDEO aux applications dont le but principal nécessite un accès large à la galerie, telles que les applications de galerie ou les éditeurs de photos. Tous les autres doivent utiliser le sélectionneur et déclarer cela dans le console de Play. Si un plugin que vous utilisez déclare ces permissions, supprimez-les du manifeste fusionné :

<uses-permission
    android:name="android.permission.READ_MEDIA_IMAGES"
    tools:node="remove" />
<uses-permission
    android:name="android.permission.READ_MEDIA_VIDEO"
    tools:node="remove" />

Ajoutez xmlns:tools="http://schemas.android.com/tools" à la <manifest> taguez en premier.

L'accès au dossier avec le Framework d'accès au stockage

Some apps need ongoing access to a folder: a notes app syncing to a user folder, an export folder for a field app. The Storage Access Framework (SAF) lets the user grant a folder with ACTION_OPEN_DOCUMENT_TREE:

const { path } = await CapgoFilePicker.pickDirectory();
// On Android: a content://com.android.externalstorage.documents/tree/... URI

Trois choses à savoir :

  1. The result is a tree URI, not a path. Vous ne pouvez pas le passer à Filesystem.writeFile. Les fichiers à l'intérieur sont créés et lus à travers DocumentsContract ou DocumentFile dans le code.
  2. Vous ne pouvez pas passer un chemin à Vous devez appeler takePersistableUriPermission Ajoutez une petite méthode native si vous en avez besoin.
@PluginMethod
public void persist(PluginCall call) {
    Uri uri = Uri.parse(call.getString("uri"));
    getContext().getContentResolver().takePersistableUriPermission(
        uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    call.resolve();
}
  1. Certains dossiers ne peuvent pas être autorisés. On Android 11+, le sélecteur refuse la racine du stockage interne, la racine du dossier, et Download et Android/data and Android/obb. Ask users to create or pick a subfolder.

Persisted grants are limited in number per app, and the user can revoke them in system settings or by clearing app data. Check access with getPersistedUriPermissions() Tout accès aux fichiers : GÉRER_LE_STOCKAGE_EXTERNE

Tous les fichiers d'accès : GÉRER_LE_STOCKAGE_EXTERNE

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

This permission gives the old “read and write everything” behavior, but it is not a runtime prompt. The user must enable it on a dedicated settings screen, and Google Play reviews it manually. Accepted use cases are file managers, backup and restore, antivirus, document management, on-device file search, and a few others where broad access is the core feature. A note-taking app, a PDF viewer that opens files from Downloads, or an app that “just needs to save exports” will be rejected.

Si vous distribuez en dehors de Play (entreprise MDM, appareils kiosque en sideload), c'est une option. Sinon, concevez autour de MediaStore et de SAF.

Comment d'autres plugins s'intègrent

Plugin Stockage scoping
@capacitor/filesystem Satisfait avec les répertoires de l'application. Documents et ExternalStorage sont obsolètes sur Android 11+. requestPermissions() seulement compte pour les répertoires sur les anciens Android
@capacitor/camera Utilise la caméra système et le sélectionneur de photos, aucune autorisation de stockage sauf saveToGallery Seul les chemins listés dans
@capacitor/share peuvent être partagés FileProviderUtilise son propre res/xml/file_paths.xml et MediaStore pour
@capgo/capacitor-file-sharer sans configuration nécessaire FileProvider Fine with app directories. save()Aucune configuration nécessaire.
@capgo/capacitor-downloader Downloads into app external storage, then use save() pour exporter
@capgo/capacitor-file-picker basé sur le sélecteur, sans autorisation sur Android 13+
@capgo/capacitor-photo-library Son manifeste ajoute READ_MEDIA_* pour une lecture complète de la bibliothèque, soumise à la politique de Play ci-dessus. pickMedia() utilise le sélecteur système et n'a pas besoin d'autorisation, donc si vous n'utilisez que cela, supprimez READ_MEDIA_* avec tools:node="remove"

Lequel répertoire utiliser

  • Données de l'application, paramètres, bases de données : Directory.Data
  • Fichiers temporaires et partages d'exportation : Directory.Cache
  • Médias redéchargeables importants : Directory.External sur Android, Directory.LibraryNoCloud On iOS
  • Un fichier que l'utilisateur souhaite dans Téléchargements ou Images : MediaStore via FileSharer.save()
  • Un fichier que l'utilisateur apporte dans l'application : sélection, puis copiez-le dans Directory.Data
  • Un dossier que l'utilisateur souhaite que l'application garde pour écrire : SAF tree URI avec une autorisation persistante

Pour la lecture et l'écriture complètes API, voir le guide de gestion des fichiers Capacitor.

Migrer une application existante

  1. Recherchez votre code pour Directory.Documents, Directory.ExternalStorage, /storage/emulated, et requestLegacyExternalStorage.
  2. Déplacez les fichiers appartenant à l'application vers Directory.Data ou Directory.ExternalCopiez les anciens fichiers que vous pouvez encore lire lors de la première utilisation de la nouvelle version, puis supprimez-les.
  3. Replace “save to device” features with FileSharer.save().
  4. Remplacez les entrées de chemin par un sélecteur.
  5. ajoutez android:maxSdkVersion passer à la gestion de stockage legacy et supprimer READ_MEDIA_* sauf si vous en avez besoin.
  6. Testez sur des appareils Android 10, 11, 13 et 15 ou 16 ou des émulateurs. Le comportement diffère exactement à ces frontières.

preserveLegacyExternalStorage="true" dans le <application> tag keeps legacy access for apps updated from a version that used requestLegacyExternalStoragele tag conserve l'accès legacy pour les applications mises à jour à partir d'une version qui utilisait

Troubleshooting

EACCES (Permission denied) écriture à /storage/emulated/0/...Vous écrivez directement dans le stockage partagé. Utilisez MediaStore ou un répertoire de l'application.

Le fichier a été enregistré avec Directory.Documents est supprimé après réinstallation: the app lost ownership of it. Use MediaStore for user files.

FileNotFoundException lecture d'un content:// URI plus tardLa délégation temporaire a expiré. Copiez le fichier lorsque celui-ci est sélectionné, ou persistez la délégation pour les trois URIs.

avertissement du Console de Play concernant les permissions photo et vidéo: un plugin a ajouté READ_MEDIA_IMAGES ou READ_MEDIA_VIDEOSupprimez-les avec tools:node="remove" écrans toujours refusés

requestPermissions() returns granted but writes still fail: sur Android 11+ la permission de stockage legacy est accordée mais sans signification pour le stockage partagé.

Keep native and web parts in sync

Les règles de stockage sont des configurations natives, donc les modifications du manifest ou des plugins nécessitent une mise à jour de l'application. Capgo Construction can produce in the cloud. The file logic around them (paths, folder names, export flows) is JavaScript you can update with Capgo live updates.

Live updates for Capacitor apps

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

Dernières actualités de notre Blog

Capgo vous offre les meilleures informations dont vous avez besoin pour créer une application mobile vraiment professionnelle.