Найти местоположение съемной SD-карты


204

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

Пожалуйста, не путайте с External Storage .

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

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


Вот мое решение, которое работает до нуга: stackoverflow.com/a/40205116/5002496
Gokul NC

'Environment.getExternalStorageState () возвращает путь к внутренней точке монтирования SD, например "/ mnt / sdcard". " Ну, это не является внутренним в том смысле, что Android использует этот термин. Я считаю, что термин, который вы ищете, «не съемный».
LarsH

Ответы:


162

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-карту (не гарантировано) и для чего нужны правила. используя его, например, какой путь использовать для него.


ОБНОВИТЬ

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

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

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

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

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


4
И эта проблема становится все более актуальной, поскольку устройства HC и ICS выходят с точкой «ExternalStorageDirectory», а все остальное похоже на внутреннюю физическую память. Кроме того, большинство пользователей не имеют ни малейшего понятия, как определить местонахождение их sdcard в файловой системе.
Тони Маро

284
Таким образом, ваш ответ в основном «связаться с производителем». Не полезно.
dragonroot

6
Последняя часть ответа не совсем точна - действительно можно определить путь к SD-карте, следуя приведенным ниже ответам (scan / proc / mounts, /system/etc/vold.fstab и т. Д.).
Изучите OpenGL ES

8
@CommonsWare: Тем не менее, все еще не точно, что «нужно» связываться с производителем, когда есть решения, которые работают на многих устройствах, и, кроме того, сам SDK не совместим на всех устройствах, так что это не гарантия. Даже если эти решения не работают на всех устройствах, они работают на достаточном количестве устройств, так что многие приложения для Android на рынке используют эти методы, в частности, для определения пути к внешней SD-карте. Я считаю, что называть всех этих разработчиков глупыми и преждевременными - разве заказчик не является окончательным судьей?
Изучите OpenGL ES

5
@ CommonsWare Это достаточно справедливо, как идут дела. Я определенно согласен с вами, что разработчик не может предполагать, что это всегда будет работать везде, и что любой такой код не может гарантировать работу на всех устройствах или для всех версий Android. Надеюсь, это исправлено в SDK! Между тем, есть еще варианты, которые работают на многих устройствах и могут улучшить работу конечного пользователя, и учитывая выбор между 80% успеха и 0% успеха, я возьму 80%.
Изучите OpenGL ES

64

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

КОД:

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

1
протестировано с nexus 4, nexus s, galaxy s2, galaxy s3, htc desire =)
Ричард

2
Привет снова, Ричард - верьте или нет, я должен спросить: вы действительно пытались записывать и читать обратно в файл таким образом, а не просто получить каталоги? Вспомните нашу старую проблему "/ sdcard0"? Я попробовал этот код, и он не удался на S3, когда я попытался прочитать обратно в файл, который он записал. ... это очень странно ... и больно :))
Говард Паутц

10
Это не работает на устройствах, которые не имеют 2 SD-карт. Предполагается, что 1-й найденный является внутренним, а 2-й найден внешним ...
Caner

НЕ работает для USB-устройств, подключенных через кабель OTG на Nexus 5 и Nexus 7.
Khawar Raza

4
/system/etc/vold.fstab не доступен в Android 4.3+
Али

37

У меня было приложение, в ListPreferenceкотором пользователь должен был выбрать место, где он хотел что-то сохранить.

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

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

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

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

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


К вашему сведению, это не работает на Galaxy S3, 2 SD-карты, только одна указана в vold.conf
3c71

1
@ 3c71 - Можете ли вы выслать мне файлы vold и mount для Galaxy S3? Я подправлю код, чтобы покрыть это.
Барон

Galaxy S, все найденные пути не были записаны, странно. Было найдено два хранилища, по умолчанию / mnt / sdcard и / storage / sdcard0, оба не смогли проверить
пятое

1
Я подправил код, чтобы игнорировать файл mounts. Это было проблемой на устройствах Motorola и Samsung. Файл mounts не покрывал случай external_sd, но он указан в vold. Первоначальная версия моего класса сравнивала монтирования с vold и отброшенными предметами, которые не были общими для обоих. Возьмите обновленный класс по той же ссылке выше.
Барон

