Universelle Möglichkeit, auf eine externe SD-Karte unter Android zu schreiben

76

In meiner Anwendung muss ich viele Bilder im Gerätespeicher speichern. Solche Dateien erfüllen in der Regel den Gerätespeicher, und ich möchte Benutzern die Möglichkeit geben, eine externe SD-Karte als Zielordner auszuwählen.

Ich habe überall gelesen, dass Android es Benutzern nicht erlaubt, auf eine externe SD-Karte zu schreiben. Mit SD-Karte meine ich die externe und montierbare SD-Karte und nicht den externen Speicher , aber Dateimanager-Anwendungen können auf allen Android-Versionen auf externe SD schreiben.

Was ist der bessere Weg, um Lese- / Schreibzugriff auf externe SD-Karten auf verschiedenen API-Ebenen (Pre-KitKat, KitKat, Lollipop +) zu gewähren?

Update 1

Ich habe Methode 1 aus Doomknights Antwort ohne Erfolg ausprobiert: Wie Sie sehen, überprüfe ich zur Laufzeit die Berechtigungen, bevor ich versuche, auf SD zu schreiben:

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();
    }
}

Ich erhalte jedoch einen Zugriffsfehler, der auf zwei verschiedenen Geräten versucht wurde: HTC10 und Shield K1.

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
Vektor88
quelle
System-Apps können vollständig auf externen SD-Kartenspeicher zugreifen, andere Apps jedoch nur, wenn Betriebssystem und App über den Root-Zugriff
verfügen
4
@PavneetSingh das ist nicht wahr, alle Datei-Explorer-Anwendungen haben Zugriff auf externe SD-Karte, auch ohne root.
Vektor88
Über welchen Datei-Explorer sprechen Sie? weil einige berühmte, verwendet die Rooting-Skripte, um auf
SD-
1
Sie verwenden die Methode, die ich Ihnen gesagt habe. Zum Test haben Sie einfach ein Kitkat-Betriebssystem (ohne Root) und installieren ES. Wenn Sie versuchen, eine Datei daraus zu löschen, erhalten Sie eine Warnung (dies kann Ihr Telefon zu einem Baustein machen), in der Sie aufgefordert werden, root anzuwenden Prozess auf eigenes Risiko
Pavneet_Singh
1
Linie von der Verbindung würde ich empfehlen , dass Sie auf dieser Code nie verlassen , wie ich Ihre sagte App allein es nicht tun können , aber Medienanbieter ist eine System - App , so dass Sie es tun bieten ausnutzen können , um was Sie können
Pavneet_Singh

Antworten:

121

Zusammenfassung

Sie können Lese- / Schreibzugriff auf externe SD-Karten auf den verschiedenen API-Ebenen ( API23 + zur Laufzeit ) gewähren .

Da KitKat, sind Berechtigungen nicht erforderlich , wenn Sie app-spezifische Verzeichnisse verwenden, erforderlich anders.

Universeller Weg:

Die Geschichte besagt, dass es keine universelle Möglichkeit gibt, auf eine externe SD-Karte zu schreiben, sondern dass ...

Diese Tatsache wird durch diese Beispiele für externe Speicherkonfigurationen für Geräte demonstriert .

API-basierter Weg:

Vor KitKat versuchen zu verwenden Doomsknight Methode 1, Methode 2 anders.

Fordern Sie Berechtigungen im Manifest (Api <23) und zur Laufzeit (Api> = 23) an.

Empfohlener Weg:

ContextCompat.getExternalFilesDirs löst den Zugriffsfehler, wenn Sie keine Dateien freigeben müssen.

Die sichere Möglichkeit zur Freigabe besteht darin, einen Inhaltsanbieter oder das neue Storage Access Framework zu verwenden .

Datenschutzbewusster Weg:

Ab Android Q Beta 4 werden Apps, die auf Android 9 (API-Stufe 28) oder niedriger abzielen, standardmäßig nicht geändert.

Apps, die standardmäßig auf Android Q abzielen (oder sich dafür entscheiden), erhalten eine gefilterte Ansicht in den externen Speicher.


1. Erste Antwort.

Universelle Möglichkeit, auf eine externe SD-Karte unter Android zu schreiben

Aufgrund ständiger Änderungen gibt es unter Android keine universelle Möglichkeit , auf eine externe SD-Karte zu schreiben :

  • Pre-KitKat: Die offizielle Android-Plattform unterstützt SD-Karten bis auf Ausnahmen überhaupt nicht.

  • KitKat: Einführung von APIs, mit denen Apps auf Dateien in app-spezifischen Verzeichnissen auf SD-Karten zugreifen können.

  • Lollipop: APIs hinzugefügt, mit denen Apps den Zugriff auf Ordner anderer Anbieter anfordern können.

  • Nougat: Bereitstellung einer vereinfachten API für den Zugriff auf allgemeine externe Speicherverzeichnisse.

  • ... Änderung des Datenschutzes für Android Q: Speicher mit App- und Medienbereich

Was ist der bessere Weg, um Lese- / Schreibzugriff auf externe SD-Karten auf verschiedenen API-Ebenen zu gewähren?

Basierend auf der Antwort von Doomsknight und meiner sowie den Blog-Posts von Dave Smith und Mark Murphy: 1 , 2 , 3 :


