81 votes

Manière universelle d'écrire sur une carte SD externe sur Android

Dans mon application, j'ai besoin de stocker beaucoup d'images dans la mémoire de l'appareil. Ces fichiers ont tendance à remplir la mémoire de l'appareil, et je veux permettre aux utilisateurs d'être en mesure de choisir la carte SD externe comme dossier de destination.

J'ai lu partout que Android n'autorise pas les utilisateurs à écrire à la carte SD externe, par carte SD, je veux dire l'externe et le montage de la carte SD et pas de stockage externe, mais le dossier applications du gestionnaire de gérer à écrire à la SD Externe sur toutes les versions d'Android.

Quel est le meilleur moyen d'accorder l'accès en lecture/écriture de carte SD externe sur les différentes API niveaux (Pré-KitKat, KitKat, une Sucette+)?

Mise à jour 1

J'ai essayé la Méthode 1 de Doomknight réponse, en vain: Comme vous pouvez le voir, je vérifie les permissions lors de l'exécution avant de tenter d'écrire sur la 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();
    }
}

Mais j'obtiens une erreur d'accès, essayé sur deux appareils différents: HTC10 et le Bouclier 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

11voto

Doomsknight Points 5998

Je crois qu'il y a deux méthodes pour y parvenir:

MÉTHODE 1: (ne PAS travailler sur 6.0 et au-dessus, en raison de modifications des autorisations)

J'ai été en utilisant cette méthode depuis des années sur de nombreux appareils version sans problème. Le crédit est dû à la source d'origine, que ce n'était pas moi qui l'a écrit.

Il sera de retour tout monté médias (y compris les Cartes SD) dans une liste de chaînes emplacements de répertoire. Avec la liste, vous pouvez alors demander à l'utilisateur l'emplacement où enregistrer, etc.

Vous pouvez appeler avec les éléments suivants:

 HashSet<String> extDirs = getStorageDirectories();

Méthode:

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

MÉTHODE 2:

Utiliser la v4 bibliothèque de prise en charge

import android.support.v4.content.ContextCompat;

Il suffit d'appeler le suivant pour obtenir une liste d' File emplacements de stockage.

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

Les lieux diffèrent dans l'utilisation de la cependant que.

Retourne le chemin complet pour l'application des répertoires spécifiques de tous les les périphériques de stockage externes où l'application peut placer des fichiers persistant il possède. Ces fichiers sont internes à l'application, et généralement pas visible à l'utilisateur des médias.

Les périphériques de stockage externes rentré ici sont considérées comme une partie intégrante de la l'appareil, y compris les émules de stockage externe et de supports physiques fentes, tels que les cartes SD dans un compartiment de la batterie. Le retour de chemins ne pas inclure transitoire les appareils, tels que les lecteurs flash USB.

Une application peut stocker des données sur un ou tous les instruments retournés. Pour exemple, une application peut choisir de stocker de gros fichiers sur l'appareil avec le plus d'espace disponible

Plus d'Infos sur ContextCompat

Ils sont comme une application spécifique des fichiers. Cachés provenant d'autres applications.

3voto

Juste une autre réponse. Cette réponse ne montre 5.0+ parce que je crois Doomknight la réponse de posté ici est la meilleure façon de le faire pour Android 4.4 et ci-dessous.

C'est posté ici (Est-il un moyen pour obtenir la taille de la Carte SD sur Android?) par moi pour obtenir la Carte SD externe de taille sur Android 5.0+

Pour obtenir la carte SD Externe comme un 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;
}

Note: je ne reçois que la "première" carte sd externe mais vous pouvez le modifier et le renvoyer ArrayList<File> au lieu de File et de laisser continuer la boucle au lieu d'appeler break après la première se trouve.

3voto

En plus de toutes les autres belles réponses, je pourrais ajouter un peu plus à cette question, de sorte qu'il peut donner une plus large couverture pour les lecteurs. Dans ma réponse ici, je voudrais utiliser 2 dénombrable des ressources à présenter de Stockage Externe.

La première ressource est de Android de Programmation, Le Big Nerd Ranch Guide 2e édition, chapitre 16, page 294.

Le livre décrit la base et externe de fichier et de répertoire des méthodes. Je vais essayer de faire un résumé de ce qui pourrait être pertinente à votre question.

La partie suivante du livre:

De Stockage Externe

Votre photo a besoin de plus de place sur l'écran. Images pleine grandeur sont trop grandes pour les coller à l'intérieur d'un La base de données SQLite, encore moins un Intent. Ils auront besoin d'un endroit pour vivre dans votre système de fichiers. Normalement, vous devez mettre dans votre stockage privé. Rappelez-vous que vous utilisé votre stockage privé pour sauvegarder votre base de données SQLite. Avec des méthodes comme Context.getFileStreamPath(String)et Context.getFilesDir(), vous pouvez faire la même chose avec les fichiers ordinaires, trop (qui vivent dans une sous-dossier adjacentes aux bases de données sous-dossier de votre base de données SQLite vit en)

