1. ホーム
  2. java

[解決済み] Androidで外部SDカードに書き込む汎用的な方法

2023-06-09 19:03:05

質問

私のアプリケーションでは、デバイスストレージに多くの画像を保存する必要があります。そのようなファイルはデバイス ストレージをいっぱいにする傾向があるので、ユーザーが外部 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 を既定でターゲットにしている (またはオプトインしている) アプリには フィルタリングされたビュー を外部ストレージに保存します。


  1. 最初の回答です。

Android で外部 SD カードに書き込む汎用的な方法

このような 普遍的な方法 への Android で外部 SD カードに書き込む により 連続的な変更 :

  • KitKat以前: 公式Androidプラットフォームは、例外を除き、SDカードを全くサポートしていませんでした。

  • KitKat: アプリが SD カード上のアプリ固有のディレクトリのファイルにアクセスできるようにする API を導入しました。

  • Lollipop: アプリが他のプロバイダーが所有するフォルダーへのアクセスを要求できるようにする API を追加しました。

  • Nougat: 一般的な外部ストレージのディレクトリにアクセスするための簡略化された API を提供しました。

  • ... Android Qのプライバシー変更。アプリをスコープとするストレージとメディアをスコープとするストレージ

<ブロッククオート

外部 SD カードへの読み取り/書き込みアクセスを許可するためのより良い方法は何ですか? 異なる API レベルで

ベースとなるのは Doomsknightの回答 と、そして デイブ・スミス マーク・マーフィーのブログ記事 1 , 2 , 3 :


  1. 回答を更新しました。

アップデート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 が解決する 問題を解決する が必要ない場合は 他のフォルダーへのアクセス .


  1. 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 インデックスに依存しており、ハードウェアに同梱されているか、マウント ポイントを調べて、何がリムーバブル メディアを表しているかを判断するためにいくつかのヒューリスティックを適用していました。

  1. Android 4.4 KitKat を導入 ストレージ アクセス フレームワーク (SAF) .

バグのため、次のノートは無視してください。 ContextCompat.getExternalFilesDirs() :

  • Android 4.2 以降、セキュリティ (マルチユーザー対応) のためにリムーバブル メディアをロックするようデバイス メーカーに Google から要求があり、4.4 で新しいテストが追加されました。
  • KitKat 以降 getExternalFilesDirs() などのメソッドが追加され、利用可能なすべてのストレージボリュームの使用可能なパスを返すようになりました (最初に返されるのはプライマリボリュームです)。 の項目はプライマリ ボリュームです)。
  • 以下の表は、開発者が何をしようとし、KitKat がどのように対応するかを示しています。

注意してください。 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 の説明はここに続きます)


  1. Android 5.0 では変更が導入され DocumentFile ヘルパークラスが導入されました。

getStorageState API 19で追加、API 21で非推奨。 使用 getExternalStorageState(File)

素晴らしいチュートリアルがあります。 でのストレージ アクセス フレームワークとの対話のための素晴らしいチュートリアルです。 フレームワークを操作するための素晴らしいチュートリアルです。

Lollipopの新しいAPIとの対話 は非常によく似ています(Jeff Sharkey氏の説明)。 .


  1. 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を持つアプリに表示されます。

  1. Android 7.0 では、外部ストレージのディレクトリにアクセスするための簡略化された API が提供されます。

スコープ付きディレクトリアクセス Android 7.0 では、アプリは新しい API を使って、特定のディレクトリへのアクセスを要求できます。 外部ストレージ ディレクトリへのアクセスを要求できます。 ディレクトリへのアクセスを要求できます。

詳細については Scoped Directory Accessトレーニング .

マーク・マーフィーの投稿を読む スコープ付きディレクトリ アクセスの注意点 . Android Qで非推奨となった :

ノート 7.0 で追加されたスコープ付きディレクトリアクセスは、Android Q では非推奨です。 Android Qでは非推奨です。

具体的には createAccessIntent() メソッドで ストレージボリューム は は非推奨です。

を追加したそうです。 createOpenDocumentTreeIntent() として使用できる を追加しました。


  1. Android 8.0 Oreo ... Android Q Beta の変更点。

Androidで始まる O では ストレージアクセスフレームワークにより カスタムドキュメント プロバイダ を使用して、リモート データ ソースに存在するファイルに対してシーク可能なファイル ディスクリプタを作成することができます。 ファイル記述子を作成します。

アクセス許可 , Android O より前のバージョン アプリが実行時に権限を要求し、その権限が付与された場合、システムは同じ権限に属する残りの権限も誤って付与していました。 アプリが実行時にパーミッションを要求し、パーミッションが付与された場合、システムは同じ マニフェストに登録されている、同じパーミッション グループに属する残りのパーミッションも誤って付与していました。

Android Oをターゲットとするアプリの場合 は、この動作は修正されました。アプリは明示的に要求されたパーミッションのみを付与されます。 ただし、ユーザーがアプリにパーミッションを付与すると、そのパーミッショングループの以降のすべての ただし、ユーザーがアプリに許可を与えると、その許可グループに含まれる許可に対するその後のすべての要求が自動的に許可されます。 許可されます。

例えば READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE ...

更新しました。 Android Q の以前のベータ版リリースでは、一時的に READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE パーミッションに、よりきめ細かいメディア固有のパーミッションを追加しました。

注意してください。 Googleが導入した 役割 をベータ 1 で導入し、ベータ 2 の前にドキュメントから削除しました...。

注意してください。

以前のベータ版リリースで導入されたメディアコレクションに固有のパーミッションである READ_MEDIA_IMAGES , READ_MEDIA_AUDIO そして READ_MEDIA_VIDEO - は現在では廃止されています . 詳細はこちら。

Mark Murphy による Q Beta 4 (最終 API) のレビュー。 外付けストレージの死。佐賀の終わり (?)

<ブロッククオート

死は生よりも普遍的である。誰もが死にますが、誰もが生きるわけではありません。 生きている。 アンドリュー・サックス


  1. 関連する質問と推奨される回答。

Android 4.0+ 用の外部 SD カード パスはどのように入手できますか。

mkdir()は内蔵フラッシュストレージ内では動作しますが、SDカード内では動作しませんか?

getExternalFilesDir と getExternalStorageDirectory() の違い。

getExternalFilesDirs() が一部のデバイスで動作しない理由は?

Android 5.0 (Lollipop) で発表された新しい SD カード アクセス API を使用する方法

Android 5.0以降で外部SDカードに書き込む場合

SAF(Storage Access Framework)によるAndroid SDカードへの書き込み許可

SAFFAQ ストレージアクセスフレームワーク FAQ


  1. 関連するバグや問題。

バグ: Android 6 で、getExternalFilesDirs を使用するとき、その結果に新しいファイルを作成することができません。

Lollipop で getExternalCacheDir() が返すディレクトリへの書き込みは、書き込み権限がないと失敗します。