Encontre a localização de um cartão SD removível

204

Existe uma maneira universal de encontrar a localização de um cartão SD externo?

Por favor, não confunda com armazenamento externo .

Environment.getExternalStorageState()retorna o caminho para o ponto de montagem SD interno, como "/ mnt / sdcard". Mas a questão é sobre o SD externo. Como obtenho um caminho como "/ mnt / sdcard / external_sd" (pode variar de dispositivo para dispositivo)?

Acho que vou terminar com a filtragem da saída do mountcomando pelo nome do sistema de arquivos. Mas não tenho certeza se esse caminho é suficientemente robusto.

borisstr
fonte
Aqui está a minha solução que funciona até o Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC
'Environment.getExternalStorageState () retorna o caminho para o ponto de montagem SD interno como "/ mnt / sdcard".' Bem, não é interno no sentido em que o Android usa o termo. Acredito que o termo que você procura não é "removível".
LarsH 28/01/19

Respostas:

162

Environment.getExternalStorageState() retorna o caminho para o ponto de montagem SD interno como "/ mnt / sdcard"

Não, Environment.getExternalStorageDirectory() refere-se ao que o fabricante do dispositivo considerou "armazenamento externo". Em alguns dispositivos, essa mídia é removível, como um cartão SD. Em alguns dispositivos, essa é uma parte do flash no dispositivo. Aqui, "armazenamento externo" significa "o material acessível via modo USB Mass Storage quando montado em uma máquina host", pelo menos para o Android 1.xe 2.x.

Mas a questão é sobre SD externo. Como obter um caminho como "/ mnt / sdcard / external_sd" (pode variar de dispositivo para dispositivo)?

O Android não tem um conceito de "SD externo", além do armazenamento externo, conforme descrito acima.

Se um fabricante de dispositivos optou por ter armazenamento externo com flash integrado e também possui um cartão SD, será necessário entrar em contato com esse fabricante para determinar se você pode ou não usar o cartão SD (não garantido) e para que servem as regras usá-lo, como qual caminho usar para ele.


ATUALIZAR

Duas coisas recentes a serem observadas:

Primeiro, no Android 4.4 ou superior, você não tem acesso de gravação à mídia removível (por exemplo, "SD externo"), exceto para quaisquer locais nessa mídia que possam ser retornados por getExternalFilesDirs()e getExternalCacheDirs(). Veja a excelente análise de Dave Smith sobre isso, principalmente se você deseja os detalhes de baixo nível.

Segundo, para que ninguém questione se o acesso à mídia removível é ou não parte do SDK do Android, aqui está a avaliação de Dianne Hackborn :

... lembre-se: até o Android 4.4, a plataforma oficial do Android não suporta cartões SD de todo com exceção de dois casos especiais: o layout de armazenamento da velha escola onde o armazenamento externo é um cartão SD (que ainda é suportado pela plataforma de hoje) , e um pequeno recurso adicionado ao Android 3.0, onde ele digitalizaria cartões SD adicionais e os adicionaria ao provedor de mídia e daria aos aplicativos acesso somente leitura aos seus arquivos (que ainda hoje são suportados na plataforma atualmente).

O Android 4.4 é o primeiro lançamento da plataforma que realmente permitiu que os aplicativos usassem cartões SD para armazenamento. Qualquer acesso a eles antes disso era por meio de APIs privadas não suportadas. Agora, temos uma API bastante rica na plataforma que permite que os aplicativos utilizem cartões SD de uma maneira suportada, da melhor maneira possível: eles podem usar gratuitamente sua área de armazenamento específica do aplicativo sem exigir nenhuma permissões no aplicativo e pode acessar outros arquivos no cartão SD, desde que passem pelo seletor de arquivos, novamente sem a necessidade de permissões especiais.

CommonsWare
fonte
4
E esse problema está se tornando um problema cada vez maior, à medida que os dispositivos HC e ICS aparecem nesse ponto "ExternalStorageDirectory" e tudo mais para armazenamento físico interno. Além disso, a maioria dos usuários não tem idéia de como localizar onde está o cartão sd no sistema de arquivos.
Tony Maro
284
Portanto, sua resposta é basicamente 'entre em contato com o fabricante'. Nao é útil.
dragonroot
6
A última parte da resposta não é muito precisa - é realmente possível detectar o caminho do cartão SD seguindo as respostas abaixo (digitalização / proc / montagens, /system/etc/vold.fstab, etc ...).
Aprenda OpenGL ES
8
@CommonsWare: Mesmo assim, ainda não é preciso que alguém "precise" entrar em contato com um fabricante quando houver soluções que funcionem em muitos dispositivos e, além disso, o próprio SDK não é consistente em todos os dispositivos, o que não garante. Mesmo que essas soluções não funcionem em todos os dispositivos, elas funcionam em dispositivos suficientes para que muitos aplicativos Android no mercado confiem nessas técnicas, entre outras, para detectar o caminho do cartão SD externo. Acho um pouco duro e prematuro chamar todos esses desenvolvedores de tolos - o cliente certamente não é o juiz final disso?
Aprenda OpenGL ES
5
@ CommmonsWare Isso é justo o suficiente, conforme as coisas acontecem. Definitivamente, concordo que um desenvolvedor não pode assumir que isso sempre funcionará em todos os lugares e que não é possível garantir que esse código funcione em todos os dispositivos ou em todas as versões do Android. Espero que seja corrigido no SDK! Enquanto isso, ainda existem opções que funcionam em muitos dispositivos e podem melhorar a experiência do usuário final, e dada a escolha entre 80% de sucesso e 0% de sucesso, eu assumirei os 80%.
Aprenda OpenGL ES
64

Eu vim com a seguinte solução com base em algumas respostas encontradas aqui.

CÓDIGO:

public class ExternalStorage {

    public static final String SD_CARD = "sdCard";
    public static final String EXTERNAL_SD_CARD = "externalSdCard";

    /**
     * @return True if the external storage is available. False otherwise.
     */
    public static boolean isAvailable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    public static String getSdCardPath() {
        return Environment.getExternalStorageDirectory().getPath() + "/";
    }