1
Спасибо, барон, это «ответ»; по крайней мере, полезный.
pstoppani

23

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

РЕДАКТИРОВАТЬ: вот более всеобъемлющий способ, который я создал, чтобы получить пути 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;
      }
    }

Это кажется очень хорошим ответом, но как интегрировать это в простое занятие? Есть несколько переменных не определены, как App, ContextCompact,EnvironmentCompact
Антонио

@Antonio ContextCompact, EnvironmentCompact доступны через библиотеку поддержки. App.global () - это просто контекст приложения, который я установил глобально, так как мне не нравится добавлять параметр Context везде.
разработчик Android

1
Большой! Работает для моего устройства v4.4 Samsung GT S Advance, надеюсь, что он будет работать для других
user25

@androiddeveloper Будет ли отредактированный ответ работать на всех устройствах и размерах SD-карт?
Rahulrr2602

1
Это отлично сработало для меня - должен быть принят ответ.
Парадокс

17

Чтобы извлечь все внешние хранилища (будь то 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-накопители.

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


2
Просто рассмотрите любого пользователя, не оставляющего комментарий, троллем, так что я проголосовал, чтобы компенсировать это. ;) Но я полагаю, что ваш метод довольно произвольный: как мы можем узнать, что пропуск этих «USB-накопителей», но сохранение всего остального на самом деле равносильно «sdcards», как было задано в вопросе? Также ваш предложенный вариант System.getenv("SECONDARY_STORAGE")может также относиться к некоторым ссылкам, так как он кажется недокументированным.
СЗ

1
Насколько я знаю, в Android API нет ссылки на стандартный метод для извлечения всех внешних хранилищ. Однако предложенный метод вовсе не является произвольным. В Android, как и в любой системе Unix / Linux, ВСЕ монтируемые устройства хранения хранятся / связаны в общем каталоге: «/ mnt» (стандартный каталог Unix / Linux для монтирования устройств хранения) или, в новейших версиях, «/ место хранения". Вот почему вы можете быть уверены, что найдете все SD-карты в этой папке.
Паоло Ровелли

1
Что касается метода System.getenv ("EXTERNAL_STORAGE"), у меня нет никакой ссылки, кроме страницы API (которая не объясняет многое): developer.android.com/reference/java/lang/… Я не смог найти ни одного официальная страница для системных переменных среды Android. Здесь, однако, вы можете найти их краткий список: herongyang.com/Android/…
Паоло Ровелли

Что я имел в виду, не будучи уверенным в SDCard, так это в том, что в /mntнем могут быть и другие fs-деревья, а не только SD-карты и USB-накопители. Ваш код также будет перечислять любые внутренние (возможно, даже виртуальные) монтирования файловой системы, если я правильно понимаю, в то время как вопрос требует только sdcards .
СЗ

1
Понимаю. Да ты прав. С моим методом вы получите также внутреннюю (несъемную) память SD.
Паоло Ровелли

15

Как и Ричард, я также использую файл / 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;
    }    
}

Спасибо. Работал отлично. И мне нравится, как вы сделали StorageInfo не изменяемым. С другой стороны printStackTrace? Когда у нас есть android.util.Log.e?
Мартин

1
НЕ работает для USB-устройств, подключенных через кабель OTG на Nexus 5 и Nexus 7.
Khawar Raza

1
Я не могу использовать это для записи файла на SDCard
Eu Vid

та же проблема, что @EuVid работает на VM / AVD, но не на оборудовании
шпион

11

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


ИМХО, использование mountболее совместимо, чем чтение /procфайловой системы. Проблема в том, что SD-карта не обязательно отформатирована как FAT. Кроме того, точка монтирования карты может варьироваться от ПЗУ к ПЗУ. Кроме того, может быть несколько других разделов VFAT ...
borisstr

1
@borisstr: Хм, на самом деле Android использует vold , поэтому уместно посмотреть и на его конфигурацию.
Ян Худек

Файл кода, которым я поделился из моего поста выше, включает метод описания обнаруженных корневых путей для пользователя. Посмотрите на метод setProperties () .
Барон

1
@borisstr, на самом деле, нет, чтение / proc / mounts является более переносимым на устройствах Android, чем запуск mountисполняемого файла, тем более что запуск исполняемых файлов не рекомендуется.
Крис Страттон