2. Aktualisierte Antwort.

Update 1 . Ich habe Methode 1 aus Doomknights Antwort ausprobiert, ohne Erfolg:

Wie Sie sehen, überprüfe ich zur Laufzeit, ob Berechtigungen vorhanden sind, bevor ich versuche, auf SD zu schreiben ...

Ich würde anwendungsspezifische Verzeichnisse verwenden , um das Problem Ihrer aktualisierten Frage zu vermeiden und ContextCompat.getExternalFilesDirs()die Dokumentation von getExternalFilesDir als Referenz zu verwenden.

Verbessern Sie die Heuristik , um zu bestimmen, was Wechselmedien darstellt, basierend auf den verschiedenen API-Ebenen wieandroid.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

... Aber ich bekomme einen Zugriffsfehler, der auf zwei verschiedenen Geräten versucht wurde: HTC10 und Shield K1.

Denken Sie daran, dass Android 6.0 tragbare Speichergeräte unterstützt und Apps von Drittanbietern das Storage Access Framework durchlaufen müssen . Ihre Geräte HTC10 und Shield K1 sind wahrscheinlich API 23.

In Ihrem Protokoll wird eine Berechtigung angezeigt, der der Zugriff auf Ausnahmen verweigert wurde/mnt/media_rw , wie dieser Fix für 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>

Ich habe es nie versucht, damit ich keinen Code freigeben kann, aber ich würde den forVersuch vermeiden , in alle zurückgegebenen Verzeichnisse zu schreiben, und nach dem besten verfügbaren Speicherverzeichnis suchen , in das basierend auf dem verbleibenden Speicherplatz geschrieben werden kann .

Vielleicht ist die Alternative von Gizm0 zu Ihrer getStorageDirectories()Methode ein guter Ausgangspunkt.

ContextCompat.getExternalFilesDirsBehebt das Problem, wenn Sie keinen Zugriff auf andere Ordner benötigen .


3. Android 1.0 .. Pre-KitKat.

Versuchen Sie vor KitKat, Doomsknight-Methode 1 zu verwenden, oder lesen Sie diese Antwort von Gnathonic.

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;
}

Fügen Sie den nächsten Code zu Ihrem hinzu AndroidManifest.xmlund lesen Sie Zugriff auf externen Speicher erhalten

Der Zugriff auf externen Speicher wird durch verschiedene Android-Berechtigungen geschützt.

Ab Android 1.0 ist der Schreibzugriff mit der WRITE_EXTERNAL_STORAGEBerechtigung geschützt .

Ab Android 4.1 ist der Lesezugriff mit der READ_EXTERNAL_STORAGEBerechtigung geschützt .

Um ... Dateien auf den externen Speicher zu schreiben, muss Ihre App ... Systemberechtigungen erwerben:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest> 

Wenn Sie beides benötigen, müssen Sie nur die WRITE_EXTERNAL_STORAGEErlaubnis anfordern .

Lesen Sie die Erklärung von Mark Murphy und empfehlen Sie die Beiträge von Dianne Hackborn und Dave Smith

  • Bis Android 4.4 gab es in Android keine offizielle Unterstützung für Wechselmedien. Ab KitKat taucht in der FMW-API das Konzept des „primären“ und „sekundären“ externen Speichers auf.
  • Frühere Apps verlassen sich lediglich auf die MediaStore-Indizierung, werden mit der Hardware geliefert oder untersuchen Einhängepunkte und wenden einige Heuristiken an, um festzustellen, was Wechselmedien darstellt.

4. Android 4.4 KitKat führt das Storage Access Framework (SAF) ein .

Ignorieren Sie die nächste Notiz aufgrund von Fehlern, aber versuchen Sie Folgendes zu verwenden ContextCompat.getExternalFilesDirs():

  • Seit Android 4.2 haben Gerätehersteller Google aufgefordert, Wechselmedien aus Sicherheitsgründen zu sperren (Mehrbenutzerunterstützung), und in 4.4 wurden neue Tests hinzugefügt.
  • Da KitKat getExternalFilesDirs()und andere Methoden hinzugefügt wurden, um einen verwendbaren Pfad für alle verfügbaren Speichervolumes zurückzugeben (Das erste zurückgegebene Element ist das primäre Volume).
  • Die folgende Tabelle zeigt, was ein Entwickler möglicherweise versucht und wie KitKat reagiert: Geben Sie hier die Bildbeschreibung ein

Hinweis: Ab Android 4.4 sind diese Berechtigungen nicht erforderlich, wenn Sie nur Dateien lesen oder schreiben, die für Ihre App privat sind. Weitere Informationen finden Sie unter Speichern von Dateien, die app-privat sind .

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
</manifest>

Lesen Sie auch die Erklärung von Paolo Rovelli und versuchen Sie, Jeff Sharkeys Lösung seit KitKat zu verwenden:

In KitKat gibt es jetzt eine öffentliche API für die Interaktion mit diesen sekundären gemeinsam genutzten Speichergeräten.

Die Methoden new Context.getExternalFilesDirs()und Context.getExternalCacheDirs()können mehrere Pfade zurückgeben, einschließlich primärer und sekundärer Geräte.