    /**
     * @return True if the external storage is writable. False otherwise.
     */
    public static boolean isWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;

    }

    /**
     * @return A map of all storage locations available
     */
    public static Map<String, File> getAllStorageLocations() {
        Map<String, File> map = new HashMap<String, File>(10);

        List<String> mMounts = new ArrayList<String>(10);
        List<String> mVold = new ArrayList<String>(10);
        mMounts.add("/mnt/sdcard");
        mVold.add("/mnt/sdcard");

        try {
            File mountFile = new File("/proc/mounts");
            if(mountFile.exists()){
                Scanner scanner = new Scanner(mountFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("/dev/block/vold/")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[1];

                        // don't add the default mount path
                        // it's already in the list.
                        if (!element.equals("/mnt/sdcard"))
                            mMounts.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            File voldFile = new File("/system/etc/vold.fstab");
            if(voldFile.exists()){
                Scanner scanner = new Scanner(voldFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("dev_mount")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[2];

                        if (element.contains(":"))
                            element = element.substring(0, element.indexOf(":"));
                        if (!element.equals("/mnt/sdcard"))
                            mVold.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        for (int i = 0; i < mMounts.size(); i++) {
            String mount = mMounts.get(i);
            if (!mVold.contains(mount))
                mMounts.remove(i--);
        }
        mVold.clear();

        List<String> mountHash = new ArrayList<String>(10);

        for(String mount : mMounts){
            File root = new File(mount);
            if (root.exists() && root.isDirectory() && root.canWrite()) {
                File[] list = root.listFiles();
                String hash = "[";
                if(list!=null){
                    for(File f : list){
                        hash += f.getName().hashCode()+":"+f.length()+", ";
                    }
                }
                hash += "]";
                if(!mountHash.contains(hash)){
                    String key = SD_CARD + "_" + map.size();
                    if (map.size() == 0) {
                        key = SD_CARD;
                    } else if (map.size() == 1) {
                        key = EXTERNAL_SD_CARD;
                    }
                    mountHash.add(hash);
                    map.put(key, root);
                }
            }
        }

        mMounts.clear();

        if(map.isEmpty()){
                 map.put(SD_CARD, Environment.getExternalStorageDirectory());
        }
        return map;
    }
}

USO:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
Richard
fonte
1
testado com nexo 4, Nexus S, Galaxy S2, S3 Galaxy, HTC desejo =)
Richard
2
Olá novamente, Richard - acredite ou não, eu tenho que perguntar: você realmente tentou escrever e ler de volta em um arquivo dessa maneira, não apenas conseguiu os diretórios? Lembre-se do nosso antigo problema "/ sdcard0"? Eu tentei esse código e ele falhou em um S3 quando tentei ler novamente no arquivo que ele gravou. ... isso é muito bizarro ... e doloroso :))
Howard Pautz
10
Isso falha em dispositivos que não possuem 2 cartões SD. Ele assume 1º encontrado é interna, e 2º encontrado é externa ...
Caner
Não trabalhou para dispositivos USB conectados através do cabo OTG no Nexus 5 e Nexus 7.
Khawar Raza
4
/system/etc/vold.fstab não está acessível no Android 4.3 ou superior
Ali
37

Eu tinha um aplicativo que usava um ListPreference local em que o usuário precisava selecionar o local em que queria salvar alguma coisa.

Nesse aplicativo, digitalizei /proc/mountse encontrei /system/etc/vold.fstabpontos de montagem de sdcard. Armazenei os pontos de montagem de cada arquivo em doisArrayList s .

Depois, comparei uma lista com a outra e descartei os itens que não estavam nas duas listas. Isso me deu uma lista de caminhos raiz para cada sdcard.

De lá, eu testei os caminhos com File.exists(), File.isDirectory()e File.canWrite(). Se qualquer um desses testes fosse falso, descartei esse caminho da lista.

Tudo o que foi deixado na lista, converti em uma String[]matriz para que pudesse ser usada pelo ListPreferenceatributo values.

Você pode visualizar o código aqui: http://sapienmobile.com/?p=204

Barão
fonte
FYI, este não funciona no Galaxy S3, 2 cartões SD, única listada em vold.conf
3c71
1
@ 3c71 - Você pode me enviar os arquivos vold e montagens para o Galaxy S3? Vou ajustar o código para cobri-lo.
Baron
Galaxy S, todos os caminhos encontrados não eram graváveis, estranhos. Foram encontrados dois armazenamento, padrão / mnt / sdcard e / storage / sdcard0, ambos falharam no teste
quinta-
1
Ajustei o código para ignorar o arquivo de montagens. Esse foi o problema nos dispositivos Motorola e Samsung. O arquivo de montagens não cobre o caso external_sd, mas está listado em vold. A versão inicial da minha turma comparava montagens com itens antigos e descartados que não eram comuns a ambos. Pegue a classe atualizada no mesmo link acima.
Baron
1
Obrigado Baron, esta é a resposta; pelo menos o útil.
precisa saber é o seguinte
23

Você pode tentar usar a função da biblioteca de suporte chamada ContextCompat.getExternalFilesDirs () :

      final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
      final ArrayList<File> extRootPaths=new ArrayList<>();
      for(final File file : appsDir)
        extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());

O primeiro é o armazenamento externo primário e o restante deve ser o caminho real dos cartões SD.

O motivo do múltiplo ".getParentFile ()" é subir outra pasta, pois o caminho original é

.../Android/data/YOUR_APP_PACKAGE_NAME/files/

EDIT: aqui está uma maneira mais abrangente que eu criei, para obter os caminhos dos cartões SD:

  /**
   * returns a list of all available sd cards paths, or null if not found.
   *
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
    {
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return null;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return null;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return null;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return null;
      }
    final List<String> result=new ArrayList<>();
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    if(result.isEmpty())
      return null;
    return result;
    }

  /** Given any file/folder inside an sd card, this will return the path of the sd card */
  private static String getRootOfInnerSdCardFolder(File file)
    {
    if(file==null)
      return null;
    final long totalSpace=file.getTotalSpace();
    while(true)
      {
      final File parentFile=file.getParentFile();
      if(parentFile==null||parentFile.getTotalSpace()!=totalSpace||!parentFile.canRead())
        return file.getAbsolutePath();
      file=parentFile;
      }
    }
desenvolvedor android
fonte
Parece uma resposta muito boa, mas como alguém poderia integrar isso em uma atividade simples? Há diversas variáveis não definidas, como App, ContextCompact,EnvironmentCompact
Antonio
@Antonio ContextCompact, EnvironmentCompact estão disponíveis na biblioteca de suporte. O "App.global ()" é apenas o contexto do aplicativo, que defini globalmente desde que não gosto de adicionar um parâmetro Context em todos os lugares.
desenvolvedor Android
1
Ótimo! Funciona para o dispositivo de minas v4.4 Samsung GT S Advance, espero que funcione para outras pessoas
user25
@androiddeveloper A resposta editada funcionará para todos os tamanhos de dispositivos e cartões SD?
precisa saber é o seguinte
1
Isso funcionou perfeitamente para mim - deve ser a resposta aceita.
Paradox
17

Para recuperar todos os armazenamentos externos (sejam cartões SD ou armazenamentos internos não removíveis ), você pode usar o seguinte código:

final String state = Environment.getExternalStorageState();

if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) {  // we can read the External Storage...           
    //Retrieve the primary External Storage:
    final File primaryExternalStorage = Environment.getExternalStorageDirectory();

    //Retrieve the External Storages root directory:
    final String externalStorageRootDir;
    if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) {  // no parent...
        Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
    }
    else {
        final File externalStorageRoot = new File( externalStorageRootDir );
        final File[] files = externalStorageRoot.listFiles();

        for ( final File file : files ) {
            if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) {  // it is a real directory (not a USB drive)...
                Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
            }
        }
    }
}

