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 | 30 以降をターゲットとするアプリでは、Scoped storage が強制適用されます。 requestLegacyExternalStorage 無視されます。 WRITE_EXTERNAL_STORAGE 何も付与されなくなりました。 MANAGE_EXTERNAL_STORAGE 導入されました。Tree picker では Download ルートやボリュームルートを選択できなくなりました |
| 12 | 31-32 | ストレージに関する大きな変更なし |
| 13 | 33 | READ_EXTERNAL_STORAGE に置き換えられました READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO. 他のアプリのドキュメントにアクセスできる権限はありません |
| 14 | 34 | による写真への部分的なアクセス READ_MEDIA_VISUAL_USER_SELECTED |
| 15 および 16 | 35-36 | 新しいストレージモデルはなく、写真と動画の権限に関する Play ポリシーが適用されます |
Capacitor は API 36 を対象としているため、最新のデバイスでは常にフルスコープストレージで動作します。以前の挙動に戻すフラグは存在しません。
古い Capacitor code で何が壊れたのか
Capacitor 2 または 3 用に書かれた Code や、Cordova から移植されたものは、新しいデバイスでサイレントに失敗することがよくあります:
Filesystem.writeFile({ directory: Directory.Documents })またはDirectory.ExternalStorage「端末に保存」について。Android 11以降、これらのパスは共有ストレージを指すため、書き込みに失敗したり、再インストール後にファイルが読み込めなくなったりします。- 次のような任意のパスの読み取り:
/storage/emulated/0/Download/file.pdf設定で入力されたパスや、サーバーから受信したパスから。 - リクエスト
READ_EXTERNAL_STORAGEユーザーのドキュメントを列挙するために使用します。Android 13以降では、メディアファイル以外に対してこの権限は何も機能しません。 - 使用する
Filesystem.requestPermissions()そして、grantedアプリがどこにでも書き込めることを意味します。 - ピッカーが返す URI から
content://ファイルパスを構築する。
権限なしでも動作する機能
アプリ自身のディレクトリはスコープ付きストレージの影響を受けず、権限も不要です:
Capacitor Directory |
Android のパス | ユーザーに表示 | アンインストール時に削除 |
|---|---|---|---|
Data, Library, LibraryNoCloud |
/data/data/<package>/files |
いいえ | はい |
Cache |
/data/data/<package>/cache |
いいえ | はい |
External |
/storage/emulated/0/Android/data/<package>/files |
USBまたは一部のファイルマネージャー経由のみ | はい |
ExternalCache |
/storage/emulated/0/Android/data/<package>/cache |
同じ | はい |
Documents |
共有 Documents フォルダ |
はい | いいえ |
ExternalStorage |
共有ストレージのルート | はい | いいえ |
最初の4行は安全です。 Documents と ExternalStorage はレガシーな行です。Android 11 以降、アプリはそこに存在するファイルのうち、自分が作成したもののみ参照できます。再インストール後は、それらへのアクセス権も失われます。新しい code では非サポートとして扱うべきです。
External は、大きなファイル(オフライン動画、マップなど)に有用です。より大きなパーティション上に存在する可能性があり、そのファイルは Auto Backup の対象外です。Android 11 以降、他のアプリは Android/dataを読み取ることができないため、実質的には依然としてプライベートです。
ユーザーが見つけられる場所にファイルを保存: MediaStore
ユーザーが請求書で「ダウンロード」をタップすると、Downloads フォルダに保存されることを期待します。Android 10 以降、アプリは権限なしで共有 Downloads, Pictures, Movies, Music と Documents コレクションにファイルを挿入できます(MediaStore を使用)。
@capgo/capacitor-file-sharer これをラップします 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
iOSでは同じ呼び出しでシェアシートが開き、ユーザーが「ファイルに保存」を選ぶので、1つの code パスで両プラットフォームに対応できます。
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のピッカーは content:// ストレージ権限がなくてもアプリが読み取れる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の pickMedia と @capacitor/cameraの chooseFromGallery Android 13以降では、権限が一切不要なAndroid Photo Pickerを使用してください。
READ_MEDIA_* 権限を避ける
Google Playの写真・動画権限ポリシーでは、 READ_MEDIA_IMAGES と READ_MEDIA_VIDEO ギャラリーアプリや写真編集アプリなど、コア機能として幅広いギャラリーアクセスが必要なアプリにのみ許可されています。それ以外のアプリはPhoto Pickerを使用し、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> タグを先に設定します。
Storage Access Frameworkによるフォルダアクセス
フォルダへの継続的なアクセスが必要なアプリがあります。ユーザーフォルダに同期するメモアプリや、フィールドアプリのエクスポートフォルダなどです。Storage Access Framework (SAF) を使用すると、ユーザーがフォルダを ACTION_OPEN_DOCUMENT_TREE:
const { path } = await CapgoFilePicker.pickDirectory();
// On Android: a content://com.android.externalstorage.documents/tree/... URI
3つのポイント:
- 結果はパスではなく、ツリー URI です。 これを渡すことはできません
Filesystem.writeFile。内部のファイルは、以下を通じて作成・読み取りますDocumentsContractまたはDocumentFileネイティブ 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();
}
- 権限を付与できないフォルダがあります。 Android 11以降では、内部ストレージのルートや、
Downloadフォルダのルート、およびAndroid/dataとAndroid/obbは、ピッカーで選択できません。ユーザーにはサブフォルダを作成するか選択するよう案内してください。
永続的な権限はアプリごとに数が制限されており、ユーザーはシステム設定やアプリデータの消去によって取り消すことができます。アクセスは以下で確認します。 getPersistedUriPermissions() 起動時に確認し、失われていれば再度リクエストしてください。
全ファイルへのアクセス: MANAGE_EXTERNAL_STORAGE
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
この権限は従来の「すべてを読み書きできる」動作を実現しますが、ランタイムプロンプトではありません。ユーザーが専用の設定画面で有効にする必要があり、Google Play による手動審査も行われます。許可されるユースケースは、ファイルマネージャー、バックアップと復元、アンチウイルス、ドキュメント管理、端末内のファイル検索など、広範なアクセスがコア機能となるものに限られます。メモアプリや、ダウンロードフォルダからファイルを開くPDFビューア、「エクスポートを保存するだけ」のアプリは却下されます。
Play 以外(企業のMDMやサイドロードされたキオスク端末など)で配布する場合は選択肢に入ります。それ以外は MediaStore と SAF を前提に設計してください。
他のプラグインとの関係
| プラグイン | Scoped storage の影響 |
|---|---|
@capacitor/filesystem |
アプリディレクトリ内であれば問題ありません。 Documents と ExternalStorage Android 11 以降ではレガシーです。 requestPermissions() 旧Androidのディレクトリでのみ重要 |
@capacitor/camera |
システムカメラとPhoto Pickerを使用。ストレージ権限は不要 saveToGallery Android 9以前の場合 |
@capacitor/share |
を介して共有 FileProvider。にリストされたパスのみ res/xml/file_paths.xml 共有可能 |
@capgo/capacitor-file-sharer |
独自の FileProvider とMediaStoreを使用 save()、設定不要 |
@capgo/capacitor-downloader |
アプリ外部ストレージにダウンロードし、 save() でエクスポート |
@capgo/capacitor-file-picker |
Pickerベース。Android 13以降は権限不要 |
@capgo/capacitor-photo-library |
そのマニフェストに追加します READ_MEDIA_* 上記のPlayポリシーに従い、ライブラリ全体を読み取ります。 pickMedia() システムピッカーを使用し、権限は不要です。これが唯一の用途であれば、 READ_MEDIA_* を使用して tools:node="remove" |
どのディレクトリを使うか
- アプリデータ、設定、データベース:
Directory.Data - 一時ファイルと共有エクスポート:
Directory.Cache - 再ダウンロード可能な大容量メディア:
Directory.ExternalAndroidでは、Directory.LibraryNoCloudiOSの場合 - ユーザーがDownloadsやPicturesに置きたいファイル:MediaStore経由で
FileSharer.save() - ユーザーがアプリに取り込むファイル:pickerで選択してコピー
Directory.Data - アプリに継続的に書き込ませたいフォルダ:永続化された許可付きのSAF tree URI
読み書きの完全な API については、 Capacitor ファイルハンドリングガイド.
既存アプリの移行
- code 内で検索
Directory.Documents,Directory.ExternalStorage,/storage/emulatedやrequestLegacyExternalStorage. - アプリ所有のファイルを以下へ移動
Directory.DataまたはDirectory.External. 新バージョンの初回起動時に、まだ読める古いファイルをすべてコピーしてから削除してください。 - 「デバイスへの保存」機能を
FileSharer.save(). - パス入力はピッカーに置き換えてください。
- 追加
android:maxSdkVersionレガシーなストレージ権限に置き換え、READ_MEDIA_*必要でない限り削除してください。 - Android 10、11、13、15 または 16 の実機またはエミュレータでテストしてください。挙動はまさにこれらの境界で異なります。
preserveLegacyExternalStorage="true" の <application> タグは、以前のバージョンからアップデートされたアプリのレガシーアクセスを維持します requestLegacyExternalStorage, アプリが再インストールされるまでです。一度限りの移行ステップでのみ役立つので、これに依存しないでください。
トラブルシューティング
EACCES (Permission denied) への書き込み /storage/emulated/0/...:共有ストレージに直接書き込んでいます。MediaStore またはアプリディレクトリを使用してください。
ファイルが保存されました Directory.Documents 再インストール後に消えます:アプリが所有権を失いました。ユーザーファイルには MediaStore を使用してください。
FileNotFoundException 後で content:// URI を読み取る:一時的な許可の有効期限が切れました。ファイルは選択時にコピーするか、ツリー URI の許可を永続化してください。
Play Console の写真・動画権限に関する警告:プラグインが追加した READ_MEDIA_IMAGES または READ_MEDIA_VIDEO。不要な場合は tools:node="remove" で削除してください。
requestPermissions() granted を返すが、書き込みは依然として失敗する:Android 11 以降では、レガシーストレージ権限は付与されても共有ストレージでは意味がありません。
ネイティブとWeb部分の同期を維持
ストレージのルールはネイティブ設定なので、マニフェストやプラグインの変更にはストアリリースが必要で、 Capgo のビルド クラウドで生成できます。それらを取り巻くファイル処理(パス、フォルダ名、エクスポートフロー)は、JavaScriptで記述されており、 Capgo Live Updates.