Sie können sie dann durchlaufen und überprüfen Environment.getStorageState()und File.getFreeSpace()den besten Speicherort für Ihre Dateien ermitteln.

Diese Methoden sind auch ContextCompatin der support-v4-Bibliothek verfügbar .

Ab Android 4.4 werden Eigentümer, Gruppe und Modi von Dateien auf externen Speichergeräten jetzt basierend auf der Verzeichnisstruktur synthetisiert. Auf diese Weise können Apps ihre paketspezifischen Verzeichnisse auf einem externen Speicher verwalten, ohne dass sie über die allgemeine WRITE_EXTERNAL_STORAGEBerechtigung verfügen müssen. Beispielsweise kann die App mit dem Paketnamen com.example.foojetzt Android/data/com.example.foo/ohne Berechtigungen frei auf externe Speichergeräte zugreifen . Diese synthetisierten Berechtigungen werden erreicht, indem Raw-Speichergeräte in einen FUSE-Daemon eingeschlossen werden.

Mit KitKat sind Ihre Chancen auf eine "Komplettlösung" ohne Rooting so gut wie null:

Das Android-Projekt hat es hier definitiv vermasselt. Keine Apps erhalten vollen Zugriff auf externe SD-Karten:

  • Dateimanager: Sie können sie nicht zum Verwalten Ihrer externen SD-Karte verwenden. In den meisten Bereichen können sie nur lesen, aber nicht schreiben.
  • Medien-Apps: Sie können Ihre Mediensammlung nicht mehr neu markieren / organisieren, da diese Apps nicht darauf schreiben können.
  • Office-Apps: ziemlich gleich

Die einzige Ort , 3 rd Party - Anwendungen sind zu schreiben auf Ihrer externe Karte erlaubt sind „ ihre eigenen Verzeichnisse“ (dh /sdcard/Android/data/<package_name_of_the_app>).

Die einzigen Möglichkeiten, dies wirklich zu beheben, erfordern entweder den Hersteller (einige von ihnen haben es behoben, z. B. Huawei mit ihrem Kitkat-Update für das P6) - oder root ... (Izzys Erklärung wird hier fortgesetzt)


5. Android 5.0 führte Änderungen und die DocumentFile- Hilfsklasse ein.

getStorageStateIn API 19 hinzugefügt, in API 21 veraltet, verwendengetExternalStorageState(File)

Hier ist ein großartiges Tutorial für die Interaktion mit dem Storage Access Framework in KitKat.

Die Interaktion mit den neuen APIs in Lollipop ist sehr ähnlich (Jeff Sharkeys Erklärung) .


6. Android 6.0 Marshmallow führt ein neues Laufzeitberechtigungsmodell ein.

Fordern Sie zur Laufzeit Berechtigungen ab API-Stufe 23+ an und lesen Sie Anfordern von Berechtigungen zur Laufzeit

Ab Android 6.0 (API-Stufe 23) erteilen Benutzer Apps Berechtigungen, während die App ausgeführt wird, nicht, wenn sie die App installieren ... oder die App aktualisieren ... Benutzer können die Berechtigungen widerrufen.

// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_EXTERNAL_STORAGE);

Android 6.0 führt ein neues Laufzeitberechtigungsmodell ein , bei dem Apps bei Bedarf zur Laufzeit Funktionen anfordern. Da das neue Modell die READ/WRITE_EXTERNAL_STORAGEBerechtigungen enthält, muss die Plattform dynamisch Speicherzugriff gewähren, ohne bereits ausgeführte Apps zu beenden oder neu zu starten. Dazu werden drei unterschiedliche Ansichten aller bereitgestellten Speichergeräte beibehalten:

  • / mnt / runtime / default wird Apps ohne spezielle Speicherberechtigungen angezeigt ...
  • / mnt / runtime / read wird Apps mit READ_EXTERNAL_STORAGE angezeigt
  • / mnt / runtime / write wird Apps mit WRITE_EXTERNAL_STORAGE angezeigt

7. Android 7.0 bietet eine vereinfachte API für den Zugriff auf externe Speicherverzeichnisse.

Zugriff auf Bereichsverzeichnisse In Android 7.0 können Apps mithilfe neuer APIs den Zugriff auf bestimmte externe Speicherverzeichnisse anfordern , einschließlich Verzeichnisse auf Wechselmedien wie SD-Karten ...

Weitere Informationen finden Sie in der Schulung zum Scoped Directory Access .

Lesen Sie die Beiträge von Mark Murphy: Seien Sie vorsichtig mit dem Zugriff auf das Bereichsverzeichnis . Es wurde in Android Q veraltet :

Beachten Sie, dass der in 7.0 hinzugefügte Verzeichniszugriff mit Gültigkeitsbereich in Android Q veraltet ist.

Insbesondere ist die createAccessIntent()Methode in StorageVolume veraltet.

Sie fügten ein hinzu createOpenDocumentTreeIntent(), das als Alternative verwendet werden kann.


8. Android 8.0 Oreo .. Änderungen an Android Q Beta.

Ab Android O können Anbieter von benutzerdefinierten Dokumenten mit dem Storage Access Framework durchsuchbare Dateideskriptoren für Dateien erstellen, die sich in einer entfernten Datenquelle befinden ...