7

Я пробую все решения в этой теме на этот раз. Но все они не работали правильно на устройствах с одной внешней (съемной) и одной внутренней (несъемной) платами. Путь к внешней карте невозможно получить из команды '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

Если вы посмотрите на исходный код, 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

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

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)

На некоторых устройствах SECONDARY_STORAGEесть несколько путей, разделенных двоеточием (":"). Вот почему я разделил строку (см. Мой ответ выше).
Джаред Раммлер

Они оба возвращают ноль для меня.
Тим Купер

5

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

К универсальному способу , если вы имеете в виду официальный пути; да, есть один

На уровне API 19, то есть в Android версии 4.4 Kitkat, они добавили File[] getExternalFilesDirs (String type)в Contextкласс, который позволяет приложениям хранить данные / файлы на картах Micro SD.

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

getExternalFilesDirs (тип String) возвращает абсолютные пути к каталогам приложений для всех общих / внешних устройств хранения. Это означает, что он вернет пути к внутренней и внешней памяти. Как правило, второй возвращаемый путь - это путь к карте памяти microSD (если есть).

Но учтите, что

Общее хранилище может быть не всегда доступно, поскольку съемный носитель может быть извлечен пользователем. Состояние носителя можно проверить с помощью getExternalStorageState(File).

Эти файлы не обеспечивают безопасность. Например, любое приложение-холдинг WRITE_EXTERNAL_STORAGEможет записывать в эти файлы.

Терминология внутреннего и внешнего хранилища в соответствии с Google / официальной документацией по Android сильно отличается от того, что мы думаем.


«Терминология внутреннего и внешнего хранилища в соответствии с Google / официальной документацией по Android сильно отличается от того, что мы думаем». Да, фактически название вопроса уточняет, что ОП спрашивает о съемной SD-карте. getExternalFilesDirs()часто возвращает SD-карты, которые не являются съемными, поэтому нет, это не универсальный способ найти местоположение съемной SD-карты.
LarsH

«getExternalFilesDirs (тип String) возвращает абсолютные пути к каталогам приложений на всех общих / внешних устройствах хранения. Это означает, что он будет возвращать пути как во внутреннюю, так и во внешнюю память». Эта пара предложений очень вводит в заблуждение, потому что для того, чтобы они оба были правдивыми, «внешний» должен означать две разные и противоречивые вещи.
LarsH

4

Вот способ, которым я использую, чтобы найти внешнюю карту. Используйте команду mount cmd return, а затем проанализируйте часть 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.
        }
    }
}
}

4

Это решение учитывает тот факт, что System.getenv("SECONDARY_STORAGE")бесполезно с Зефир.

Протестировано и работает над:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - сток)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - сток)
  • Samsung Galaxy S4 (Android 4.4 - сток)
  • Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1 - сток)

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

2

Поскольку мой оригинальный ответ выше, сканирование vold больше не подходит для разных производителей.

Я разработал более надежный и простой метод.

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

Корни будут содержать все доступные для записи корневые каталоги в системе, включая любые USB-подключенные USB-устройства.

ПРИМЕЧАНИЕ. Для метода canWrite требуется разрешение android.permission.WRITE_EXTERNAL_STORAGE.


Метод isSymlink (File) не определен для типа new FileFilter () {}
Омид Омиди

Любая идея, если это не удастся найти внешние SD-карты на Android 4.4 из-за canWrite?
Энтони

