Die gescoperte Speicherung in Capacitor-Anwendungen beschränkt, was eine Anwendung außerhalb ihrer eigenen Ordner lesen und schreiben kann. Seit Android 10 und vollständig seit Android 11 kann eine Anwendung ihre privaten Verzeichnisse frei verwenden, Dateien in gemeinsamen Sammlungen über MediaStore hinzufügen und für alles andere die Benutzung des Speicherdienstes erfordern. Diese Anleitung erklärt, was sich änderte, Version für Version, welche Capacitor Directory Werte noch funktionieren und wie Dateien auf Capacitor 8 unterstützter Weise gespeichert, importiert und exportiert werden können.
Was sich änderte, Version für Version
| Android | API | Ein wichtiger Wechsel für Capacitor-Anwendungen |
|---|---|---|
| 10 | 29 | Scoped storage introduced. Apps can opt out with requestLegacyExternalStorage="true" während sie sich auf API 29 richten |
| 11 | 30 | Gespeicherte Daten werden für Apps ab Version 30 eingeschränkt. requestLegacyExternalStorage ignoriert. WRITE_EXTERNAL_STORAGE erhält keine Zugriffsrechte mehr. MANAGE_EXTERNAL_STORAGE eingeführt. Der Baumpicker kann den Download-Root oder den Volumen-Root nicht auswählen. |
| 12 | 31-32 | Keine große Änderung im Speicherbereich |
| 13 | 33 | READ_EXTERNAL_STORAGE ersetzt durch READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIOerhält Zugriff auf Dokumente anderer Anwendungen ohne Berechtigung |
| 14 | 34 | Teilweise Zugriff auf Fotos mit READ_MEDIA_VISUAL_USER_SELECTED |
| 15 und 16 | 35-36 | Kein neuer Speichermodell, Play-Politik für Foto- und Videoberechtigungen gilt |
Capacitor 8 Ziele API 36, so läuft Ihre App immer mit vollem gescoptem Speicher auf modernen Geräten. Es gibt keine Flagge, um das alte Verhalten zurückzubekommen.
Was ist bei älteren Capacitor code kaputt gegangen
Code geschrieben für Capacitor 2 oder 3, oder portiert von Cordova, funktioniert oft stillschweigend auf neuen Geräten:
Filesystem.writeFile({ directory: Directory.Documents })oderDirectory.ExternalStoragezu "auf das Telefon speichern". Bei Android 11+ zeigen diese Pfade auf den gemeinsamen Speicher, und Schreibvorgänge scheitern oder der Datei kann nach einem Neustart nicht mehr gelesen werden.- Zugriff auf beliebige Pfade wie
/storage/emulated/0/Download/file.pdfaus einem von einem Benutzer eingegebenen Pfad oder von einem Server empfangen. - Anfrage
READ_EXTERNAL_STORAGEum die Dokumente des Benutzers aufzulisten. Bei Android 13+ hat die Berechtigung für nicht-medienbezogene Dateien keinen Effekt. - Mit
Filesystem.requestPermissions()und vorausgesetztgrantedbedeutet, dass die App überall schreiben kann. - Dateipfade aus einem
content://URI, das von einem Picker zurückgegeben wird.
Was noch ohne jede Erlaubnis funktioniert:
Die eigenen Verzeichnisse der App bleiben von der gescannten Speicherung unberührt und benötigen keine Erlaubnis:
Capacitor Directory |
Android-Pfad | Zu sehen für den Benutzer | Bei Deinstallation entfernt |
|---|---|---|---|
Data, Library, LibraryNoCloud |
/data/data/<package>/files |
Nein | Ja |
Cache |
/data/data/<package>/cache |
Nein | Ja |
External |
/storage/emulated/0/Android/data/<package>/files |
Nur über USB oder einige Dateimanager | Ja |
ExternalCache |
/storage/emulated/0/Android/data/<package>/cache |
Gleich | Ja |
Documents |
Geteilt Documents Ordner |
Ja | Nein |
ExternalStorage |
Geteilte Speicherwurzel | Ja | Nein |
Die ersten vier Zeilen sind sicher. Documents und ExternalStorage are the legacy rows: since Android 11 your app can only see files there that it created itself, and after a reinstall it loses access even to those. Treat them as unsupported for new code.
External ist nützlich für große Dateien (Offline-Video, Karten) weil sie auf einem größeren Partition leben kann, und Dateien dort sind nicht Teil des Auto-Backup. Android/dataso ist es in der Praxis noch privat.
Dateien, die der Benutzer finden kann: MediaStore
Wenn der Benutzer auf eine Rechnung klickt, erwarten sie sie im Downloads-Ordner. Auf Android 10+ können Apps Dateien ohne jede Erlaubnis in die gemeinsamen Downloads, Pictures, Movies, Music und Documents Datenbanken über MediaStore ohne jede Erlaubnis.
@capgo/capacitor-file-sharer umhüllt dies mit 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 öffnet die gleiche Anrufung das Teilen-Sheet, wobei der Benutzer "Speichern in Dateien" auswählt, so dass ein code Pfad beide Plattformen abdeckt.
Auf Android 9 und niedriger benötigt die Schreibzugriff auf gemeinsame Speicher noch WRITE_EXTERNAL_STORAGE. Erkläre es nur für diese Versionen:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
Capacitor 8 unterstützt Android 7 (API 24) und höher, also wenn Sie immer noch Geräte mit Android 7 bis 9 ausliefern, behalten Sie diese Zeile bei.
Fotos und Videos
Für Medien bevorzugen Sie die Plattform-Flüsse gegenüber dem Schreiben von Dateien selbst. @capacitor/camera schreibt in die Galerie mit saveToGallery: true, und benötigt nur noch Legacy-Speicherberechtigungen auf alten Android-Versionen. Siehe unsere Anleitung zu dem Aufnehmen und Bearbeiten von Fotos in Capacitor.
Lesen Sie Benutzerdateien: Picker anstatt Berechtigungen
Der Scoped Storage erwartet, dass der Benutzer Dateien auswählt. Der Android-Picker gibt eine content:// URI zurück, die Ihr App auch lesen kann, obwohl sie keine Speicherberechtigung hat.
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,
});
Für Fotos und Videos @capgo/capacitor-file-picker's pickMedia und @capacitor/camera's chooseFromGallery Verwenden Sie auf Android 13+ den Android Photo Picker, der keine Berechtigung benötigt.
Vermeiden Sie die Berechtigungen READ_MEDIA_*
Google Play erlaubt die Foto- und Videoberechtigungen nur READ_MEDIA_IMAGES und READ_MEDIA_VIDEO für Apps, deren Hauptzweck eine breite Zugriffsmöglichkeit auf das Galerie-Verzeichnis benötigt, wie z.B. Galerie-Apps oder Foto-Editor-Apps. Alle anderen müssen den Picker verwenden und dies im Play Console deklarieren. Wenn ein Plugin, das Sie verwenden, diese Berechtigungen deklariert, entfernen Sie sie aus dem kombinierten Manifest:
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
tools:node="remove" />
<uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"
tools:node="remove" />
Hinzufügen xmlns:tools="http://schemas.android.com/tools" zum <manifest> erstes Mal.
Ordnerzugriff mit der Storage Access Framework
Einige Apps benötigen einen ständigen Zugriff auf einen Ordner: eine Notizen-App, die sich mit einem Benutzerordner synchronisiert, ein Exportordner für eine Feld-App. Die Storage Access Framework (SAF) ermöglicht es dem Benutzer, einen Ordner zu genehmigen ACTION_OPEN_DOCUMENT_TREE:
const { path } = await CapgoFilePicker.pickDirectory();
// On Android: a content://com.android.externalstorage.documents/tree/... URI
Drei Dinge zu beachten:
- Das Ergebnis ist ein Baum-URI, nicht ein Pfad. Sie können es nicht weitergeben an
Filesystem.writeFileDateien darin werden über oder erstellt und gelesenDocumentsContractoderDocumentFilein native code. - Der Zugriff überlebt einen Neustart nicht Der Zugriff überlebt einen Neustart nicht
takePersistableUriPermissionfügen Sie eine kleine native Methode hinzu, wenn Sie das benötigen.
@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();
}
- Einige Ordner können nicht zugewiesen werden. On Android 11+, verweigert der Picker die Root des internen Speichers, die Root des Ordners und
DownloadundAndroid/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() Alle Dateien zugreifen: MANAGE_EXTERNAL_STORAGE
Alle Dateien-Zugriff: MANAGE_EXTERNAL_STORAGE
<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.
Wenn Sie außerhalb von Play verteilen (Unternehmens- MDM, kiosk-gestellte Geräte), ist es eine Option. Ansonsten sollten Sie sich auf MediaStore und SAF einstellen.
Wie andere Plugins passen
| Plugin | Gespeicherte Speicherplatzierung |
|---|---|
@capacitor/filesystem |
Gut mit App-Verzeichnissen. Documents und ExternalStorage wirkt nur für Verzeichnisse auf alten Android-Geräten requestPermissions() nur für die alten Android-Verzeichnisse |
@capacitor/camera |
Verwendet die Systemkamera und den Photo Picker, keine Speicherberechtigung außer saveToGallery auf Android 9 und älter |
@capacitor/share |
. Nur die in der Liste aufgeführten FileProviderkönnen geteilt werden res/xml/file_paths.xml verwendet eigene |
@capgo/capacitor-file-sharer |
und MediaStore für FileProvider keine Konfiguration erforderlich save()Gut mit App-Verzeichnissen. |
@capgo/capacitor-downloader |
Downloads in die App-externe Speicherung, dann verwenden save() um zu exportieren |
@capgo/capacitor-file-picker |
Picker-basiert, keine Berechtigung auf Android 13+ |
@capgo/capacitor-photo-library |
Sein Manifest fügt READ_MEDIA_* für die vollständige Bibliothekslayout, unterliegt sie der oben genannten Google-Policy. pickMedia() verwendet das Systempicker und benötigt keine Berechtigung, daher wenn Sie nur das verwenden, entfernen Sie READ_MEDIA_* mit tools:node="remove" |
Welche Verzeichnis verwenden
- Anwendungsdaten, Einstellungen, Datenbanken:
Directory.Data - Temporäre Dateien und geteilte Exporte:
Directory.Cache - Große wiederherstellbare Medien:
Directory.Externalauf Android,Directory.LibraryNoCloudOn iOS - Eine Datei, die der Benutzer in Downloads oder Bilder speichern möchte: MediaStore via
FileSharer.save() - Eine Datei, die der Benutzer in die App bringt: Picker, dann kopieren Sie sie in
Directory.Data - Eine Ordner, den der Benutzer möchte, dass die App weiterhin schreibt: SAF Baum URI mit einer persistierten Genehmigung
Für die vollständige Lesen und Schreiben API, siehe die Capacitor-Datei-Handling-Leitfaden.
Ein bestehendes App migrieren
- Suchen Sie nach code
Directory.Documents,Directory.ExternalStorage,/storage/emulated, undrequestLegacyExternalStorage. - Übertragen Sie app-eigene Dateien auf
Directory.DataoderDirectory.ExternalBeim ersten Start der neuen Version, kopieren Sie alle alten Dateien, die Sie noch lesen können, und löschen Sie sie dann. - Ersetzen Sie die "Speichern auf Gerät"-Funktionen durch
FileSharer.save(). - Ersetze Pfad-Eingabefelder mit einem Picker.
- Hinzufügen
android:maxSdkVersionzu Legacy-Speichereinstellungen und entfernenREAD_MEDIA_*es sei denn, Sie benötigen es. - Testen Sie auf Geräten oder Emulatoren mit Android 10, 11, 13 und 15 oder 16.
preserveLegacyExternalStorage="true" im <application> tag hält Legacy-Zugriff für Apps bei Updates aus einer Version, die Legacy-Zugriff verwendet requestLegacyExternalStorage, bis die App neu installiert wird. Es hilft nur bei einem einmaligen Migrationsschritt, also sollten Sie nicht auf es zählen.
Schadensbegrenzung
EACCES (Permission denied) Schreiben Sie in /storage/emulated/0/...: Sie schreiben direkt in den gemeinsamen Speicher. Verwenden Sie MediaStore oder einen App-Ordner.
Datei wurde mit Directory.Documents ist nach Neustart verschwunden: Die App verlor die Kontrolle über sie. Verwenden Sie MediaStore für Benutzerdateien.
FileNotFoundException eine URI später content:// URI späterDie temporäre Genehmigung ist abgelaufen. Kopieren Sie das Datei, wenn es ausgewählt wird, oder persistieren Sie die Genehmigung für drei URIs.
Warnung im Google Play Console über Foto- und VideoberechtigungenEin Plugin wurde hinzugefügt READ_MEDIA_IMAGES or READ_MEDIA_VIDEOEntfernen Sie sie mit tools:node="remove" erwähnt, aber schreibt immer noch fehlschlagen
requestPermissions() Zugriffe gewährt, aber Schreibvorgänge fehlschlagen: auf Android 11+ wird die Legacy-Speicherberechtigung erteilt, aber für gemeinsame Speicher ist sie bedeutungslos.
Halten Sie native und Web-Teile im Einklang.
Speicherrichtlinien sind native Konfiguration, daher benötigen Änderungen an der Manifestdatei oder Plugins einen Store-Release, der Capgo Build kann in der Cloud erzeugt werden. Die Dateilogik rund um sie (Pfade, Ordnernamen, Exportflüsse) ist JavaScript, das Sie mit Capgo live Updates.