[解決済み] Androidで外部SDカードに書き込む汎用的な方法
質問
私のアプリケーションでは、デバイスストレージに多くの画像を保存する必要があります。そのようなファイルはデバイス ストレージをいっぱいにする傾向があるので、ユーザーが外部 SD カードを保存先フォルダーとして選択できるようにしたいと思います。
Android はユーザーが外部 SD カードに書き込むことを許可しないということをどこでも読みます。SD カードというのは、外部でマウント可能な SD カードと 外部ストレージではなく しかし、ファイル マネージャー アプリケーションは、すべての Android バージョンで外部 SD に書き込むことができます。
異なる API レベル (Pre-KitKat, KitKat, Lollipop+) で外部 SD カードに読み取り/書き込みアクセスを許可する良い方法は何でしょうか?
アップデート 1
Doomknightの回答から方法1を試しましたが、無駄でした。 お分かりのように、私は SD に書き込もうとする前に実行時にパーミッションをチェックしています。
HashSet<String> extDirs = getStorageDirectories();
for(String dir: extDirs) {
Log.e("SD",dir);
File f = new File(new File(dir),"TEST.TXT");
try {
if(ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)==PackageManager.PERMISSION_GRANTED) {
f.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
}
しかし、アクセスエラーが発生します、2種類の端末で試しました。HTC10とShield K1の2つのデバイスで試してみました。
10-22 14:52:57.329 30280-30280/? E/SD: /mnt/media_rw/F38E-14F8
10-22 14:52:57.329 30280-30280/? W/System.err: java.io.IOException: open failed: EACCES (Permission denied)
10-22 14:52:57.329 30280-30280/? W/System.err: at java.io.File.createNewFile(File.java:939)
10-22 14:52:57.329 30280-30280/? W/System.err: at com.myapp.activities.TestActivity.onResume(TestActivity.java:167)
10-22 14:52:57.329 30280-30280/? W/System.err: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1326)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.Activity.performResume(Activity.java:6338)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3336)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3384)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2574)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.access$900(ActivityThread.java:150)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1399)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.os.Handler.dispatchMessage(Handler.java:102)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.os.Looper.loop(Looper.java:168)
10-22 14:52:57.330 30280-30280/? W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5885)
10-22 14:52:57.330 30280-30280/? W/System.err: at java.lang.reflect.Method.invoke(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:819)
10-22 14:52:57.330 30280-30280/? W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709)
10-22 14:52:57.330 30280-30280/? W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
10-22 14:52:57.330 30280-30280/? W/System.err: at libcore.io.Posix.open(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err: at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
10-22 14:52:57.330 30280-30280/? W/System.err: at java.io.File.createNewFile(File.java:932)
10-22 14:52:57.330 30280-30280/? W/System.err: ... 14 more
どのように解決するのですか?
概要
あなたは 読み取り/書き込みアクセスを許可する で外部 SD カードにアクセスすることができます。 異なるAPIレベル ( 実行時のAPI23+。 ).
KitKat 以降。 のパーミッションは は必要ありません。 となります。 必須 を指定します。
普遍的な方法です。
この 履歴 はないと言っています。 外部 SD カードに書き込むための普遍的な方法 と書いていますが、次のように続けます。
APIベースの方法です。
以前は キットカット を使用してみてください。 Doomsknight 方法 1、それ以外は方法 2。
パーミッションのリクエスト マニフェストで (Api < 23)と を実行時に (Api >= 23)となります。
おすすめの方法
ContextCompat.getExternalFilesDirs 解決方法 アクセスエラー を解決します。
安全な共有の仕方は コンテンツ プロバイダ または 新しいストレージアクセスフレームワーク .
プライバシーに配慮した方法。
Android Q Beta 4 時点で Android 9 (API レベル 28) 以下をターゲットとするアプリには、デフォルトで何の変更もありません。
Android Q を既定でターゲットにしている (またはオプトインしている) アプリには フィルタリングされたビュー を外部ストレージに保存します。
- 最初の回答です。
Android で外部 SD カードに書き込む汎用的な方法
このような 普遍的な方法 への Android で外部 SD カードに書き込む により 連続的な変更 :
-
KitKat以前: 公式Androidプラットフォームは、例外を除き、SDカードを全くサポートしていませんでした。
-
KitKat: アプリが SD カード上のアプリ固有のディレクトリのファイルにアクセスできるようにする API を導入しました。
-
Lollipop: アプリが他のプロバイダーが所有するフォルダーへのアクセスを要求できるようにする API を追加しました。
-
Nougat: 一般的な外部ストレージのディレクトリにアクセスするための簡略化された API を提供しました。
外部 SD カードへの読み取り/書き込みアクセスを許可するためのより良い方法は何ですか? 異なる API レベルで
ベースとなるのは Doomsknightの回答 と 私 と、そして デイブ・スミス と マーク・マーフィーのブログ記事 1 , 2 , 3 :
- 理想的には ストレージアクセス フレームワーク と ドキュメントファイル として Jared Rummler が指摘した . または
-
使用方法
アプリ固有のパス
/storage/extSdCard/Android/data/com.myapp.example/files
. - 追加 マニフェストへの読み取り/書き込み権限 をKitKat以前用に追加する。 は必要ありません。 というパスがあります。
- Appのパスと Doomsknight のメソッド を考えて KitKatとSamsungのケース .
- フィルタリングして使用 ストレージディレクトリの取得 を使用すると、KitKat より前の App のパスと読み取り/書き込みのパーミッションが表示されます。
- ContextCompat.getExternalFilesDirs をKitKat以降に追加しました。考慮事項 のデバイスで、内部を先に返すようにした。
- 回答を更新しました。
アップデート1 . Doomknightさんの回答にある方法1を試しましたが、効果がありませんでした。
お分かりのように、私は SD に書き込もうとする前に、実行時にパーミッションをチェックしています。 SD に書き込もうとする前に、実行時に権限をチェックしています。
私はアプリケーション固有のディレクトリを使用します
を使用して、この問題を回避します。
を使用し、更新された質問の
ContextCompat.getExternalFilesDirs()
を使って
getExternalFilesDir ドキュメント
を参照してください。
を改善し
ヒューリスティック
のような異なる api レベルに基づいてリムーバブルメディアを表すものを決定するために
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT
... しかし、アクセスエラーが発生します、2つのデバイスで試しました。HTC10 とShield K1の2つのデバイスで試してみました。
思い出してください Android 6.0 はポータブル ストレージ デバイスをサポートします。 をサポートし、サードパーティ製アプリは ストレージ アクセス フレームワーク . お使いのデバイス HTC10 および シールドK1 は、おそらくAPI 23です。
あなたのログには
パーミッション拒否例外
アクセス中
/mnt/media_rw
のように
この修正
のように、API 19+ 用に修正します。
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
<group gid="sdcard_rw" />
<group gid="media_rw" /> // this line is added via root in the link to fix it.
</permission>
私は試したことがないのでコードを共有することはできませんが、私なら
for
が返されるディレクトリに書き込もうとすると
残りのスペースに基づいて書き込むのに最適なストレージディレクトリを探します。
.
おそらく
Gizm0 の代替案
を
getStorageDirectories()
メソッドに置き換えることができ、良い出発点となります。
ContextCompat.getExternalFilesDirs
が解決する
問題を解決する
が必要ない場合は
他のフォルダーへのアクセス
.
- Android 1.0 ... KitKat より前のバージョン。
KitKat より前のバージョンでは Doomsknightの方法1 または この応答 をご覧ください。
public static HashSet<String> getExternalMounts() {
final HashSet<String> out = new HashSet<String>();
String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
String s = "";
try {
final Process process = new ProcessBuilder().command("mount")
.redirectErrorStream(true).start();
process.waitFor();
final InputStream is = process.getInputStream();
final byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
s = s + new String(buffer);
}
is.close();
} catch (final Exception e) {
e.printStackTrace();
}
// parse output
final String[] lines = s.split("\n");
for (String line : lines) {
if (!line.toLowerCase(Locale.US).contains("asec")) {
if (line.matches(reg)) {
String[] parts = line.split(" ");
for (String part : parts) {
if (part.startsWith("/"))
if (!part.toLowerCase(Locale.US).contains("vold"))
out.add(part);
}
}
}
}
return out;
}
次のコードを
AndroidManifest.xml
に追加し
外部ストレージへのアクセス権取得
外部ストレージへのアクセスは、Android のさまざまな権限によって保護されています。 のパーミッションによって保護されています。
Android 1.0から、書き込みアクセスは以下のように保護されています。 その
WRITE_EXTERNAL_STORAGE
許可 .Android 4.1 からは、読み取り アクセスは
READ_EXTERNAL_STORAGE
パーミッションで保護されます。外部ストレージにファイルを書き込むには、アプリは以下の権限を取得する必要があります。 取得する必要があります。 パーミッションを取得する必要があります。
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> </manifest>
両方必要な場合は のみを要求します。
WRITE_EXTERNAL_STORAGE
のみを許可する必要があります。
読む マーク・マーフィーの解説 と 推奨 ダイアン・ハックボーン と デイブ・スミス という投稿があります。
- Android 4.4 までは、Android ではリムーバブル メディアの公式サポートはありませんでしたが、KitKat からは FMW API で「プライマリ」および「セカンダリ」外部ストレージのコンセプトが登場しました。
- 以前のアプリは MediaStore インデックスに依存しており、ハードウェアに同梱されているか、マウント ポイントを調べて、何がリムーバブル メディアを表しているかを判断するためにいくつかのヒューリスティックを適用していました。
- Android 4.4 KitKat を導入 ストレージ アクセス フレームワーク (SAF) .
バグのため、次のノートは無視してください。
ContextCompat.getExternalFilesDirs()
:
- Android 4.2 以降、セキュリティ (マルチユーザー対応) のためにリムーバブル メディアをロックするようデバイス メーカーに Google から要求があり、4.4 で新しいテストが追加されました。
- KitKat 以降
getExternalFilesDirs()
などのメソッドが追加され、利用可能なすべてのストレージボリュームの使用可能なパスを返すようになりました (最初に返されるのはプライマリボリュームです)。 の項目はプライマリ ボリュームです)。
注意してください。 Android 4.4以降では、これらのパーミッションは必要ありません。 アプリのプライベートなファイルだけを読み書きしている場合は必要ありません。 詳しくは アプリ非公開ファイルの保存 を参照してください。 .
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> </manifest>
こちらもお読みください パオロ・ロヴェッリによる解説 を読んで、試しに Jeff Sharkey の解決策 を使用してみてください。
KitKat では、これらのセカンダリ共有ストレージ デバイスと対話するためのパブリック API が用意されています。 公開されています。
新しい
Context.getExternalFilesDirs()
とContext.getExternalCacheDirs()
メソッドは複数のパスを返すことができます。 を返します。その後 をチェックし
Environment.getStorageState()
とFile.getFreeSpace()
を参照して、ファイルを保存するのに最適な場所を決定してください。これらのメソッドは
ContextCompat
をサポート-v4 ライブラリで使用することができます。
Android 4.4 から、外部ストレージ デバイス上のファイルの所有者、グループ、およびモードは、ディレクトリに基づいて合成されるようになりました。 外部ストレージ デバイスのファイルの所有者、グループ、およびモードは、ディレクトリ構造に基づいて合成されるようになりました。 構造に基づいて合成されるようになりました。これにより、アプリは、外部ストレージ上のパッケージ固有のディレクトリを管理することができます。 ディレクトリを管理できるようになります。
WRITE_EXTERNAL_STORAGE
パーミッションを持つ必要がありません。例えば、アプリでパッケージ という名前com.example.foo
は自由にAndroid/data/com.example.foo/
に自由にアクセスできるようになりました。 に自由にアクセスできるようになります。これらの合成されたパーミッションは、FUSEデーモンで生のストレージデバイスをラップすることによって達成されます。 FUSE デーモンで生のストレージデバイスをラップすることによって達成されます。
KitKat では、root 化せずに完全なソリューションを提供できる可能性はほぼゼロです。 完全なソリューションである可能性はほぼゼロです。
Android プロジェクトは間違いなくここで失敗しています。 どのアプリも外部 SD カードへのフル アクセスを取得できません。
- ファイル マネージャー。 を使用すると、外部 SD カードの管理に使用できません。ほとんどの地域で ほとんどの地域で、読み込みだけで、書き込みはできません。
- メディアアプリです。 を使用することはできません。 メディアコレクションを再タグ付け/再整理することはできません。 は書き込むことができません。
- オフィス用アプリ。 ほとんど同じ
唯一の場所 3 rd パーティ製アプリが外部カードに書き込むことができるのは 外部カードに書き込むことができるのは、そのアプリ自身のディレクトリだけです (つまり
/sdcard/Android/data/<package_name_of_the_app>
).唯一の方法は 本当に修正するには、メーカー (中には修正したメーカーもあります。 P6 の Kitkat アップデートで Huawei が修正しました) - または root が必要です... (Izzy の説明はここに続きます)
- Android 5.0 では変更が導入され DocumentFile ヘルパークラスが導入されました。
getStorageState
API 19で追加、API 21で非推奨。
使用
getExternalStorageState(File)
素晴らしいチュートリアルがあります。 でのストレージ アクセス フレームワークとの対話のための素晴らしいチュートリアルです。 フレームワークを操作するための素晴らしいチュートリアルです。
Lollipopの新しいAPIとの対話 は非常によく似ています(Jeff Sharkey氏の説明)。 .
- Android 6.0 Marshmallow では、新しい ランタイムパーミッション モデルを導入しました。
リクエストの許可 を実行時に要求し、APIレベル23以上で読み取りが可能な場合 実行時にパーミッションを要求する
<ブロッククオートAndroid 6.0 (API レベル 23) 以降では、ユーザーはアプリのインストール時ではなく、アプリの実行中にパーミッションを付与します。 アプリをインストールしたときやアプリを更新したときではなく、アプリが実行されているときに、ユーザーはアプリに権限を付与します。
// Assume thisActivity is the current activity int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
Android 6.0では、新しい
ランタイムパーミッション
モデルを導入しました。
モデルを導入しました。新しいモデルには
には
READ/WRITE_EXTERNAL_STORAGE
パーミッションが含まれるため、プラットフォームは
ストレージへのアクセスを動的に許可する必要があります。
プラットフォームは、すでに実行されているアプリを停止または再起動することなく、ストレージへのアクセスを動的に許可する必要があります。このためには、マウントされたすべてのストレージ デバイスに対して、3 つの
マウントされたすべてのストレージ デバイスの 3 つの異なるビューを維持することでこれを行います。
- /mnt/runtime/default は、特別なストレージ権限を持っていないアプリに表示されます。 パーミッションのないアプリに表示されます...
- /mnt/runtime/read は、アプリケーションに表示されます。 read_external_storage
- /mnt/runtime/writeは、read_external_storageを持つアプリに表示されます。 write_external_storageを持つアプリに表示されます。
- Android 7.0 では、外部ストレージのディレクトリにアクセスするための簡略化された API が提供されます。
スコープ付きディレクトリアクセス Android 7.0 では、アプリは新しい API を使って、特定のディレクトリへのアクセスを要求できます。 外部ストレージ ディレクトリへのアクセスを要求できます。 ディレクトリへのアクセスを要求できます。
詳細については Scoped Directory Accessトレーニング .
マーク・マーフィーの投稿を読む スコープ付きディレクトリ アクセスの注意点 . Android Qで非推奨となった :
ノート 7.0 で追加されたスコープ付きディレクトリアクセスは、Android Q では非推奨です。 Android Qでは非推奨です。
具体的には
createAccessIntent()
メソッドで ストレージボリューム は は非推奨です。を追加したそうです。
createOpenDocumentTreeIntent()
として使用できる を追加しました。
- Android 8.0 Oreo ... Android Q Beta の変更点。
Androidで始まる O では ストレージアクセスフレームワークにより カスタムドキュメント プロバイダ を使用して、リモート データ ソースに存在するファイルに対してシーク可能なファイル ディスクリプタを作成することができます。 ファイル記述子を作成します。
アクセス許可 , Android O より前のバージョン アプリが実行時に権限を要求し、その権限が付与された場合、システムは同じ権限に属する残りの権限も誤って付与していました。 アプリが実行時にパーミッションを要求し、パーミッションが付与された場合、システムは同じ マニフェストに登録されている、同じパーミッション グループに属する残りのパーミッションも誤って付与していました。
Android Oをターゲットとするアプリの場合 は、この動作は修正されました。アプリは明示的に要求されたパーミッションのみを付与されます。 ただし、ユーザーがアプリにパーミッションを付与すると、そのパーミッショングループの以降のすべての ただし、ユーザーがアプリに許可を与えると、その許可グループに含まれる許可に対するその後のすべての要求が自動的に許可されます。 許可されます。
例えば
READ_EXTERNAL_STORAGE
とWRITE_EXTERNAL_STORAGE
...
更新しました。
Android Q の以前のベータ版リリースでは、一時的に
READ_EXTERNAL_STORAGE
と
WRITE_EXTERNAL_STORAGE
パーミッションに、よりきめ細かいメディア固有のパーミッションを追加しました。
注意してください。 Googleが導入した 役割 をベータ 1 で導入し、ベータ 2 の前にドキュメントから削除しました...。
注意してください。
以前のベータ版リリースで導入されたメディアコレクションに固有のパーミッションである
READ_MEDIA_IMAGES
,
READ_MEDIA_AUDIO
そして
READ_MEDIA_VIDEO
-
は現在では廃止されています
.
詳細はこちら。
Mark Murphy による Q Beta 4 (最終 API) のレビュー。 外付けストレージの死。佐賀の終わり (?)
<ブロッククオート死は生よりも普遍的である。誰もが死にますが、誰もが生きるわけではありません。 生きている。 アンドリュー・サックス
- 関連する質問と推奨される回答。
Android 4.0+ 用の外部 SD カード パスはどのように入手できますか。
mkdir()は内蔵フラッシュストレージ内では動作しますが、SDカード内では動作しませんか?
getExternalFilesDir と getExternalStorageDirectory() の違い。
getExternalFilesDirs() が一部のデバイスで動作しない理由は?
Android 5.0 (Lollipop) で発表された新しい SD カード アクセス API を使用する方法
SAF(Storage Access Framework)によるAndroid SDカードへの書き込み許可
- 関連するバグや問題。
バグ: Android 6 で、getExternalFilesDirs を使用するとき、その結果に新しいファイルを作成することができません。
Lollipop で getExternalCacheDir() が返すディレクトリへの書き込みは、書き込み権限がないと失敗します。
関連
-
Javaでよくある構文エラー
-
ブートレイヤーの初期化中にエラーが発生しました java.lang.module.FindException: モジュールが見つかりません
-
が 'X-Frame-Options' を 'sameorigin' に設定したため、フレーム内に存在する。
-
Zipファイルの圧縮・解凍にantを使用する
-
[解決済み] Androidのソフトキーボードをプログラムで閉じる/隠すにはどうすればよいですか?
-
[解決済み] Androidでアクティビティ起動時にEditTextにフォーカスが当たらないようにする方法
-
[解決済み] Java の配列を表示する最も簡単な方法は何ですか?
-
[解決済み] Androidの「コンテキスト」とは何ですか?
-
[解決済み] AndroidでPythonを実行する方法はありますか?
-
[解決済み】Android UserManager.isUserAGoat()の正しい使用例?)
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】ビットマップを保存する場所について
-
java.sql.SQLException: executeQuery()でデータ操作文を発行できません。
-
List list = new ArrayList(); Error: ArrayList は型に解決できません。
-
Java基礎編 - オブジェクト指向
-
Javaがリソースリークに遭遇した:'input'が閉じない 解決方法
-
代入の左辺は変数でなければならない 解答
-
ecplise プロンプトが表示されます。"選択したものは起動できません。" "最近の起動はありません。"
-
[解決済み】例外「open failed: Androidで「EACCES (Permission denied)」という例外が発生する。
-
[解決済み】リムーバブルSDカードの場所を検索する。
-
[解決済み] Android 4.0以降で外部SDカードのパスを取得するにはどうすればよいですか?