Como alternativa, você pode usar System.getenv ("EXTERNAL_STORAGE") para recuperar o diretório primário de armazenamento externo (por exemplo, "/ storage / sdcard0" ) e System.getenv ("SECONDARY_STORAGE") para recuperar a lista de todos os diretórios secundários (por exemplo " / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB " ). Lembre-se de que, também nesse caso, convém filtrar a lista de diretórios secundários para excluir as unidades USB.

De qualquer forma, observe que o uso de caminhos codificados é sempre uma abordagem ruim (especialmente quando todos os fabricantes podem alterá-lo conforme desejado).

Paolo Rovelli
fonte
2
Apenas considere qualquer defensor que não deixe um comentário um troll, então eu votei para compensá-lo. ;) MAS, acho que seu método é um tanto arbitrário: como podemos saber que pular essas "unidades USB", mas manter todo o resto é realmente igual a "sdcards", como perguntado na pergunta? Além disso, o que você sugeriu System.getenv("SECONDARY_STORAGE")também pode fazer algumas referências, pois parece não documentado.
Sz.
1
Até onde eu sei, na API do Android não há referência a um método padrão para recuperar todos os armazenamentos externos. No entanto, o método proposto não é arbitrário. No Android, como em todos os sistemas Unix / Linux, TODOS os dispositivos de armazenamento de montagem são armazenados / vinculados em um diretório comum: "/ mnt" (o diretório Unix / Linux padrão para montagem de dispositivos de armazenamento) ou, nas versões mais recentes, "/ armazenamento". É por isso que você pode ter certeza de que encontrará todos os cartões SD vinculados nesta pasta.
Paolo Rovelli
1
Em relação ao método System.getenv ("EXTERNAL_STORAGE"), não tenho nenhuma referência em vez da página da API (que não explica muito): developer.android.com/reference/java/lang/… Não consegui encontrar nenhuma página oficial para as variáveis ​​de ambiente do sistema Android. Aqui, no entanto, você pode encontrar uma pequena lista deles: herongyang.com/Android/...
Paolo Rovelli
O que eu quis dizer com não ter certeza sobre os cartões SD é que /mnttambém pode haver várias outras árvores fs, não apenas cartões SD e unidades USB. Seu código também listaria quaisquer montagens de sistema de arquivos internas (talvez até virtuais), se bem entendi, enquanto a pergunta deseja apenas cartões SD .
Sz.
1
Entendo. Sim você está certo. Com o meu método, você recuperará também as memórias SD internas (não removíveis).
Paolo Rovelli
15

Como Richard, eu também uso o arquivo / proc / mounts para obter a lista de opções de armazenamento disponíveis

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

        public final String path;
        public final boolean internal;
        public final boolean readonly;
        public final int display_number;

        StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
            this.path = path;
            this.internal = internal;
            this.readonly = readonly;
            this.display_number = display_number;
        }

        public String getDisplayName() {
            StringBuilder res = new StringBuilder();
            if (internal) {
                res.append("Internal SD card");
            } else if (display_number > 1) {
                res.append("SD card " + display_number);
            } else {
                res.append("SD card");
            }
            if (readonly) {
                res.append(" (Read only)");
            }
            return res.toString();
        }
    }

    public static List<StorageInfo> getStorageList() {

        List<StorageInfo> list = new ArrayList<StorageInfo>();
        String def_path = Environment.getExternalStorageDirectory().getPath();
        boolean def_path_internal = !Environment.isExternalStorageRemovable();
        String def_path_state = Environment.getExternalStorageState();
        boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
                                    || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
        BufferedReader buf_reader = null;
        try {
            HashSet<String> paths = new HashSet<String>();
            buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
            String line;
            int cur_display_number = 1;
            Log.d(TAG, "/proc/mounts");
            while ((line = buf_reader.readLine()) != null) {
                Log.d(TAG, line);
                if (line.contains("vfat") || line.contains("/mnt")) {
                    StringTokenizer tokens = new StringTokenizer(line, " ");
                    String unused = tokens.nextToken(); //device
                    String mount_point = tokens.nextToken(); //mount point
                    if (paths.contains(mount_point)) {
                        continue;
                    }
                    unused = tokens.nextToken(); //file system
                    List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
                    boolean readonly = flags.contains("ro");

                    if (mount_point.equals(def_path)) {
                        paths.add(def_path);
                        list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1));
                    } else if (line.contains("/dev/block/vold")) {
                        if (!line.contains("/mnt/secure")
                            && !line.contains("/mnt/asec")
                            && !line.contains("/mnt/obb")
                            && !line.contains("/dev/mapper")
                            && !line.contains("tmpfs")) {
                            paths.add(mount_point);
                            list.add(new StorageInfo(mount_point, false, readonly, cur_display_number++));
                        }
                    }
                }
            }

            if (!paths.contains(def_path) && def_path_available) {
                list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1));
            }

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (buf_reader != null) {
                try {
                    buf_reader.close();
                } catch (IOException ex) {}
            }
        }
        return list;
    }    
}
Vitaliy Polchuk
fonte
Obrigado. Funcionou perfeitamente. E eu gosto da maneira como você tornou o StorageInfo não mutável. Por outro lado printStackTrace? Quando temos android.util.Log.e?
Martin
1
Não trabalhou para dispositivos USB conectados através do cabo OTG no Nexus 5 e Nexus 7.
Khawar Raza
1
Não consigo usar isso para gravar arquivos no SDCard
Eu Vid
mesmo problema que o @EuVid funciona na VM / AVD, mas não no hardware
espie
11

É possível encontrar onde quaisquer cartões SD adicionais são montados lendo /proc/mounts(arquivo padrão do Linux) e verificando os dados antigos ( /system/etc/vold.conf). E observe que o local retornado por Environment.getExternalStorageDirectory()pode não aparecer na configuração vold (em alguns dispositivos, o armazenamento interno não pode ser desmontado), mas ainda precisa ser incluído na lista. No entanto, não encontramos uma boa maneira de descrevê-los ao usuário .

Jan Hudec
fonte
Imo, o uso de mounté mais compatível do que a leitura do /procsistema de arquivos. O problema é que o cartão SD não é necessário formatado como FAT. Além disso, o ponto de montagem do cartão pode variar de ROM para ROM. Além disso, poderia haver vários outros VFAT partições ...
borisstr
1
@borisstr: Hm, na verdade, o Android usa o vold , portanto, olhar para a sua configuração também é apropriado.
Jan Hudec
O arquivo de código que compartilhei da minha postagem acima inclui um método para descrever os caminhos raiz descobertos para o usuário. Veja o método setProperties () .
Baron
1
@borisstr, na verdade não, a leitura / proc / mounts é mais portátil em dispositivos Android do que iniciar o mountexecutável, especialmente porque o desencadeamento de executáveis ​​é desencorajado.
Chris Stratton
7