Это, конечно, проще, чем ваш другой метод, но надежен ли он? Например, я читал, что на некоторых устройствах Samsung /external_sdэто внешняя карта microSD; на некоторых LG это /_ExternalSD; на некоторых устройствах это /sdcard. Может быть, последняя является символической ссылкой /storage/sdcard0или похожей, но будут ли эти другие действительно надежно защищены /storage/*и /mount/*?
LarsH

Кроме того, необходимо ли использовать pathname.canWrite()и требовать разрешения WRITE_EXTERNAL_STORAGE? Почему бы просто не позвонить pathname.canRead()?
LarsH

1

было так поздно, но, наконец, у меня появилось кое-что, что я проверил на большинстве устройств (по производителям и версиям для Android), работающих на Android 2.2+. если вы обнаружите, что он не работает, прокомментируйте его с именем вашего устройства. я починю это. если кому-то интересно, я объясню, как это работает.

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

    }

}

Эй, downvoter, пожалуйста, попробуйте сначала. Если он не работает, то прокомментируйте ваше устройство. мы используем более тысячи устройств с Android 2.2+
Ajeet47

Ваш класс дает мне / mnt / media_rw / extSdCard на Samsung Galaxy S4, GT-I9500, Android 5.0.1 (устройство НЕ рутировано). Но в папке / mnt / media_rw с ES File Manager ничего не видно ...
isabsent

@isabsent use if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {File [] file = context.getExternalFilesDirs (null); возвращаемое file.length> 1? file [1]: null; }
Ajeet47

Что вы думаете о stackoverflow.com/a/27197248/753575 ? Является ли этот подход более полным для случая Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
isabsent

да. гарантируется, что вы получите значимый путь из context.getExternalFilesDirs (null), но вы обрезали его для корневого пути (он вернет путь для каталога вашего приложения. обрежьте его на "/ Android")
Ajeet47

1

Написав код ниже, вы получите местоположение:

/ Хранение / 663D-554E / Android / данные / app_package_name / файлы /

который хранит данные вашего приложения в / android / data location внутри sd_card.

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

list[1]+"/fol" 

для получения местоположения передайте 0 для внутреннего и 1 для SDCard в массив файлов.

Я тестировал этот код на устройстве moto g4 plus и Samsung (все работает нормально).

надеюсь, что это может быть полезным.


Иногда путь SD-карты отсутствует в индексе 1, я видел случаи, когда он был в индексе 0. Лучше следовать чему-то другому
Рагхав Сатьядев

1

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

Пожалуйста, дайте мне знать, если вы столкнетесь с какими-либо случаями, когда это не работает.

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

  • Не забудьте <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />в манифесте. А на уровне API 23 и выше обязательно используйте checkSelfPermission/ requestPermissions.
  • Установите KNOWNFILE = "myappfile", если вы ожидаете найти файл или папку на SD-карте. Это делает обнаружение более точным.
  • Очевидно, вы захотите кэшировать значение, findSdCardPath(),а не пересчитывать его каждый раз, когда вам это нужно.
  • В Log.d()приведенном выше коде есть куча logging ( ). Это помогает диагностировать любые случаи, когда правильный путь не найден. Прокомментируйте это, если вы не хотите регистрироваться.

Downvoters, вы можете предложить способ улучшения этого ответа?
LarsH

1

Единственное рабочее решение, которое я нашел, было это, которое использует отражение

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

Я лично не предпочитаю использовать отражение, потому что Google не ценит обратную совместимость в новых версиях Android!
Behrouz.M

0

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


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


0

Я создал метод utils, чтобы проверить, доступна ли SD-карта на устройстве или нет, и получить путь SD-карты на устройстве, если она доступна.

Вы можете скопировать 2 метода ниже в класс вашего проекта, который вам нужен. Вот и все.

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

-1

Он работает для всех внешних устройств, но убедитесь, что вы получаете только имя папки внешнего устройства, а затем вам нужно получить файл из заданного местоположения, используя класс 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;
    }

Вызов:

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

                }
            }
        }

Получение доступа к внешнему хранилищу

Для чтения или записи файлов на внешнее хранилище ваше приложение должно получить системные разрешения READ_EXTERNAL_STORAGE или WRITE_EXTERNAL_STORAGE . Например:

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

-2

/ sdcard => Внутреннее хранилище (это символическая ссылка, но она должна работать)

/ mnt / extSdCard => Внешняя SDCard

Это для Samsung Galaxy S3

Вы, вероятно, можете рассчитывать на то, что это верно для большинства ... однако дважды проверьте!


8
У меня было несколько разных телефонов Android, около половины из которых Samsung, и я никогда не видел, чтобы это местоположение использовалось. Это может быть правдой на S3, но говорить «вы, вероятно, можете рассчитывать на то, что это верно для большинства», совершенно неправильно.
Geobits

неправильно. /sdcardэто символическая ссылка на внешний на моем Sony 2305.
jiggunjer

2
Разве я не говорил, что это не так?
robbyoconnor
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.