Penyimpanan Terbatas Android membatasi apa yang dapat dibaca dan ditulis oleh aplikasi Capacitor di luar folder sendiri. Sejak Android 10, dan sepenuhnya sejak Android 11, aplikasi dapat menggunakan direktori pribadi secara bebas, dapat menambahkan file ke koleksi bersama melalui MediaStore, dan memerlukan pengguna untuk memilih apa pun lainnya melalui Framework Akses Penyimpanan. Panduan ini menjelaskan apa yang berubah per versi Android, nilai Capacitor yang masih berfungsi, dan cara menyimpan, mengimpor, dan mengexport file dengan cara yang didukung di Capacitor 8. Directory Nilai-nilai masih berfungsi, dan cara menyimpan, mengimport, serta mengexport file dengan cara yang didukung pada Capacitor 8.
Apa yang berubah, versi demi versi
| Android | API | Perubahan yang berpengaruh bagi aplikasi Capacitor |
|---|---|---|
| 10 | 29 | Penyimpanan terbatas diperkenalkan. Aplikasi dapat menolaknya dengan requestLegacyExternalStorage="true" saat mengarahkan ke API 29 |
| 11 | 30 | Penyimpanan Terbatas Diperlukan untuk Aplikasi yang Target 30+. requestLegacyExternalStorage diabaikan. WRITE_EXTERNAL_STORAGE tidak lagi memberikan akses apa pun. MANAGE_EXTERNAL_STORAGE diperkenalkan. Pemilih pohon tidak dapat memilih Root Download atau Root Volume |
| 12 | 31-32 | Tidak ada perubahan penyimpanan utama |
| 13 | 33 | READ_EXTERNAL_STORAGE diperlukan. READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIOTidak ada izin yang memberikan akses ke dokumen aplikasi lain |
| 14 | 34 | Akses foto parsial dengan READ_MEDIA_VISUAL_USER_SELECTED |
| 15 dan 16 | 35-36 | Tidak ada model penyimpanan baru, kebijakan Play pada izin foto dan video ditegakkan |
Capacitor 8 target API 36, sehingga aplikasi Anda selalu berjalan dengan penyimpanan terbatas pada perangkat modern. Tidak ada flag untuk mendapatkan perilaku lama kembali.
Apa yang rusak pada versi lama Capacitor code
Code ditulis untuk Capacitor 2 atau 3, atau dipindahkan dari Cordova, sering gagal diam-diam pada perangkat baru:
Filesystem.writeFile({ directory: Directory.Documents })atauDirectory.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.- Membaca jalur sembarang seperti
/storage/emulated/0/Download/file.pdfdari sebuah jalur yang ditulis di pengaturan atau diterima dari server. - Requesting
READ_EXTERNAL_STORAGEMengakses daftar dokumen pengguna. Pada Android 13+, izin ini tidak berlaku untuk file non-media. - Memakai
Filesystem.requestPermissions()dan menganggapgrantedberarti aplikasi dapat menulis di mana saja. - Membangun jalur file dari sebuah
content://URI yang dikembalikan oleh sebuah picker.
Apa yang masih berfungsi tanpa izin apa pun
Direktori aplikasi sendiri tidak terpengaruh oleh penyimpanan terbatas dan tidak memerlukan izin:
Capacitor Directory |
Rute Android | Jalur Android | Dapat dilihat oleh pengguna |
|---|---|---|---|
Data, Library, LibraryNoCloud |
/data/data/<package>/files |
No | Ya |
Cache |
/data/data/<package>/cache |
Belum | Ya |
External |
/storage/emulated/0/Android/data/<package>/files |
Hanya melalui USB atau beberapa pengelola file | Ya |
ExternalCache |
/storage/emulated/0/Android/data/<package>/cache |
Sama | Ya |
Documents |
Dibagikan Documents Folder bersama |
Ya | Belum |
ExternalStorage |
Root penyimpanan bersama | Ya | Belum |
Empat baris pertama aman. Documents dan ExternalStorage adalah baris warisan: sejak Android 11 aplikasi Anda hanya dapat melihat file di sana yang dibuat sendiri, dan setelah reinstall bahkan kehilangan akses bahkan ke mereka. Tangan mereka sebagai tidak mendukung untuk code baru.
External berguna untuk file besar (video offline, peta) karena mungkin hidup di partisi yang lebih besar, dan file di sana tidak termasuk Auto Backup. Sejak Android 11, aplikasi lain tidak dapat membaca Android/data, sehingga masih privat secara praktis.
Menyimpan file yang dapat ditemukan pengguna: MediaStore
Ketika pengguna mengetuk “Download” pada faktur, mereka mengharapkan di folder Downloads. Pada Android 10+, aplikasi dapat memasukkan file ke koleksi bersama melalui MediaStore tanpa izin apa pun. Downloads, Pictures, Movies, Music dan Documents mengakses koleksi melalui MediaStore tanpa izin apa pun.
@capgo/capacitor-file-sharer mengelilingi ini dengan 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, panggilan yang sama membuka sheet berbagi, di mana pengguna memilih “Simpan ke File”, sehingga satu code jalur menutupi kedua platform.
On Android 9 dan lebih rendah, menulis ke penyimpanan bersama masih memerlukan WRITE_EXTERNAL_STORAGE. Deklarasikan hanya untuk versi-versi tersebut:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
Capacitor 8 mendukung Android 7 (API 24) dan di atasnya, jadi jika Anda masih mengirimkan ke perangkat Android 7 hingga 9, jangan lupa garis itu.
Foto dan video
Untuk media, prefer aliran platform daripada menulis file sendiri. @capacitor/camera menulis ke galeri dengan saveToGallery: true, dan hanya memerlukan izin penyimpanan lama pada versi Android yang lebih lama. Lihat panduan kami untuk mengambil dan mengedit foto di Capacitor.
Membaca file pengguna: pilih file daripada izin
Penyimpanan terbatas mengharapkan pengguna untuk memilih file. Pemilih Android mengembalikan content:// URI yang aplikasi Anda dapat membaca meskipun tidak memiliki izin penyimpanan.
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,
});
Untuk foto dan video, @capgo/capacitor-file-pickers pickMedia dan @capacitor/cameras chooseFromGallery gunakan Picker Foto Android di Android 13+, yang tidak memerlukan izin sama sekali.
Hindari izin akses media baca
Google Play memiliki kebijakan izin foto dan video yang hanya memungkinkan READ_MEDIA_IMAGES dan READ_MEDIA_VIDEO for apps whose core purpose needs broad gallery access, such as gallery apps or photo editors. Everyone else must use the picker and declare that in Play Console. If a plugin you use declares these permissions, remove them from the merged 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" />
Add xmlns:tools="http://schemas.android.com/tools" Tambahkan <manifest> Tandai pertama.
Akses Folder dengan Framework Akses Penyimpanan
Beberapa aplikasi memerlukan akses berkelanjutan ke folder: aplikasi catatan yang sinkron dengan folder pengguna, folder ekspor untuk aplikasi lapangan. Framework Akses Penyimpanan (SAF) memungkinkan pengguna memberikan akses ke folder dengan ACTION_OPEN_DOCUMENT_TREE:
const { path } = await CapgoFilePicker.pickDirectory();
// On Android: a content://com.android.externalstorage.documents/tree/... URI
Tiga hal yang perlu diketahui:
- Hasilnya adalah URI pohon, bukan jalur. Anda tidak dapat melewatkan URI tersebut ke
Filesystem.writeFile. File di dalamnya dibuat dan dibaca melaluiDocumentsContractatauDocumentFiledi dalam native code. - Akses tidak bertahan setelah restart kecuali aplikasi Anda memanggil
takePersistableUriPermissionpada URI tersebut. Tambahkan metode native kecil jika Anda membutuhkannya:
@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();
}
- Beberapa folder tidak dapat diberikan akses. On Android 11+, pilih file tidak menerima akar penyimpanan internal, akar folder, dan
DownloaddanAndroid/dataandAndroid/obbMinta pengguna membuat atau memilih sebuah 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() Akses semua file: MANAGE_EXTERNAL_STORAGE
Semua file akses: 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.
Jika Anda distribusikan di luar Play (perusahaan MDM, perangkat kiosk yang disediakan), itu adalah pilihan. Jika tidak, rancang sekitar MediaStore dan SAF.
Bagaimana plugin lainnya berintegrasi
| Plugin | Penyimpanan Terbatas |
|---|---|
@capacitor/filesystem |
Baik dengan direktori aplikasi. Documents dan ExternalStorage sudah tidak kompatibel di Android 11+. requestPermissions() Hanya berlaku untuk direktori pada Android yang lama |
@capacitor/camera |
Menggunakan kamera sistem dan Picker Foto, tidak ada izin penyimpanan except saveToGallery pada Android 9 dan lebih lama |
@capacitor/share |
Berbagi melalui FileProviderpada Android 9 dan lebih lama res/xml/file_paths.xml Dapat dibagikan |
@capgo/capacitor-file-sharer |
pada Android 9 dan lebih lama FileProvider dan MediaStore untuk save()pada Android 9 dan lebih lama |
@capgo/capacitor-downloader |
Unduh ke penyimpanan eksternal aplikasi, kemudian gunakan save() untuk mengexport |
@capgo/capacitor-file-picker |
Pilih berdasarkan, tidak memerlukan izin pada Android 13+ |
@capgo/capacitor-photo-library |
Manifestnya menambahkan READ_MEDIA_* untuk membaca library secara lengkap, dengan memenuhi kebijakan Play di atas. pickMedia() menggunakan pilih sistem dan tidak memerlukan izin, jadi jika itu semua yang Anda gunakan, hapus READ_MEDIA_* dengan tools:node="remove" |
Di mana direktori untuk digunakan
- Data aplikasi, pengaturan, database:
Directory.Data - File sementara dan ekspor berbagi:
Directory.Cache - Media yang dapat diunduh kembali besar:
Directory.Externalpada Android,Directory.LibraryNoCloudPada iOS - Sebuah file yang pengguna ingin di Downloads atau Pictures: MediaStore via
FileSharer.save() - Sebuah file yang pengguna membawa ke dalam aplikasi: picker, kemudian salin ke
Directory.Data - Sebuah folder yang pengguna ingin aplikasi menulis kembali: SAF tree URI dengan grant yang disimpan
Untuk akses penuh baca dan tulis API, lihat Panduan pengelolaan file Capacitor.
Migrasi aplikasi yang sudah ada
- Cari code Anda untuk
Directory.Documents,Directory.ExternalStorage,/storage/emulated, danrequestLegacyExternalStorage. - Pindahkan file yang dimiliki aplikasi ke
Directory.DataatauDirectory.ExternalPada pertama kali menjalankan versi baru, salin semua file lama yang masih dapat dibaca, kemudian hapus mereka. - Ubah fitur "simpan ke perangkat" dengan
FileSharer.save(). - Ganti input jalur dengan pilihan.
- Tambahkan
android:maxSdkVersionke penyimpanan legacy dan hapusREAD_MEDIA_*kecuali Anda membutuhkannya. - Jalankan tes pada perangkat Android 10, 11, 13, dan 15 atau 16, atau emulator. Sifatnya berbeda tepat pada batasan-batasan tersebut.
preserveLegacyExternalStorage="true" di <application> tag ini menjaga akses legacy untuk aplikasi yang diperbarui dari versi yang menggunakan requestLegacyExternalStoragesebelum aplikasi diinstal ulang. Ini hanya membantu selama langkah migrasi satu kali, jadi jangan bergantung padanya.
Troubleshooting
EACCES (Permission denied) menulis ke /storage/emulated/0/...anda menulis ke penyimpanan bersama secara langsung. Gunakan MediaStore atau direktori aplikasi.
File disimpan dengan Directory.Documents hilang setelah reinstall: aplikasi kehilangan kepemilikan atasnya. Gunakan MediaStore untuk file pengguna.
FileNotFoundException membaca sebuah content:// URI kemudian: izin sementara telah berakhir. Salin file ketika dipilih, atau tetapkan izin untuk URI-URI yang lebih dari satu.
peringatan Play Console tentang izin foto dan video: plugin tambahan READ_MEDIA_IMAGES atau READ_MEDIA_VIDEOHapus mereka dengan tools:node="remove" jika Anda tidak membutuhkannya.
requestPermissions() izin kembali diberikan tetapi menulis masih gagal: pada Android 11+ izin penyimpanan warisan diberikan tetapi tidak bermakna untuk penyimpanan bersama.
Sinkronkan bagian native dan web
Aturan penyimpanan adalah konfigurasi native, jadi perubahan pada manifest atau plugin memerlukan rilis penyimpanan, yang Capgo Pembangunan Dapat menghasilkan di awan. Logika file di sekitar mereka (path, nama folder, aliran ekspor) adalah JavaScript yang dapat Anda perbarui dengan update Capgo secara langsung.