Eu tento todas as soluções contidas neste tópico neste momento. Mas todos eles não funcionaram corretamente em dispositivos com uma placa externa (removível) e uma interna (não removível). O caminho do cartão externo não é possível obter do comando 'mount', do arquivo 'proc / mounts' etc.

E eu crio minha própria solução (na de Paulo Luan):

String sSDpath = null;
File   fileCur = null;
for( String sPathCur : Arrays.asList( "ext_card", "external_sd", "ext_sd", "external", "extSdCard",  "externalSdCard")) // external sdcard
{
   fileCur = new File( "/mnt/", sPathCur);
   if( fileCur.isDirectory() && fileCur.canWrite())
   {
     sSDpath = fileCur.getAbsolutePath();
     break;
   }
}
fileCur = null;
if( sSDpath == null)  sSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
Tapa Save
fonte
6

Se você olhar o código-fonte android.os.Environment, verá que o Android depende muito de variáveis ​​de ambiente para caminhos. Você pode usar a variável de ambiente "SECONDARY_STORAGE" para encontrar o caminho para o cartão SD removível.

/**
 * Get a file using an environmental variable.
 *
 * @param variableName
 *         The Environment variable name.
 * @param paths
 *         Any paths to the file if the Environment variable was not found.
 * @return the File or {@code null} if the File could not be located.
 */
private static File getDirectory(String variableName, String... paths) {
    String path = System.getenv(variableName);
    if (!TextUtils.isEmpty(path)) {
        if (path.contains(":")) {
            for (String _path : path.split(":")) {
                File file = new File(_path);
                if (file.exists()) {
                    return file;
                }
            }
        } else {
            File file = new File(path);
            if (file.exists()) {
                return file;
            }
        }
    }
    if (paths != null && paths.length > 0) {
        for (String _path : paths) {
            File file = new File(_path);
            if (file.exists()) {
                return file;
            }
        }
    }
    return null;
}

Exemplo de uso:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
Jared Rummler
fonte
5

Simplesmente use isso:

String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
    Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
    Log.i("SECONDARY_STORAGE", secondary_sd)
Behrouz.M
fonte
Em alguns dispositivos, SECONDARY_STORAGEhá vários caminhos separados por dois pontos (":"). É por isso que eu divido a String (veja minha resposta acima).
Jared Rummler
Ambos retornam nulos para mim.
Tim Cooper
5

Existe uma maneira universal de encontrar a localização de um cartão SD externo?

Por via universal , se você quer dizer via oficial; sim há um.

Em nível de API 19 ou seja, na versão Android 4.4 Kitkat, eles adicionaram File[] getExternalFilesDirs (String type)noContext classe que permite que aplicativos para armazenar dados / arquivos em cartões micro SD.

O Android 4.4 é o primeiro lançamento da plataforma que permite que os aplicativos usem cartões SD para armazenamento. Qualquer acesso a cartões SD antes do nível 19 da API era por meio de APIs privadas não suportadas.

getExternalFilesDirs (tipo String) retorna caminhos absolutos para diretórios específicos de aplicativos em todos os dispositivos de armazenamento compartilhado / externo. Isso significa que ele retornará caminhos para a memória interna e externa. Geralmente, o segundo caminho retornado seria o caminho de armazenamento para o cartão microSD (se houver).

Mas note que,

O armazenamento compartilhado pode nem sempre estar disponível, pois a mídia removível pode ser ejetada pelo usuário. O estado da mídia pode ser verificado usando getExternalStorageState(File).

Não há segurança imposta com esses arquivos. Por exemplo, qualquer retenção de aplicativo WRITE_EXTERNAL_STORAGEpode gravar nesses arquivos.

A terminologia de armazenamento interno e externo, de acordo com os documentos oficiais do Google / Android, é bem diferente do que pensamos.

AnV
fonte
"A terminologia de armazenamento interno e externo, de acordo com os documentos oficiais do Google / Android, é bem diferente do que pensamos." Sim, de fato, o título da pergunta esclarece que o OP está perguntando sobre um cartão SD removível . getExternalFilesDirs()geralmente retorna cartões SD que não são removíveis; portanto, essa não é uma maneira universal de encontrar o local de um cartão SD removível.
LarsH
"getExternalFilesDirs (tipo String) retorna caminhos absolutos para diretórios específicos de aplicativos em todos os dispositivos de armazenamento compartilhados / externos. Isso significa que retornará caminhos para a memória interna e externa." Esse par de frases é muito enganador, porque, para que ambos sejam verdadeiros, "externo" deve significar duas coisas diferentes e conflitantes.
LarsH
4

Aqui está a maneira que eu uso para encontrar o cartão externo. Use mount cmd return e analise a parte vfat.

String s = "";
try {
Process process = new ProcessBuilder().command("mount")
        .redirectErrorStream(true).start();

process.waitFor();

InputStream is = process.getInputStream();
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
    s = s + new String(buffer);
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}

//用行分隔mount列表
String[] lines = s.split("\n");
for(int i=0; i<lines.length; i++) {
//如果行内有挂载路径且为vfat类型,说明可能是内置或者外置sd的挂载点
if(-1 != lines[i].indexOf(path[0]) && -1 != lines[i].indexOf("vfat")) {
    //再用空格分隔
    String[] blocks = lines[i].split("\\s");
    for(int j=0; j<blocks.length; j++) {
        //判断是否是挂载为vfat类型
        if(-1 != blocks[j].indexOf(path[0])) {
            //Test if it is the external sd card.
        }
    }
}
}
Fakebear
fonte
4

Esta solução lida com o fato de que System.getenv("SECONDARY_STORAGE") não serve para o Marshmallow.

Testado e trabalhando em:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - Estoque)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - Estoque)
  • Samsung Galaxy S4 (Android 4.4 - Estoque)
  • Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1 - Estoque)

    /**
     * Returns all available external SD-Card roots in the system.
     *
     * @return paths to all available external SD-Card roots in the system.
     */
    public static String[] getStorageDirectories() {
        String [] storageDirectories;
        String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            List<String> results = new ArrayList<String>();
            File[] externalDirs = applicationContext.getExternalFilesDirs(null);
            for (File file : externalDirs) {
                String path = file.getPath().split("/Android")[0];
                if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Environment.isExternalStorageRemovable(file))
                        || rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){
                    results.add(path);
                }
            }
            storageDirectories = results.toArray(new String[0]);
        }else{
            final Set<String> rv = new HashSet<String>();
    
            if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
                final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
                Collections.addAll(rv, rawSecondaryStorages);
            }
            storageDirectories = rv.toArray(new String[rv.size()]);
        }
        return storageDirectories;
    }
DaveAlden
fonte
2

Desde a minha resposta original acima, a digitalização do vold não é mais viável entre os vários fabricantes.

Eu desenvolvi um método mais confiável e direto.

File mnt = new File("/storage");
if (!mnt.exists())
    mnt = new File("/mnt");