Base de fichier et de répertoire des méthodes dans le Contexte

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File etFilesDir()                                                                      |
| - 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|

Si vous stockez les fichiers que seul votre application doit utiliser, ces méthodes sont exactement ce que vous avez besoin d'.

D'autre part, si vous avez besoin d'une autre application pour écrire dans ces fichiers, vous êtes hors de la chance: alors que il y a un Context.MODE_WORLD_READABLE drapeau vous pouvez passer à l' openFileOutput(String, int), il est obsolète, et pas totalement fiable dans ses effets sur les appareils récents. Si vous stockez des fichiers à partager avec d'autres applications ou de recevoir des fichiers à partir d'autres applications (comme les fichiers des images enregistrées), vous devez les stocker sur de stockage externe à la place.

Il existe deux types de stockage externe: primaire, et tout le reste. Tous les appareils Android ont à moins un emplacement pour le stockage externe: l'emplacement principal, qui est situé dans le dossier renvoyé par Environment.getExternalStorageDirectory(). Cela peut être une carte SD, mais aujourd'hui il n'est plus souvent intégré dans l'appareil lui-même. Certains appareils peuvent avoir de stockage externe supplémentaire. Que relèveraient de la "tout le reste".

Contexte fournit quelques méthodes pour arriver au stockage externe, trop. Ces méthodes fournissent facile façons d'obtenir à votre primaire de stockage externe, et un peu-sorta-des moyens faciles à obtenir à tout le reste. Tous ces méthodes de stocker des fichiers dans des places disponibles, aussi, donc être prudent avec eux.

Externe de fichier et de répertoire des méthodes dans le Contexte

| 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.                                                                         |

Techniquement, les dossiers fournis ci-dessus peuvent ne pas être disponibles, comme certains périphériques utilisation d'une carte SD amovible pour le stockage externe. Dans la pratique, c'est rarement un problème, parce que presque tous les appareils modernes ont inamovible de stockage interne pour leur "externe" de stockage. Il n'est donc pas la peine d'aller à des moyens extrêmes pour en rendre compte. Mais nous n'avons recommandé d'inclure un code simple pour se prémunir contre la possibilité, ce que vous allez faire dans un instant.

De stockage externe de l'autorisation

En général, vous avez besoin d'une permission pour écrire ou lire de stockage externe. Les autorisations sont bien connus de la chaîne de valeurs que vous mettez dans votre manifeste, à l'aide de l' <uses-permission> balise. Ils racontent Android qui vous voulez faire quelque chose que Android veut vous demander la permission pour.

Ici, Android attend que vous pour demander la permission, car il veut appliquer une certaine responsabilité. Vous dites Android que vous avez besoin de l'accès de stockage externe, et Android va alors indiquer à l'utilisateur que c'est l'une des choses que votre demande n'est quand ils essaient de l'installer. De cette façon, personne n'est surpris quand vous commencez à épargner les choses à leur carte SD.

Dans Android 4.4, KitKat, ils desserrer cette restriction. Depuis Context.getExternalFilesDir(String) renvoie un dossier qui est spécifique à votre application, il est logique que vous voulez être en mesure de lire et d'écrire des fichiers qui y vivent. Donc sur Android 4.4 (API 19) et le haut, vous n'avez pas besoin de cette autorisation pour ce dossier. (Mais vous avez encore besoin pour d'autres types de stockage externe.)

Ajouter une ligne à votre manifeste qui demande la permission de lire de stockage externe, mais seulement jusqu'à l'API Liste De 16,5 Demandant de stockage externe de l'autorisation (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" />

Le maxSdkVersion attribut qu'il fait en sorte que votre application ne demande de cette autorisation sur les versions d'Android qui sont plus âgés que les API 19, Android KitKat. Notez que vous ne demandant qu'à lire de stockage externe. Il y a aussi un WRITE_EXTERNAL_STORAGE autorisation, mais vous n'en avez pas besoin. Vous ne serez pas écrit quoi que ce soit pour du stockage externe: L'application appareil photo ne sera que pour vous

La deuxième ressource est ce lien lire tout cela, mais vous pouvez aussi sauter à l'Aide de l'appareil de Stockage Externe de la section.

Référence:

De plus la lecture de trucs:

Avertissement: Cette information a été prise à partir de l'Android de Programmation: Le Big Nerd Ranch Guide avec l'autorisation de l'auteur. Pour plus d'informations sur ce livre ou pour acheter un exemplaire, veuillez visiter bignerdranch.com.

2voto

Václav Hodek Points 279

Ce sujet est un peu vieux, mais je cherchais une solution et après quelques recherches je suis venu avec le code ci-dessous pour récupérer la liste des "externes", les points de montage qui, selon mes connaissances, travaille sur de nombreux appareils différents.

Fondamentalement, il lit la disposition des points de montage, les filtres non valides, les tests le reste si ils sont accessibles et ajoute que si toutes les conditions sont remplies.

Bien sûr, les autorisations requises doit être accordée avant que le code est invoquée.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X