Berechtigungen , vor dem Android O , wenn eine App angefordert wurde eine Genehmigung zur Laufzeit und die Erlaubnis erteilt, das System auch die App des Rest der Berechtigungen nicht ordnungsgemäß gewährt, die zur gleichen Berechtigungsgruppe gehörten, und die in dem Manifest registriert wurden.

Bei Apps für Android O wurde dieses Verhalten korrigiert. Der App werden nur die Berechtigungen erteilt, die sie ausdrücklich angefordert hat. Sobald der Benutzer der App jedoch eine Berechtigung erteilt, werden alle nachfolgenden Berechtigungsanforderungen in dieser Berechtigungsgruppe automatisch erteilt.

Zum Beispiel READ_EXTERNAL_STORAGEund WRITE_EXTERNAL_STORAGE...

Update: Eine frühere Beta-Version von Android Q hat die Berechtigungen READ_EXTERNAL_STORAGEund vorübergehend durch WRITE_EXTERNAL_STORAGEdetailliertere, medienspezifische Berechtigungen ersetzt.

Hinweis: Google hat Rollen in Beta 1 eingeführt und diese vor Beta 2 aus der Dokumentation entfernt ...

Hinweis: Die Berechtigungen für Mediensammlungen, die in früheren Betaversionen READ_MEDIA_IMAGES- READ_MEDIA_AUDIO, und READ_MEDIA_VIDEO- eingeführt wurden, sind jetzt veraltet . Mehr Info:

Q Beta 4 (endgültige APIs) von Mark Murphy: Der Tod des externen Speichers: Das Ende der Saga (?)

"Der Tod ist universeller als das Leben. Jeder stirbt, aber nicht jeder lebt." - Andrew Sachs


9. Verwandte Fragen und empfohlene Antworten.

Wie kann ich einen externen SD-Kartenpfad für Android 4.0+ erhalten?

mkdir () funktioniert im internen Flash-Speicher, aber nicht auf der SD-Karte?

Unterschied zwischen getExternalFilesDir und getExternalStorageDirectory ()

Warum funktioniert getExternalFilesDirs () auf einigen Geräten nicht?

Verwendung der neuen SD-Kartenzugriffs-API für Android 5.0 (Lollipop)

Schreiben auf eine externe SD-Karte in Android 5.0 und höher

Schreibberechtigung für Android SD-Karten mit SAF (Storage Access Framework)

SAFFAQ: Die häufig gestellten Fragen zum Storage Access Framework


10. Verwandte Fehler und Probleme.

Fehler: Unter Android 6 können Sie bei Verwendung von getExternalFilesDirs keine neuen Dateien in den Ergebnissen erstellen

Das Schreiben in das von getExternalCacheDir () auf Lollipop zurückgegebene Verzeichnis schlägt ohne Schreibberechtigung fehl

Albodelu
quelle
2
Eine sehr eingehende Analyse. +1. Obwohl ich beachten muss, habe ich diese Methode nicht geschrieben. Es ist aus einer anderen Quelle vor ein paar Jahren :) Hoffe, es hilft ihm.
IAmGroot
Vielen Dank für die vollständige Antwort. Mir scheint, dass das Problem hier darin besteht, dass ich SAF verwenden sollte, um in das Stammverzeichnis meiner SD-Karte zu schreiben, anstatt die @ Doomsknight-Methode 1 zu verwenden, da dies auf höheren API-Ebenen nicht funktioniert, während ich es unterhalb von KitKat verwenden kann.
Vektor88
Np, wenn Sie außerhalb Ihres App-Verzeichnisses schreiben müssen, scheint der richtige Weg zu sein, die offizielle API zu verwenden . Scheint hier ein ähnliches Problem gelöst zu haben, ich werde es an einem anderen Tag überprüfen.
Albodelu
Wie erstellt der ES-Datei-Explorer in 4.4.2-Telefonen Ordner? @ Albodelu Können Sie bitte erklären
karanatwal.github.io
In Punkt 3 gibt es einen Link, in dem erklärt wird, wie dies für gerootete Geräte gemacht wird. Diese App hat eine Option, um die in kitKat hinzugefügte Einschränkung zu deaktivieren: Tool> Root Explorer> Mount R / W
Albodelu
10

Ich glaube, es gibt zwei Methoden, um dies zu erreichen:

METHODE 1: (funktioniert aufgrund von Berechtigungsänderungen NICHT unter 6.0 und höher)

Ich verwende diese Methode seit Jahren auf vielen Geräteversionen ohne Probleme. Der Kredit ist auf die ursprüngliche Quelle zurückzuführen, da nicht ich es geschrieben habe.

Es wird wieder alle montierten Medien (einschließlich Echt SD - Karten) in einer Liste von Strings Verzeichnissen. Mit der Liste können Sie den Benutzer dann fragen, wo er speichern soll usw.

Sie können es wie folgt aufrufen:

 HashSet<String> extDirs = getStorageDirectories();

Methode:

/**
 * Returns all the possible SDCard directories
 */