File[] roots = mnt.listFiles(new FileFilter() {

    @Override
    public boolean accept(File pathname) {
        return pathname.isDirectory() && pathname.exists()
                && pathname.canWrite() && !pathname.isHidden()
                && !isSymlink(pathname);
    }
});

roots conterá todos os diretórios raiz graváveis ​​no sistema, incluindo todos os dispositivos usb conectados por usb.

NOTA: O método canWrite precisa da permissão android.permission.WRITE_EXTERNAL_STORAGE.

Barão
fonte
O método isSymlink (File) é indefinido para o tipo new FileFilter () {}
Omid Omidi
Alguma idéia se isso não conseguir encontrar cartões SD externos no Android 4.4 devido ao canWrite?
Anthony
Isso é certamente mais direto do que seu outro método, mas é confiável? Por exemplo, eu li que em alguns dispositivos Samsung, /external_sdé o cartão microSD externo; em alguns LGs, é /_ExternalSD; em alguns dispositivos é /sdcard. Talvez o último seja um link simbólico /storage/sdcard0ou similar, mas será que esses outros serão realmente cobertos por /storage/*e /mount/*?
Larsh
Além disso, é necessário usar pathname.canWrite()e exigir permissão WRITE_EXTERNAL_STORAGE? Por que não apenas ligar pathname.canRead()?
Larsh
1

Foi tão tarde, mas finalmente consegui algo que testei a maioria dos dispositivos (por fabricante e versões do Android) e está funcionando no Android 2.2+. se você achar que não está funcionando, comente com o nome do seu dispositivo. eu irei consertar isso. se alguém estiver interessado, vou explicar como funciona.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;

import android.util.Log;


/**
 * @author ajeet
 *05-Dec-2014  2014
 *
 */
public class StorageUtil {

    public boolean isRemovebleSDCardMounted() {
        File file = new File("/sys/class/block/");
        File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
        boolean flag = false;
        for (File mmcfile : files) {
            File scrfile = new File(mmcfile, "device/scr");
            if (scrfile.exists()) {
                flag = true;
                break;
            }
        }
        return flag;
    }

    public String getRemovebleSDCardPath() throws IOException {
        String sdpath = null;
        File file = new File("/sys/class/block/");
        File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
        String sdcardDevfile = null;
        for (File mmcfile : files) {
            Log.d("SDCARD", mmcfile.getAbsolutePath());
            File scrfile = new File(mmcfile, "device/scr");
            if (scrfile.exists()) {
                sdcardDevfile = mmcfile.getName();
                Log.d("SDCARD", mmcfile.getName());
                break;
            }
        }
        if (sdcardDevfile == null) {
            return null;
        }
        FileInputStream is;
        BufferedReader reader;

        files = file.listFiles(new MmcblkFilter(sdcardDevfile + "p\\d+"));
        String deviceName = null;
        if (files.length > 0) {
            Log.d("SDCARD", files[0].getAbsolutePath());
            File devfile = new File(files[0], "dev");
            if (devfile.exists()) {
                FileInputStream fis = new FileInputStream(devfile);
                reader = new BufferedReader(new InputStreamReader(fis));
                String line = reader.readLine();
                deviceName = line;
            }
            Log.d("SDCARD", "" + deviceName);
            if (deviceName == null) {
                return null;
            }
            Log.d("SDCARD", deviceName);

            final File mountFile = new File("/proc/self/mountinfo");

            if (mountFile.exists()) {
                is = new FileInputStream(mountFile);
                reader = new BufferedReader(new InputStreamReader(is));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    // Log.d("SDCARD", line);
                    // line = reader.readLine();
                    // Log.d("SDCARD", line);
                    String[] mPonts = line.split("\\s+");
                    if (mPonts.length > 6) {
                        if (mPonts[2].trim().equalsIgnoreCase(deviceName)) {
                            if (mPonts[4].contains(".android_secure")
                                    || mPonts[4].contains("asec")) {
                                continue;
                            }
                            sdpath = mPonts[4];
                            Log.d("SDCARD", mPonts[4]);

                        }
                    }

                }
            }

        }

        return sdpath;
    }

    static class MmcblkFilter implements FilenameFilter {
        private String pattern;

        public MmcblkFilter(String pattern) {
            this.pattern = pattern;

        }

        @Override
        public boolean accept(File dir, String filename) {
            if (filename.matches(pattern)) {
                return true;
            }
            return false;
        }

    }

}
Ajeet47
fonte
Hey downvoter, tente primeiro. Se não estiver funcionando, comente o seu dispositivo. estamos usando ele em mais de mil dispositivos com Android
2.2+
Sua classe me fornece / mnt / media_rw / extSdCard no Samsung Galaxy S4, GT-I9500, Android 5.0.1 (o dispositivo NÃO está enraizado). Mas não há nada visível na pasta / mnt / media_rw com o ES File Manager ...
isabsent
@ isabsent use if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {Arquivo [] arquivo = context.getExternalFilesDirs (null); retornar file.length> 1? file [1]: null; }
Ajeet47 24/04
O que você acha sobre stackoverflow.com/a/27197248/753575 ? Essa abordagem é mais abrangente para o caso de Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
Isabent
sim. é garantido que você obter o caminho significativo a partir context.getExternalFilesDirs (null), mas você prepará-la para o caminho raiz (ele retornará caminho para você diretório app prepará-la em "/ Android".)
Ajeet47
1

Ao escrever o código abaixo, você obterá o local:

/ storage / 663D-554E / Android / data / app_package_name / files /

que armazena os dados do seu aplicativo em / android / local de dados dentro do sd_card.

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

list[1]+"/fol" 

para obter o local, passe 0 para interno e 1 para sdcard para matriz de arquivos.

Eu testei esse código em um moto g4 plus e no dispositivo Samsung (tudo funciona bem).

espero que isso possa ser útil.

Rk215 Tech
fonte
às vezes caminho do cartão SD não está no índice 1, tenho visto os casos em que foi no índice 0. melhor seguir outra coisa
Raghav Satyadev
1

Aqui está o método que eu uso para encontrar um removível cartão SD . É complexo e provavelmente exagero em algumas situações, mas funciona em uma ampla variedade de versões do Android e fabricantes de dispositivos que testei nos últimos anos. Não conheço nenhum dispositivo desde o nível 15 da API no qual não encontra o cartão SD, se houver um montado. Na maioria dos casos, ele não retornará falsos positivos, especialmente se você der o nome de um arquivo conhecido para procurar.

Informe-me se você encontrar algum caso em que não funcione.

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.regex.Pattern;

public class SDCard {
    private static final String TAG = "SDCard";

    /** In some scenarios we can expect to find a specified file or folder on SD cards designed
     * to work with this app. If so, set KNOWNFILE to that filename. It will make our job easier.
     * Set it to null otherwise. */
    private static final String KNOWNFILE = null;

