Найти место съемной SD-карты - PullRequest
193 голосов
/ 17 апреля 2011

Существует ли универсальный способ определения местоположения внешней SD-карты?

Пожалуйста, не путайте с External Storage . Environment.getExternalStorageState() возвращает путь к внутренней точке монтирования SD, например "/ mnt / sdcard". Но вопрос по поводу внешней SD. Как получить путь типа "/ mnt / sdcard / external_sd" (он может отличаться от устройства к устройству)?

Полагаю, я закончу фильтрацией команды mount по имени файловой системы. Но я не уверен, что этот способ достаточно надежен.

Ответы [ 21 ]

153 голосов
/ 17 апреля 2011

Environment.getExternalStorageState() возвращает путь к внутренней точке монтирования SD, например "/ mnt / sdcard"

Нет, Environment.getExternalStorageDirectory() относится к тому, что производитель устройства считает «внешним хранилищем». На некоторых устройствах это съемный носитель, например SD-карта. На некоторых устройствах это часть флэш-памяти на устройстве. Здесь «внешнее хранилище» означает «содержимое, доступное через режим USB Mass Storage при подключении к хост-машине», по крайней мере для Android 1.x и 2.x.

Но вопрос по внешней SD. Как получить путь типа "/ mnt / sdcard / external_sd" (он может отличаться от устройства к устройству)?

Android не имеет понятия «внешний SD», кроме внешнего хранилища, как описано выше.

Если производитель устройства выбрал встроенную флэш-память, а также имеет SD-карту, вам нужно будет связаться с этим производителем, чтобы определить, можете ли вы использовать SD-карту (не гарантируется) и какие правила его использования, например, какой путь использовать для него.


UPDATE

Две последние заметки:

Во-первых, в Android 4.4+ у вас нет доступа для записи на съемные носители (например, «внешняя SD-карта»), за исключением любых мест на этом носителе, которые могут быть возвращены getExternalFilesDirs() и getExternalCacheDirs(). См. превосходный анализ Дейва Смита этого, особенно если вы хотите детали низкого уровня.

Во-вторых, чтобы никто не спорил о том, является ли доступ к съемным носителям иным образом частью Android SDK, вот оценка Дайан Хакборн :

... имейте в виду: до Android 4.4 официальная платформа Android не поддерживала SD-карты на all , за исключением двух особых случаев: макет старой школы, где внешним хранилищем является SD-карта ( которая по-прежнему поддерживается платформой сегодня), и небольшая функция добавлена ​​в Android 3.0, где она будет сканировать дополнительные SD-карты и добавлять их к поставщику мультимедиа и предоставлять приложениям доступ только для чтения к своим файлам (что также все еще поддерживается в платформа сегодня).

Android 4.4 - это первая версия платформы, которая фактически позволила приложениям использовать SD-карты для хранения. Любой доступ к ним до этого осуществлялся через частные неподдерживаемые API. Теперь у нас есть довольно богатый API в платформе, который позволяет приложениям использовать SD-карты поддерживаемым способом, лучше, чем они могли раньше: они могут бесплатно использовать свою область хранения, специфичную для приложения, без необходимости какого-либо разрешения в приложении, а также доступ к любым другим файлам на SD-карте, если они проходят через средство выбора файлов, опять же, без необходимости каких-либо специальных разрешений.

64 голосов
/ 25 марта 2013

Я нашел следующее решение, основанное на некоторых ответах, найденных здесь.

КОД:

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

ИСПОЛЬЗОВАНИЕ:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
36 голосов
/ 16 февраля 2012

У меня было приложение, которое использовало ListPreference, где пользователь должен был выбрать местоположение, где он хотел что-то сохранить. В этом приложении я сканировал / proc / mounts и /system/etc/vold.fstab на наличие точек монтирования sdcard. Я сохранил точки монтирования из каждого файла в две отдельные ArrayList s.

Затем я сравнил один список с другим и отбросил элементы, которых не было в обоих списках. Это дало мне список корневых путей к каждой SD-карте.

Оттуда я проверил пути с File.exists(), File.isDirectory() и File.canWrite(). Если какой-либо из этих тестов был ложным, я исключил этот путь из списка.

Все, что осталось в списке, я преобразовал в массив String[], чтобы он мог использоваться атрибутом ListPreference values.

Вы можете посмотреть код здесь: http://sapienmobile.com/?p=204

18 голосов
/ 02 сентября 2014

Вы можете попытаться использовать функцию библиотеки поддержки, которая называется 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());

Первая - это первичное внешнее хранилище,а остальные должны быть настоящими путями к SD-картам.

Причина, по которой множественный метод ".getParentFile ()" состоит в том, чтобы перейти в другую папку, поскольку исходный путь -

.../Android/data/YOUR_APP_PACKAGE_NAME/files/
* 1012.*

РЕДАКТИРОВАТЬ: вот более всеобъемлющий способ, который я создал, чтобы получить пути 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)
        return file.getAbsolutePath();
      file=parentFile;
      }
    }
17 голосов
/ 07 ноября 2013

Для извлечения всех внешних хранилищ (будь то SD-карты или внутренних несъемных хранилищ ), вы можете использовать следующий код:

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

В качестве альтернативы вы можете использовать System.getenv ("EXTERNAL_STORAGE") для извлечения основного каталога внешнего хранилища (например, "/ storage / sdcard0" ) и System. getenv ("SECONDARY_STORAGE") для восстановления списка всех вторичных каталогов (например, "/ storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB" ). Помните, что и в этом случае вам может потребоваться отфильтровать список вторичных каталогов, чтобы исключить USB-накопители.

В любом случае, обратите внимание, что использование жестко закодированных путей - это всегда плохой подход (особенно когда каждый производитель может изменить его по своему усмотрению).

12 голосов
/ 14 ноября 2013

Как и Ричард, я также использую файл / proc / mounts , чтобы получить список доступных вариантов хранения

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;
    }    
}
11 голосов
/ 16 ноября 2011

Можно найти место для установки любых дополнительных карт SD, прочитав /proc/mounts (стандартный файл Linux) и сопоставив данные vold (/system/etc/vold.conf).И обратите внимание, что расположение, возвращаемое Environment.getExternalStorageDirectory(), может не отображаться в конфигурации vold (на некоторых устройствах это внутреннее хранилище, которое не может быть отключено), но все равно должно быть включено в список.Однако мы не нашли хорошего способа описать их пользователю .

7 голосов
/ 23 сентября 2014

Я пробую все решения в этой теме на этот раз.Но все они некорректно работали на устройствах с одной внешней (съемной) и одной внутренней (несъемной) платами.Путь к внешней карте невозможно получить из команды 'mount', из файла 'proc / mounts' и т. Д.

И я создаю свое собственное решение (для Пауло Луана):

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();
6 голосов
/ 21 июня 2014

Если вы посмотрите на исходный код android.os.Environment, вы увидите, что Android сильно зависит от переменных среды для путей. Вы можете использовать переменную окружения "SECONDARY_STORAGE", чтобы найти путь к съемной SD-карте.

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

Пример использования:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
5 голосов
/ 08 августа 2014

Просто используйте это:

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