public static HashSet<String> getStorageDirectories() {
    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().contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase().contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

Methode 2:

Verwenden Sie die v4-Unterstützungsbibliothek

import android.support.v4.content.ContextCompat;

Rufen Sie einfach Folgendes an, um eine Liste Fileder Speicherorte zu erhalten.

 File[] list = ContextCompat.getExternalFilesDirs(myContext, null);

Die Standorte unterscheiden sich jedoch in der Nutzung.

Gibt absolute Pfade zu anwendungsspezifischen Verzeichnissen auf allen externen Speichergeräten zurück, auf denen die Anwendung persistente Dateien ablegen kann, deren Eigentümer sie ist. Diese Dateien sind anwendungsintern und für den Benutzer normalerweise nicht als Medium sichtbar.

Hier zurückgegebene externe Speichergeräte werden als fester Bestandteil des Geräts betrachtet, einschließlich emulierter externer Speicher und physischer Mediensteckplätze, z. B. SD-Karten in einem Batteriefach. Die zurückgegebenen Pfade enthalten keine vorübergehenden Geräte wie USB-Sticks.

Eine Anwendung kann Daten auf einem oder allen zurückgegebenen Geräten speichern. Beispielsweise kann eine App große Dateien auf dem Gerät mit dem meisten verfügbaren Speicherplatz speichern

Weitere Informationen zu ContextCompat

Sie sind wie app-spezifische Dateien. Versteckt vor anderen Apps.

IAmGroot
quelle
1
Bitte teilen Sie einen Link, über den Sie den Code in Methode 1 erhalten haben. Gutschrift, wo er fällig ist. Vielen Dank.
Eugen Pechanec
2
@ EugenPechanec Ich stimme vollkommen zu. Es ist jedoch Jahre her, dass ich diesen Code aus einer fantastischen Quelle bezogen habe. Ich werde es suchen
IAmGroot
1
Mir ist nicht klar, was ich mit diesen Pfaden tun soll: Wenn das Gerät nicht gerootet ist, erhalte ich keinen Schreibzugriff auf die SD-Karte, indem ich ihren absoluten Pfad angebe. Liege ich falsch? Vielleicht kann ich einige Daten in einem dedizierten App-Ordner in /external_sd/data/data/com.myapp ... speichern, aber ich kann nicht in / external_sd / MyApp / Images
Vektor88
1
@ vektor88 Sie können problemlos darauf zugreifen, ohne zu rooten. Ich und meine Kunden verwenden niemals gerootete Geräte. Welchen Fehler bekommen Sie? Haben Sie die Berechtigung hinzugefügt? Und wie unten angegeben, zielen Sie auf die neueste Version ab und fordern zur Laufzeit die Erlaubnis an. Ändern Sie das Ziel in 20 und prüfen Sie, ob es funktioniert, bevor Sie die Laufzeitberechtigungsanforderung lösen. Andernfalls bitte Fehler angeben.
IAmGroot
1
@ Vektor88 Dieses Konzept dient lediglich als Test, um festzustellen, ob Sie jetzt Berechtigungsprobleme haben. Wenn Sie auf API 23 abzielen, wird die Frage gestellt, ob Sie zur Laufzeit Berechtigungen angefordert haben. Dies ist zwar ein anderes Problem als der Wunsch, den SDCard-Speicherort zum Speichern zu erhalten (wie ursprünglich in Frage gestellt). developer.android.com/training/permissions/requesting.html
IAmGroot
3

Nur eine andere Antwort. Diese Antwort zeigt nur 5.0+, da ich glaube, dass die hier veröffentlichte Antwort von Doomknight der beste Weg für Android 4.4 und niedriger ist.

Dies wurde ursprünglich hier ( Gibt es eine Möglichkeit, die SD-Kartengröße in Android zu ermitteln? ) Von mir veröffentlicht, um die Größe der externen SD-Karte in Android 5.0+ zu ermitteln

So erhalten Sie die externe SD-Karte als File:

public File getExternalSdCard() {
    File externalStorage = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        File storage = new File("/storage");

        if(storage.exists()) {
            File[] files = storage.listFiles();

            for (File file : files) {
                if (file.exists()) {
                    try {
                        if (Environment.isExternalStorageRemovable(file)) {
                            externalStorage = file;
                            break;
                        }
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                }
            }
        }
    } else {
        // do one of many old methods
        // I believe Doomsknight's method is the best option here
    }

    return externalStorage;
}

Hinweis: Ich erhalte nur die "erste" externe SD-Karte. Sie können sie jedoch ändern und ArrayList<File>stattdessen zurückkehren Fileund die Schleife fortsetzen, anstatt zu rufen, breaknachdem die erste gefunden wurde.

ᴛʜᴇᴘᴀᴛᴇʟ
quelle
Danke @ th3pat3l isExternalStorageRemovableReferenz developer.android.com/reference/android/os/…
albodelu
1
Dies funktioniert nur, um auf das externe SD-Karten-Unterverzeichnis zuzugreifen, das auf Ihrer Anwendung basiert: z. B. auf meinem LG-Telefon: /storage/0403-0201/Android/data/com.your.application/files - wenn Sie hinzufügen möchten eine Datei in das DCIM-Verzeichnis - auch wenn Sie Berechtigungen in Ihrem Manifest haben und diese zur Laufzeit anfordern und in Ihre Anwendung gehen -> Berechtigungen unter Einrichten und Speicher aktivieren SIE ERHALTEN NOCH ENOENT (Keine solche Datei oder kein solches Verzeichnis ) Fehler, wenn Sie versuchen, dort eine Datei zu erstellen und zu öffnen. Zumindest passiert das auf meinem LG Aristo SDK 23. Das einzige, was ich sehe, dass es funktioniert, ist SAF.
MarkJoel60
isExternalStorageRemovableIst es nur für> = 21 möglich, festzustellen, ob der Speicher auf <= 19 API-Geräten entfernbar ist (Microsd-Karte), und eine ähnliche Methode zu implementieren?
user924
3

Zusätzlich zu allen anderen netten Antworten könnte ich dieser Frage etwas mehr hinzufügen, damit sie den Lesern eine breitere Berichterstattung bietet. In meiner Antwort hier würde ich 2 zählbare Ressourcen verwenden, um externen Speicher darzustellen.

Die erste Ressource stammt aus der Android-Programmierung, 2. Ausgabe des Big Nerd Ranch Guide , Kapitel 16, Seite 294.

Das Buch beschreibt die grundlegenden und externen Datei- und Verzeichnismethoden. Ich werde versuchen, einen Lebenslauf darüber zu erstellen, was für Ihre Frage relevant sein könnte.

Der folgende Teil aus dem Buch:

Externer Speicher

Ihr Foto benötigt mehr als einen Platz auf dem Bildschirm. Bilder in voller Größe sind zu groß, um in einer SQLite-Datenbank gespeichert zu werden, geschweige denn Intent. Sie benötigen einen Platz im Dateisystem Ihres Geräts. Normalerweise würden Sie sie in Ihrem privaten Speicher ablegen. Denken Sie daran, dass Sie Ihren privaten Speicher zum Speichern Ihrer SQLite-Datenbank verwendet haben. Mit Methoden wie Context.getFileStreamPath(String)und Context.getFilesDir()können Sie dasselbe auch mit regulären Dateien tun (die sich in einem Unterordner neben dem Datenbankunterordner befinden, in dem sich Ihre SQLite-Datenbank befindet).

Grundlegende Datei- und Verzeichnismethoden im Kontext

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - Returns a handle to the directory for private application files.                    |
|                                                                                       |
|FileInputStream openFileInput(String name)                                             |
| - Opens an existing file for input (relative to the files directory).                 |
|                                                                                       |
|FileOutputStream openFileOutput(String name, int mode)                                 |
| - Opens a file for output, possibly creating it (relative to the files directory).    |
|                                                                                       |
|File getDir(String name, int mode)                                                     |
| - Gets (and possibly creates) a subdirectory within the files directory.              |
|                                                                                       |
|String[] fileList()                                                                    |
| - Gets a list of file names in the main files directory, such as for use with         |
|   openFileInput(String).                                                              |
|                                                                                       |
|File getCacheDir()                                                                     |
| - Returns a handle to a directory you can use specifically for storing cache files.   |
|   You should take care to keep this directory tidy and use as little space as possible|

Wenn Sie Dateien speichern, die nur Ihre aktuelle Anwendung verwenden muss, sind diese Methoden genau das, was Sie benötigen.

Wenn Sie jedoch eine andere Anwendung zum Schreiben in diese Dateien benötigen, haben Sie kein Glück: Obwohl es ein Context.MODE_WORLD_READABLEFlag gibt, an das Sie übergeben können openFileOutput(String, int), ist es veraltet und in seinen Auswirkungen auf neuere Geräte nicht vollständig zuverlässig. Wenn Sie Dateien speichern, um sie mit anderen Apps zu teilen, oder Dateien von anderen Apps empfangen (Dateien wie gespeicherte Bilder), müssen Sie sie stattdessen auf einem externen Speicher speichern.

Es gibt zwei Arten von externem Speicher: Primärspeicher und alles andere. Alle Android-Geräte verfügen über mindestens einen Speicherort für externen Speicher: den primären Speicherort, der sich in dem von zurückgegebenen Ordner befindet Environment.getExternalStorageDirectory(). Dies mag eine SD-Karte sein, aber heutzutage wird sie häufiger in das Gerät selbst integriert. Einige Geräte verfügen möglicherweise über zusätzlichen externen Speicher. Das würde unter "alles andere" fallen.

Der Kontext bietet auch einige Methoden, um auf externen Speicher zuzugreifen. Diese Methoden bieten einfache Möglichkeiten, um an Ihren primären externen Speicher zu gelangen, und irgendwie einfache Möglichkeiten, um an alles andere zu gelangen. Alle diese Methoden speichern Dateien auch an öffentlich zugänglichen Orten. Seien Sie also vorsichtig mit ihnen.

Externe Datei- und Verzeichnismethoden im Kontext

| Method                                                                                |
| --------------------------------------------------------------------------------------|
|File getExternalCacheDir()                                                             |
| - Returns a handle to a cache folder in primary external storage. Treat it like you do|
|   getCacheDir(), except a little more carefully. Android is even less likely to clean |
|   up this folder than the private storage one.                                        |
|                                                                                       |
|File[] getExternalCacheDirs()                                                          |
| - Returns cache folders for multiple external storage locations.                      |
|                                                                                       |
|File getExternalFilesDir(String)                                                       |
| - Returns a handle to a folder on primary external storage in which to store regular  |
|   files. If you pass in a type String, you can access a specific subfolder dedicated  |
|   to a particular type of content. Type constants are defined in Environment, where   |
|   they are prefixed with DIRECTORY_.                                                  |
|   For example, pictures go in Environment.DIRECTORY_PICTURES.                         |
|                                                                                       |
|File[] getExternalFilesDirs(String)                                                    |
| - Same as getExternalFilesDir(String), but returns all possible file folders for the  |
|   given type.                                                                         |
|                                                                                       |
|File[] getExternalMediaDirs()                                                          |
| - Returns handles to all the external folders Android makes available for storing     |
|   media – pictures, movies, and music. What makes this different from calling         |
|   getExternalFilesDir(Environment.DIRECTORY_PICTURES) is that the media scanner       |
|   automatically scans this folder. The media scanner makes files available to         |
|   applications that play music, or browse movies and photos, so anything that you     |
|   put in a folder returned by getExternalMediaDirs() will automatically appear in     |
|   those apps.                                                                         |

Technisch gesehen sind die oben angegebenen externen Ordner möglicherweise nicht verfügbar, da einige Geräte eine austauschbare SD-Karte für die externe Speicherung verwenden. In der Praxis ist dies selten ein Problem, da fast alle modernen Geräte einen nicht entfernbaren internen Speicher für ihren „externen“ Speicher haben. Es lohnt sich also nicht, extreme Anstrengungen zu unternehmen, um dies zu erklären. Wir empfehlen jedoch, einfachen Code einzuschließen, um die Möglichkeit zu vermeiden, die Sie in Kürze ausführen werden.

Externe Speicherberechtigung

Im Allgemeinen benötigen Sie eine Berechtigung zum Schreiben oder Lesen aus einem externen Speicher. Berechtigungen sind bekannte Zeichenfolgenwerte, die Sie mithilfe des <uses-permission>Tags in Ihr Manifest einfügen. Sie teilen Android mit, dass Sie etwas tun möchten, für das Android Sie um Erlaubnis bitten soll.

Hier erwartet Android, dass Sie um Erlaubnis bitten, weil es eine gewisse Rechenschaftspflicht erzwingen möchte. Sie teilen Android mit, dass Sie auf externen Speicher zugreifen müssen, und Android teilt dem Benutzer dann mit, dass dies eines der Dinge ist, die Ihre Anwendung tut, wenn sie versucht, es zu installieren. Auf diese Weise ist niemand überrascht, wenn Sie anfangen, Dinge auf seiner SD-Karte zu speichern.

In Android 4.4, KitKat, wurde diese Einschränkung gelockert. Da Context.getExternalFilesDir(String)ein Ordner zurückgegeben wird, der für Ihre App spezifisch ist, ist es sinnvoll, dass Sie in der Lage sein möchten, dort lebende Dateien zu lesen und zu schreiben. Unter Android 4.4 (API 19) und höher benötigen Sie diese Berechtigung für diesen Ordner nicht. (Aber Sie brauchen es immer noch für andere Arten von externem Speicher.)

Fügen Sie Ihrem Manifest eine Zeile hinzu, die die Berechtigung zum Lesen des externen Speichers anfordert, jedoch nur bis zu API Listing 16.5: Anfordern der Berechtigung zum externen Speicher ( AndroidManifest.xml)

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.bignerdranch.android.criminalintent" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="18" />

Das Attribut maxSdkVersion bewirkt, dass Ihre App diese Berechtigung nur für Versionen von Android anfordert, die älter als API 19, Android KitKat, sind. Beachten Sie, dass Sie nur den externen Speicher lesen möchten. Es gibt auch eine WRITE_EXTERNAL_STORAGEErlaubnis, aber Sie brauchen sie nicht. Sie werden nichts in den externen Speicher schreiben: Die Kamera-App erledigt das für Sie

Die zweite Ressource ist, dass dieser Link alles liest. Sie können jedoch auch zum Abschnitt Verwenden des externen Speichers springen .

Referenz:

Weitere Lektüre:

Haftungsausschluss: Diese Informationen stammen aus der Android-Programmierung: The Big Nerd Ranch Guide mit Genehmigung der Autoren. Für weitere Informationen zu diesem Buch oder um eine Kopie zu erwerben, besuchen Sie bitte bignerdranch.com.

Maytham-ɯɐɥʇʎɐɯ
quelle
0

Dieses Thema ist etwas alt, aber ich suchte nach einer Lösung und kam nach einigen Recherchen mit dem folgenden Code, um eine Liste der verfügbaren "externen" Einhängepunkte abzurufen, die meines Wissens auf vielen verschiedenen Geräten funktionieren.

Grundsätzlich liest es verfügbare Einhängepunkte, filtert ungültige heraus, testet den Rest, wenn sie zugänglich sind, und fügt sie hinzu, wenn alle Bedingungen erfüllt sind.

Natürlich müssen die erforderlichen Berechtigungen erteilt werden, bevor der Code aufgerufen wird.

// Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. 

private List<FileSystemDevice> getDevices() {

    List<FileSystemDevice> devices = new ArrayList<>();

    // Add default external storage if available.
    File sdCardFromSystem = null;
    switch(Environment.getExternalStorageState()) {
        case Environment.MEDIA_MOUNTED:
        case Environment.MEDIA_MOUNTED_READ_ONLY:
        case Environment.MEDIA_SHARED:
            sdCardFromSystem = Environment.getExternalStorageDirectory();
            break;
    }

    if (sdCardFromSystem != null) {
        devices.add(new FileSystemDevice(sdCardFromSystem));
    }

    // Read /proc/mounts and add all mount points that are available
    // and are not "special". Also, check if the default external storage
    // is not contained inside the mount point. 
    try {
        FileInputStream fs = new FileInputStream("/proc/mounts");
        String mounts = IOUtils.toString(fs, "UTF-8");
        for(String line : mounts.split("\n")) {
            String[] parts = line.split(" ");

            // parts[0] - mount type
            // parts[1] - mount point
            if (parts.length > 1) {
                try {

                    // Skip "special" mount points and mount points that can be accessed
                    // directly by Android's functions. 
                    if (parts[0].equals("proc")) { continue; }
                    if (parts[0].equals("rootfs")) { continue; }
                    if (parts[0].equals("devpts")) { continue; }
                    if (parts[0].equals("none")) { continue; }
                    if (parts[0].equals("sysfs")) { continue; }
                    if (parts[0].equals("selinuxfs")) { continue; }
                    if (parts[0].equals("debugfs")) { continue; }
                    if (parts[0].equals("tmpfs")) { continue; }
                    if (parts[1].equals(Environment.getRootDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getDataDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { continue; }

                    // Verify that the mount point is accessible by listing its content. 
                    File file = new File(parts[1]);
                    if (file.listFiles() != null) {
                        try {

                            // Get canonical path for case it's just symlink to another mount point.
                            String devPath = file.getCanonicalPath();

                            for(FileSystemDevice device : devices) {

                                if (!devices.contains(devPath)) {                        
                                    devices.add(new FileSystemDevice(new File(devPath)));
                                }

                            }
                        } catch (Exception e) {
                            // Silently skip the exception as it can only occur if the mount point is not valid. 
                            e.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    // Silently skip the exception as it can only occur if the mount point is not valid. 
                    e.printStackTrace();
                }
            }
        }

        fs.close();
    } catch (FileNotFoundException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. 
        // Possibly, another detection method can be called here.
        e.printStackTrace();
    } catch (IOException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable.
        // Possibly, another detection method can be called here.
        e.printStackTrace();            
    }

    return devices;
}
Václav Hodek
quelle
-1

Hier ist eine Möglichkeit, eine neue Datei im externen Speicher zu erstellen (SDCard, falls im Gerät vorhanden, oder externer Speicher des Geräts, falls nicht). Ersetzen Sie einfach "Ordnername" durch den Namen Ihres gewünschten Zielordners und "Dateiname" durch den Namen der Datei, die Sie speichern. Natürlich können Sie hier sehen, wie Sie eine generische Datei speichern. Jetzt können Sie suchen, wie Sie Bilder speichern können, vielleicht hier oder was auch immer in einer Datei.

try {
            File dir =  new File(Environment.getExternalStorageDirectory() + "/foldername/");
            if (!dir.exists()){
                dir.mkdirs();
            }
            File sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileName );
            int num = 1;
            String fileNameAux = fileName;
            while (sdCardFile.exists()){
                fileNameAux = fileName+"_"+num;
                sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileNameAux);
                num++;
            }

Dies steuert auch, dass eine Datei vorhanden ist, und fügt am Ende des Namens der neuen Datei eine Nummer hinzu, um sie zu speichern.

Ich hoffe es hilft!

EDIT: Entschuldigung, ich habe vergessen, dass Sie <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />in Ihrem Manifest nachfragen müssen (oder programmgesteuert, wenn Sie es vorziehen, von Marshmallow)

Hugo
quelle
-3

Für Versionen unter Marshmallow können Sie die Berechtigungen direkt im Manifest angeben.

Bei Geräten mit Marshmallow und höher müssen Sie jedoch die Berechtigungen zur Laufzeit erteilen.

Durch die Nutzung

Environment.getExternalStorageDirectory();

Sie können direkt auf die externe SD-Karte (gemountete) zugreifen. Ich hoffe, dies hilft.

Geet Choubey
quelle
Environment.getExternalStorageDirectory();gibt Ihnen den Pfad zur geräteinternen SD-Karte.
Vektor88
Ab Android Lollipop haben Sie zwei Möglichkeiten: Externe SD-Karte als internen Speicher oder wie eine typische SD-Karte zu verwenden, hängt auch von dieser Auswahl ab.
Geet Choubey
Versuche dies. stackoverflow.com/questions/15744064/…
Geet Choubey
2
Dieser Code gibt mir den Pfad an, auf dem die externe SD-Karte montiert ist. Was dann? Ich kann immer noch nicht darauf schreiben.
Vektor88
Welchen Fehler erhalten Sie, wenn Sie versuchen, auf eine externe SD-Karte zu schreiben?
Geet Choubey