    /** Common paths for microSD card. **/
    private static String[] commonPaths = {
            // Some of these taken from
            // /programming/13976982/removable-storage-external-sdcard-path-by-manufacturers
            // These are roughly in order such that the earlier ones, if they exist, are more sure
            // to be removable storage than the later ones.
            "/mnt/Removable/MicroSD",
            "/storage/removable/sdcard1", // !< Sony Xperia Z1
            "/Removable/MicroSD", // Asus ZenPad C
            "/removable/microsd",
            "/external_sd", // Samsung
            "/_ExternalSD", // some LGs
            "/storage/extSdCard", // later Samsung
            "/storage/extsdcard", // Main filesystem is case-sensitive; FAT isn't.
            "/mnt/extsd", // some Chinese tablets, e.g. Zeki
            "/storage/sdcard1", // If this exists it's more likely than sdcard0 to be removable.
            "/mnt/extSdCard",
            "/mnt/sdcard/external_sd",
            "/mnt/external_sd",
            "/storage/external_SD",
            "/storage/ext_sd", // HTC One Max
            "/mnt/sdcard/_ExternalSD",
            "/mnt/sdcard-ext",

            "/sdcard2", // HTC One M8s
            "/sdcard1", // Sony Xperia Z
            "/mnt/media_rw/sdcard1",   // 4.4.2 on CyanogenMod S3
            "/mnt/sdcard", // This can be built-in storage (non-removable).
            "/sdcard",
            "/storage/sdcard0",
            "/emmc",
            "/mnt/emmc",
            "/sdcard/sd",
            "/mnt/sdcard/bpemmctest",
            "/mnt/external1",
            "/data/sdext4",
            "/data/sdext3",
            "/data/sdext2",
            "/data/sdext",
            "/storage/microsd" //ASUS ZenFone 2

            // If we ever decide to support USB OTG storage, the following paths could be helpful:
            // An LG Nexus 5 apparently uses usb://1002/UsbStorage/ as a URI to access an SD
            // card over OTG cable. Other models, like Galaxy S5, use /storage/UsbDriveA
            //        "/mnt/usb_storage",
            //        "/mnt/UsbDriveA",
            //        "/mnt/UsbDriveB",
    };

