Android scoped storage限制了Capacitor应用程序可以在其自身文件夹之外读取和写入的内容。从Android 10开始,完全从Android 11开始,应用程序可以自由使用其私有目录,可以通过MediaStore添加文件到共享集合,并需要用户选择其他任何内容通过Storage Access Framework。这一指南解释了每个Android版本发生了什么变化,哪些Capacitor Directory 值仍然有效,以及如何在Capacitor 8上以支持的方式保存、导入和导出文件。
每个版本发生了什么变化
| Android | API | Capacitor中的变化 |
|---|---|---|
| 10 | 29 | scoped storage被引入。应用程序可以通过 requestLegacyExternalStorage="true" 在开发 API 29 的应用时 |
| 11 | 30 | scoped storage对目标 requestLegacyExternalStorage 被忽略。 WRITE_EXTERNAL_STORAGE 不再授予任何权限。 MANAGE_EXTERNAL_STORAGE 被引入。树选择器不能选择下载根或卷根 |
| 12 | 31-32 | 没有主要的存储变化 |
| 13 | 33 | READ_EXTERNAL_STORAGE 被替换为 READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO.其他应用程序的文档没有权限授予访问 |
| 14 | 34 | Partial photo access with READ_MEDIA_VISUAL_USER_SELECTED |
| 15 和 16 | 35-36 | 没有新存储模型,Play 政策对照片和视频权限实施 |
Capacitor 8 个目标 API 36,因此您的应用程序始终在现代设备上运行全局存储。没有标志可以恢复旧的行为。
在较旧的 Capacitor code 中
Code 编写用于 Capacitor 2 或 3,或者从 Cordova 迁移,通常在新设备上静默失败:
Filesystem.writeFile({ directory: Directory.Documents })或Directory.ExternalStorage到“将文件保存到手机”。在 Android 11+ 上,这些路径指向共享存储,写入失败或文件无法在重新安装后读取。- 读取任意路径,如
/storage/emulated/0/Download/file.pdf从一个路径中接收 - 请求
READ_EXTERNAL_STORAGE列出用户的文档。从 Android 13+ 开始,权限对非媒体文件无效。 - 使用
Filesystem.requestPermissions()并且假设granted意味着应用程序可以在任何地方写入。 - 从一个
content://URI返回的文件路径
仍然不需要任何权限就可以工作
应用程序自己的目录不受scoped storage影响,需要的权限是:
Capacitor Directory |
Android路径 | 可见 | 在卸载时删除 |
|---|---|---|---|
Data, Library, LibraryNoCloud |
/data/data/<package>/files |
否 | Yes |
Cache |
/data/data/<package>/cache |
No | Yes |
External |
/storage/emulated/0/Android/data/<package>/files |
仅通过USB或一些文件管理器 | Yes |
ExternalCache |
/storage/emulated/0/Android/data/<package>/cache |
相同 | Yes |
Documents |
共享 Documents 文件夹 |
Yes | No |
ExternalStorage |
共享存储根目录 | Yes | No |
安全的前四行 Documents 并且 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 保存用户可以找到文件:MediaStore Android/data所以在实践中,它仍然是私有的。
MediaStore
Android 11 之后,应用程序只能看到它自己创建的文件 Downloads, Pictures, Movies, Music 并且 Documents 重新安装后,即使是那些文件也会丢失访问权
@capgo/capacitor-file-sharer 对于大文件(离线视频,地图)有用,因为它可能存储在更大的分区上,文件不属于 Auto Backup。 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 中,同样的调用会打开分享面板,用户选择“保存到文件”,因此一个 code 路径可以同时支持两种平台。
On Android 9 以下版本,写入共享存储仍然需要 WRITE_EXTERNAL_STORAGE. 只需为这些版本声明:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
Capacitor 8 支持 Android 7 (API 24) 以上版本,因此如果您仍然将应用程序分发到 Android 7 到 9 设备,请保留该行。
照片和视频
对于媒体,优先使用平台流程而不是自己写文件。 @capacitor/camera 将写入相册中 saveToGallery: true,并且只需要在旧 Android 版本上请求旧的存储权限。请参阅我们的指南 在 Capacitor 中拍摄和编辑照片.
读取用户文件:选择器而不是权限
scoped storage 期望用户选择文件。Android 选择器返回一个 URI,应用程序可以读取,即使它没有存储权限。 content:// scoped storage 期望用户选择文件。Android 选择器返回一个 URI,应用程序可以读取,即使它没有存储权限。
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,
});
对于照片和视频, @capgo/capacitor-file-picker’s pickMedia 和 @capacitor/camera’s chooseFromGallery 使用 Android 13+ 的 Android Photo Picker,不需要任何权限。
避免 READ_MEDIA_* 权限
Google Play 的照片和视频权限政策仅允许 READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 为需要广泛图库访问的应用程序,例如图库应用程序或照片编辑器。其他应用程序必须使用选择器并在 Play Console 中声明。如果您使用的插件声明了这些权限,请从合并的清单中删除它们:
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
tools:node="remove" />
<uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"
tools:node="remove" />
添加 xmlns:tools="http://schemas.android.com/tools" 到标签中。 <manifest> 首先。
使用存储访问框架访问文件夹
某些应用程序需要持续访问文件夹:一个笔记应用程序同步到用户文件夹,一个字段应用程序的导出文件夹。存储访问框架(SAF)允许用户授予一个文件夹 ACTION_OPEN_DOCUMENT_TREE:
const { path } = await CapgoFilePicker.pickDirectory();
// On Android: a content://com.android.externalstorage.documents/tree/... URI
三点需要注意
- 结果是一个树型URI,而不是路径 不能将其传递给
Filesystem.writeFile或DocumentsContract在本地DocumentFilein native code. - 除非您的应用程序在URI上调用 添加一个小型本地方法,如果您需要这样做
takePersistableUriPermission某些文件夹无法授予
@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();
}
- 在本地__CAPGO_KEEP_0__. On Android 11+, internal storage root folder is refused, and the picker asks users to create or pick a subfolder.
DownloadfolderAndroid/dataandAndroid/obb.
Ask users to create or pick a subfolder. getPersistedUriPermissions() 启动时询问,询问时再次启动。
on start and ask again when it is gone.
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
All files access: MANAGE_EXTERNAL_STORAGE
如果您需要在非Play商店(企业MDM,侧载式kiosk设备)中分发应用,则这是一个选项。否则,请围绕MediaStore和安全文件访问框架(SAF)进行设计。
If you distribute outside Play (enterprise MDM, sideloaded kiosk devices), it is an option. Otherwise, design around MediaStore and SAF.
| Plugin | Plugin (Plugin) is not translated as it is a protected token. Scoped storage impact |
|---|---|
@capacitor/filesystem |
与应用目录无关。 Documents 并且 ExternalStorage 在 Android 11+ 上是遗留的。 requestPermissions() 仅对旧 Android 上的这些目录有效。 |
@capacitor/camera |
使用系统相机和相册选择器,除了存储权限外, saveToGallery 在 Android 9 和更早版本上 |
@capacitor/share |
通过一个 FileProvider。仅列出的路径可以共享 res/xml/file_paths.xml 使用自己的 |
@capgo/capacitor-file-sharer |
和 MediaStore FileProvider ,无需配置 save()__CAPGO_KEEP_0__ |
@capgo/capacitor-downloader |
将下载到应用程序的外部存储,然后使用 save() 导出 |
@capgo/capacitor-file-picker |
基于选择器,Android 13+ 不需要权限 |
@capgo/capacitor-photo-library |
其清单添加 READ_MEDIA_* 以便全库读取,遵循上述 Google Play 政策。 pickMedia() 使用系统选择器且不需要权限,因此如果您只使用此功能,请移除 READ_MEDIA_* 使用 tools:node="remove" |
选择使用哪个目录
- 应用程序数据、设置、数据库:
Directory.Data - 临时文件和共享导出:
Directory.Cache - 可重下载的大型媒体:
Directory.External在 Android 上,Directory.LibraryNoCloudOn iOS - A file the user wants in Downloads or Pictures:MediaStore via
FileSharer.save() - A file the user brings into the app:picker,then copy into
Directory.Data - A folder the user wants the app to keep writing to:SAF tree URI with a persisted grant
For the full read and write API,see the Capacitor 文件处理指南.
迁移现有应用
- 在您的code中搜索
Directory.Documents,Directory.ExternalStorage,/storage/emulated, andrequestLegacyExternalStorage. - 将应用所有权文件移动到
Directory.Data或Directory.External在新版本首次启动时,复制您仍然可以读取的任何旧文件,然后删除它们 - 替换“保存到设备”功能
FileSharer.save(). - 替换路径输入为选择器
- Add
android:maxSdkVersion替换路径输入为选择器READ_MEDIA_*到遗留存储权限并移除 - 除非您需要它
preserveLegacyExternalStorage="true" 在应用中 <application> 在 requestLegacyExternalStorage标签保留了对从使用
Troubleshooting
EACCES (Permission denied) 故障排除 /storage/emulated/0/...写入:您正在直接写入共享存储。使用MediaStore或应用目录。
使用 Directory.Documents 重新安装后: 文件丢失了所有权。 使用 MediaStore 处理用户文件。
FileNotFoundException 读取 content:// URI 后: 临时授权过期。 复制文件时选择它,或者持久化授权以处理多个 URI。
Play Console 警告关于照片和视频权限: 插件添加了 READ_MEDIA_IMAGES 或 READ_MEDIA_VIDEO: HTML 文本片段来自更长的 Capgo UI 字符串(父级键 `consulting_faq_subtitle`)。 页面/区域: 咨询服务页面。 角色: 部分标题或标语。 见于: 页面 consulting.astro。 保留 Capgo 产品/品牌和开发者术语的原始形式。 消息键 `consulting_faq_subtitle`(咨询 FAQ 部分标题)。 | 页面/区域: Appflow 比较/迁移营销文本。 角色: 短 UI 标签或导航项。 见于: 页面 alternatives/ionic-appflow.astro,页面 ionic-enterprise-plugins.astro,页面 solutions/ionic-enterprise-plugins.astro。 消息键 `appflow_plugins_or`(Appflow 插件或)。 tools:node="remove" .
requestPermissions() 移除它们,如果您不需要它们。: 在 Android 11+ 上,legacy 存储权限被授予,但对于共享存储来说是无意义的。
保持原生和 web 部分的同步
存储规则是原生配置,因此对清单或插件的更改需要发布一个存储版本,这 Capgo 构建 在云端可以生成。围绕它们的文件逻辑(路径、文件夹名称、导出流程)是您可以使用 Capgo 实时更新.