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 })ouDirectory.ExternalStorageto “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_STORAGELister 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 supposantgrantedpeut é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 :
- 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 à traversDocumentsContractouDocumentFiledans le code. - Vous ne pouvez pas passer un chemin à Vous devez appeler
takePersistableUriPermissionAjoutez 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();
}
- 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
DownloadetAndroid/dataandAndroid/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.Externalsur Android,Directory.LibraryNoCloudOn 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
- Recherchez votre code pour
Directory.Documents,Directory.ExternalStorage,/storage/emulated, etrequestLegacyExternalStorage. - Déplacez les fichiers appartenant à l'application vers
Directory.DataouDirectory.ExternalCopiez les anciens fichiers que vous pouvez encore lire lors de la première utilisation de la nouvelle version, puis supprimez-les. - Replace “save to device” features with
FileSharer.save(). - Remplacez les entrées de chemin par un sélecteur.
- ajoutez
android:maxSdkVersionpasser à la gestion de stockage legacy et supprimerREAD_MEDIA_*sauf si vous en avez besoin. - 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.