    /** Find path to removable SD card. */
    public static File findSdCardPath(Context context) {
        String[] mountFields;
        BufferedReader bufferedReader = null;
        String lineRead = null;

        /** Possible SD card paths */
        LinkedHashSet<File> candidatePaths = new LinkedHashSet<>();

        /** Build a list of candidate paths, roughly in order of preference. That way if
         * we can't definitively detect removable storage, we at least can pick a more likely
         * candidate. */

        // Could do: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists and has contents,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // I moved hard-coded paths toward the end, but we need to make sure we put the ones in
        // backwards order that are returned by the OS. And make sure the iterators respect
        // the order!
        // This is because when multiple "external" storage paths are returned, it's always (in
        // experience, but not guaranteed by documentation) with internal/emulated storage
        // first, removable storage second.

        // Add value of environment variables as candidates, if set:
        // EXTERNAL_STORAGE, SECONDARY_STORAGE, EXTERNAL_SDCARD_STORAGE
        // But note they are *not* necessarily *removable* storage! Especially EXTERNAL_STORAGE.
        // And they are not documented (API) features. Typically useful only for old versions of Android.

        String val = System.getenv("SECONDARY_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
        val = System.getenv("EXTERNAL_SDCARD_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        // Get listing of mounted devices with their properties.
        ArrayList<File> mountedPaths = new ArrayList<>();
        try {
            // Note: Despite restricting some access to /proc (http://stackoverflow.com/a/38728738/423105),
            // Android 7.0 does *not* block access to /proc/mounts, according to our test on George's Alcatel A30 GSM.
            bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));

            // Iterate over each line of the mounts listing.
            while ((lineRead = bufferedReader.readLine()) != null) {
                Log.d(TAG, "\nMounts line: " + lineRead);
                mountFields = lineRead.split(" ");

                // columns: device, mountpoint, fs type, options... Example:
                // /dev/block/vold/179:97 /storage/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
                String device = mountFields[0], path = mountFields[1], fsType = mountFields[2];

                // The device, path, and fs type must conform to expected patterns.
                if (!(devicePattern.matcher(device).matches() &&
                        pathPattern.matcher(path).matches() &&
                        fsTypePattern.matcher(fsType).matches()) ||
                        // mtdblock is internal, I'm told.
                        device.contains("mtdblock") ||
                        // Check for disqualifying patterns in the path.
                        pathAntiPattern.matcher(path).matches()) {
                    // If this mounts line fails our tests, skip it.
                    continue;
                }

                // TODO maybe: check options to make sure it's mounted RW?
                // The answer at http://stackoverflow.com/a/13648873/423105 does.
                // But it hasn't seemed to be necessary so far in my testing.

                // This line met the criteria so far, so add it to candidate list.
                addPath(path, null, mountedPaths);
            }
        } catch (IOException ignored) {
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ignored) {
                }
            }
        }

        // Append the paths from mount table to candidate list, in reverse order.
        if (!mountedPaths.isEmpty()) {
            // See https://stackoverflow.com/a/5374346/423105 on why the following is necessary.
            // Basically, .toArray() needs its parameter to know what type of array to return.
            File[] mountedPathsArray = mountedPaths.toArray(new File[mountedPaths.size()]);
            addAncestors(candidatePaths, mountedPathsArray);
        }

        // Add hard-coded known common paths to candidate list:
        addStrings(candidatePaths, commonPaths);

        // If the above doesn't work we could try the following other options, but in my experience they
        // haven't added anything helpful yet.

        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.

        // This may be non-removable.
        Log.d(TAG, "Environment.getExternalStorageDirectory():");
        addPath(null, ancestor(Environment.getExternalStorageDirectory()), candidatePaths);

        // Context.getExternalFilesDirs() is only available from API level 19. You can use
        // ContextCompat.getExternalFilesDirs() on earlier APIs, but it only returns one dir anyway.
        Log.d(TAG, "context.getExternalFilesDir(null):");
        addPath(null, ancestor(context.getExternalFilesDir(null)), candidatePaths);

        // "Returns absolute paths to application-specific directories on all external storage
        // devices where the application can place persistent files it owns."
        // We might be able to use these to deduce a higher-level folder that isn't app-specific.
        // Also, we apparently have to call getExternalFilesDir[s](), at least in KITKAT+, in order to ensure that the
        // "external files" directory exists and is available.
        Log.d(TAG, "ContextCompat.getExternalFilesDirs(context, null):");
        addAncestors(candidatePaths, ContextCompat.getExternalFilesDirs(context, null));
        // Very similar results:
        Log.d(TAG, "ContextCompat.getExternalCacheDirs(context):");
        addAncestors(candidatePaths, ContextCompat.getExternalCacheDirs(context));

        // TODO maybe: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // A "public" external storage directory. But in my experience it doesn't add anything helpful.
        // Note that you can't pass null, or you'll get an NPE.
        final File publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
        // Take the parent, because we tend to get a path like /pathTo/sdCard/Music.
        addPath(null, publicDirectory.getParentFile(), candidatePaths);
        // EXTERNAL_STORAGE: may not be removable.
        val = System.getenv("EXTERNAL_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        if (candidatePaths.isEmpty()) {
            Log.w(TAG, "No removable microSD card found.");
            return null;
        } else {
            Log.i(TAG, "\nFound potential removable storage locations: " + candidatePaths);
        }

        // Accept or eliminate candidate paths if we can determine whether they're removable storage.
        // In Lollipop and later, we can check isExternalStorageRemovable() status on each candidate.
        if (Build.VERSION.SDK_INT >= 21) {
            Iterator<File> itf = candidatePaths.iterator();
            while (itf.hasNext()) {
                File dir = itf.next();
                // handle illegalArgumentException if the path is not a valid storage device.
                try {
                    if (Environment.isExternalStorageRemovable(dir)
                        // && containsKnownFile(dir)
                            ) {
                        Log.i(TAG, dir.getPath() + " is removable external storage");
                        return dir;
                    } else if (Environment.isExternalStorageEmulated(dir)) {
                        Log.d(TAG, "Removing emulated external storage dir " + dir);
                        itf.remove();
                    }
                } catch (IllegalArgumentException e) {
                    Log.d(TAG, "isRemovable(" + dir.getPath() + "): not a valid storage device.", e);
                }
            }
        }

        // Continue trying to accept or eliminate candidate paths based on whether they're removable storage.
        // On pre-Lollipop, we only have singular externalStorage. Check whether it's removable.
        if (Build.VERSION.SDK_INT >= 9) {
            File externalStorage = Environment.getExternalStorageDirectory();
            Log.d(TAG, String.format(Locale.ROOT, "findSDCardPath: getExternalStorageDirectory = %s", externalStorage.getPath()));
            if (Environment.isExternalStorageRemovable()) {
                // Make sure this is a candidate.
                // TODO: Does this contains() work? Should we be canonicalizing paths before comparing?
                if (candidatePaths.contains(externalStorage)
                    // && containsKnownFile(externalStorage)
                        ) {
                    Log.d(TAG, "Using externalStorage dir " + externalStorage);
                    return externalStorage;
                }
            } else if (Build.VERSION.SDK_INT >= 11 && Environment.isExternalStorageEmulated()) {
                Log.d(TAG, "Removing emulated external storage dir " + externalStorage);
                candidatePaths.remove(externalStorage);
            }
        }

        // If any directory contains our special test file, consider that the microSD card.
        if (KNOWNFILE != null) {
            for (File dir : candidatePaths) {
                Log.d(TAG, String.format(Locale.ROOT, "findSdCardPath: Looking for known file in candidate path, %s", dir));
                if (containsKnownFile(dir)) return dir;
            }
        }

        // If we don't find the known file, still try taking the first candidate.
        if (!candidatePaths.isEmpty()) {
            Log.d(TAG, "No definitive path to SD card; taking the first realistic candidate.");
            return candidatePaths.iterator().next();
        }

        // If no reasonable path was found, give up.
        return null;
    }

    /** Add each path to the collection. */
    private static void addStrings(LinkedHashSet<File> candidatePaths, String[] newPaths) {
        for (String path : newPaths) {
            addPath(path, null, candidatePaths);
        }
    }

    /** Add ancestor of each File to the collection. */
    private static void addAncestors(LinkedHashSet<File> candidatePaths, File[] files) {
        for (int i = files.length - 1; i >= 0; i--) {
            addPath(null, ancestor(files[i]), candidatePaths);
        }
    }

    /**
     * Add a new candidate directory path to our list, if it's not obviously wrong.
     * Supply path as either String or File object.
     * @param strNew path of directory to add (or null)
     * @param fileNew directory to add (or null)
     */
    private static void addPath(String strNew, File fileNew, Collection<File> paths) {
        // If one of the arguments is null, fill it in from the other.
        if (strNew == null) {
            if (fileNew == null) return;
            strNew = fileNew.getPath();
        } else if (fileNew == null) {
            fileNew = new File(strNew);
        }

        if (!paths.contains(fileNew) &&
                // Check for paths known not to be removable SD card.
                // The antipattern check can be redundant, depending on where this is called from.
                !pathAntiPattern.matcher(strNew).matches()) {

            // Eliminate candidate if not a directory or not fully accessible.
            if (fileNew.exists() && fileNew.isDirectory() && fileNew.canExecute()) {
                Log.d(TAG, "  Adding candidate path " + strNew);
                paths.add(fileNew);
            } else {
                Log.d(TAG, String.format(Locale.ROOT, "  Invalid path %s: exists: %b isDir: %b canExec: %b canRead: %b",
                        strNew, fileNew.exists(), fileNew.isDirectory(), fileNew.canExecute(), fileNew.canRead()));
            }
        }
    }

    private static final String ANDROID_DIR = File.separator + "Android";

    private static File ancestor(File dir) {
        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.
        if (dir == null) {
            return null;
        } else {
            String path = dir.getAbsolutePath();
            int i = path.indexOf(ANDROID_DIR);
            if (i == -1) {
                return dir;
            } else {
                return new File(path.substring(0, i));
            }
        }
    }

    /** Returns true iff dir contains the special test file.
     * Assumes that dir exists and is a directory. (Is this a necessary assumption?) */
    private static boolean containsKnownFile(File dir) {
        if (KNOWNFILE == null) return false;

        File knownFile = new File(dir, KNOWNFILE);
        return knownFile.exists();
    }

    private static Pattern
            /** Pattern that SD card device should match */
            devicePattern = Pattern.compile("/dev/(block/.*vold.*|fuse)|/mnt/.*"),
    /** Pattern that SD card mount path should match */
    pathPattern = Pattern.compile("/(mnt|storage|external_sd|extsd|_ExternalSD|Removable|.*MicroSD).*",
            Pattern.CASE_INSENSITIVE),
    /** Pattern that the mount path should not match.
     * 'emulated' indicates an internal storage location, so skip it.
     * 'asec' is an encrypted package file, decrypted and mounted as a directory. */
    pathAntiPattern = Pattern.compile(".*(/secure|/asec|/emulated).*"),
    /** These are expected fs types, including vfat. tmpfs is not OK.
     * fuse can be removable SD card (as on Moto E or Asus ZenPad), or can be internal (Huawei G610). */
    fsTypePattern = Pattern.compile(".*(fat|msdos|ntfs|ext[34]|fuse|sdcard|esdfs).*");
}

PS

  • Não esqueça <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />no manifesto. E no nível 23 da API e superior, use checkSelfPermission/requestPermissions .
  • Defina KNOWNFILE = "myappfile" se houver um arquivo ou pasta que você espera encontrar no cartão SD. Isso torna a detecção mais precisa.
  • Obviamente, você desejará armazenar em cache o valor de findSdCardPath(), vez de recalculá-lo sempre que precisar.
  • Há um monte de log ( Log.d()) no código acima. Ajuda a diagnosticar os casos em que o caminho certo não foi encontrado. Comente se você não deseja fazer logon.
LarsH
fonte
Downvoters, você pode sugerir uma maneira de melhorar essa resposta?
LarsH 18/10/19
1

A única solução de trabalho que encontrei foi essa que usa reflexão

 /**
 * Get external sd card path using reflection
 * @param mContext
 * @param is_removable is external storage removable
 * @return
 */
private static String getExternalStoragePath(Context mContext, boolean is_removable) {

    StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
    Class<?> storageVolumeClazz = null;
    try {
        storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
        Method getPath = storageVolumeClazz.getMethod("getPath");
        Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
        Object result = getVolumeList.invoke(mStorageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            Object storageVolumeElement = Array.get(result, i);
            String path = (String) getPath.invoke(storageVolumeElement);
            boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
            if (is_removable == removable) {
                return path;
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}
Habib Kazemi
fonte
Eu, pessoalmente, não prefiro usar a reflexão, porque o Google não aprecia compatibilidade com versões anteriores em novas versões do Android!
Behrouz.M
0

Não sei por que, mas preciso chamar .createNewFile () em um arquivo criado nos diretórios de armazenamento público antes de usá-lo. Na estrutura, os comentários para esse método dizem que não é útil. Aqui está uma amostra ...


 String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS) + File.separator + "My Directory";
            final File myDir = new File(myPath);
            try {
                myDir.mkdirs();
            } catch (Exception ex) {
                Toast.makeText(this, "error: " + ex.getMessage(), Toast.LENGTH_LONG).show();
            }

        String fname = "whatever";
        File newFile = new File(myDir, fname);

        Log.i(TAG, "File exists --> " + newFile.exists()) //will be false  
    try {
            if (newFile.createNewFile()) {

                 //continue 

              } else {

                Log.e(TAG, "error creating file");

            }

        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }

user1743524
fonte
0

Eu criei um método utils para verificar se um cartão SD está disponível no dispositivo ou não e obter o caminho do cartão SD no dispositivo, se disponível.

Você pode copiar dois métodos abaixo na classe do seu projeto que precisar. Isso é tudo.

public String isRemovableSDCardAvailable() {
    final String FLAG = "mnt";
    final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
    final String EXTERNAL_STORAGE_DOCOMO = System.getenv("EXTERNAL_STORAGE_DOCOMO");
    final String EXTERNAL_SDCARD_STORAGE = System.getenv("EXTERNAL_SDCARD_STORAGE");
    final String EXTERNAL_SD_STORAGE = System.getenv("EXTERNAL_SD_STORAGE");
    final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE");

    Map<Integer, String> listEnvironmentVariableStoreSDCardRootDirectory = new HashMap<Integer, String>();
    listEnvironmentVariableStoreSDCardRootDirectory.put(0, SECONDARY_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(1, EXTERNAL_STORAGE_DOCOMO);
    listEnvironmentVariableStoreSDCardRootDirectory.put(2, EXTERNAL_SDCARD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(3, EXTERNAL_SD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(4, EXTERNAL_STORAGE);

    File externalStorageList[] = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        externalStorageList = getContext().getExternalFilesDirs(null);
    }
    String directory = null;
    int size = listEnvironmentVariableStoreSDCardRootDirectory.size();
    for (int i = 0; i < size; i++) {
        if (externalStorageList != null && externalStorageList.length > 1 && externalStorageList[1] != null)
            directory = externalStorageList[1].getAbsolutePath();
        else
            directory = listEnvironmentVariableStoreSDCardRootDirectory.get(i);

        directory = canCreateFile(directory);
        if (directory != null && directory.length() != 0) {
            if (i == size - 1) {
                if (directory.contains(FLAG)) {
                    Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
                    return directory;
                } else {
                    return null;
                }
            }
            Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
            return directory;
        }
    }
    return null;
}

/**
 * Check if can create file on given directory. Use this enclose with method
 * {@link BeginScreenFragement#isRemovableSDCardAvailable()} to check sd
 * card is available on device or not.
 * 
 * @param directory
 * @return
 */
public String canCreateFile(String directory) {
    final String FILE_DIR = directory + File.separator + "hoang.txt";
    File tempFlie = null;
    try {
        tempFlie = new File(FILE_DIR);
        FileOutputStream fos = new FileOutputStream(tempFlie);
        fos.write(new byte[1024]);
        fos.flush();
        fos.close();
        Log.e(getClass().getSimpleName(), "Can write file on this directory: " + FILE_DIR);
    } catch (Exception e) {
        Log.e(getClass().getSimpleName(), "Write file error: " + e.getMessage());
        return null;
    } finally {
        if (tempFlie != null && tempFlie.exists() && tempFlie.isFile()) {
            // tempFlie.delete();
            tempFlie = null;
        }
    }
    return directory;
}
user3161772
fonte
-1

Seu trabalho para todos os dispositivos externos, mas certifique-se de obter apenas o nome da pasta do dispositivo externo e, em seguida, você precisa obter o arquivo de um determinado local usando a classe File.

public static List<String> getExternalMounts() {
        final List<String> out = new ArrayList<>();
        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;
    }

A ligar:

List<String> list=getExternalMounts();
        if(list.size()>0)
        {
            String[] arr=list.get(0).split("/");
            int size=0;
            if(arr!=null && arr.length>0) {
                size= arr.length - 1;
            }
            File parentDir=new File("/storage/"+arr[size]);
            if(parentDir.listFiles()!=null){
                File parent[] = parentDir.listFiles();

                for (int i = 0; i < parent.length; i++) {

                    // get file path as parent[i].getAbsolutePath());

                }
            }
        }

Obtendo acesso ao armazenamento externo

Para ler ou gravar arquivos no armazenamento externo, seu aplicativo deve adquirir as permissões do sistema READ_EXTERNAL_STORAGE ou WRITE_EXTERNAL_STORAGE . Por exemplo:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    ...
</manifest>
Neeraj Singh
fonte
-2

/ sdcard => Armazenamento interno (é um link simbólico, mas deve funcionar)

/ mnt / extSdCard => Sdcard externo

Isto é para Samsung Galaxy S3

Você provavelmente pode acreditar que isso é verdade para a maioria ... no entanto, verifique novamente!

robbyoconnor
fonte
8
Eu tive vários telefones Android diferentes, cerca de metade deles da Samsung, e nunca vi esse local usado. Pode ser verdade no S3, mas dizer "você provavelmente pode acreditar nisso para a maioria" está completamente errado.
Geobits
errado. /sdcardé um link simbólico para externo no meu sony 2305.
jiggunjer
2
Eu não disse que poderia não ser?
robbyoconnor