Знайдіть місцезнаходження знімної SD-карти


204

Чи існує універсальний спосіб знайти місце зовнішньої SD-карти?

Будь ласка, не плутайте з зовнішнім сховищем .

Environment.getExternalStorageState()повертає шлях до внутрішньої точки монтажу SD, наприклад "/ mnt / sdcard". Але питання стосується зовнішньої СД. Як я можу отримати шлях на зразок "/ mnt / sdcard / external_sd" (він може відрізнятися від пристрою до пристрою)?

Я думаю, я закінчу фільтруванням виводу mountкоманди за назвою файлової системи. Але я не впевнений, що цей спосіб достатньо надійний.


Ось моє рішення, яке працює до Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

'Environment.getExternalStorageState () повертає шлях до внутрішньої точки монтажу SD типу "/ mnt / sdcard". " Ну, це не внутрішнє в тому сенсі, що Android використовує цей термін. Я вважаю, що термін, який ви шукаєте, "не можна видалити".
ЛарШ

Відповіді:


162

Environment.getExternalStorageState() повертає шлях до внутрішньої точки кріплення SD типу "/ mnt / sdcard"

Ні, Environment.getExternalStorageDirectory()стосується того, що виробник пристрою вважав "зовнішнім сховищем". На деяких пристроях це знімні носії, як-от SD-карта. На деяких пристроях це частина спалаху на пристрої. Тут "зовнішнє зберігання" означає "речі, доступні через режим масового зберігання USB, коли вони встановлені на хост-машині", принаймні для Android 1.x та 2.x.

Але питання стосується зовнішньої СД. Як отримати шлях на зразок "/ 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" і все інше, як це у внутрішнє фізичне зберігання. На додаток, більшість користувачів абсолютно не мають поняття, як знайти, де їхня карта SDD є у файловій системі.
Тоні Маро

283
Тож ваша відповідь - це «зв’яжіться з виробником». Не корисно.
бабок

6
Остання частина відповіді не зовсім точна - дійсно можливо виявити шлях SD-карти, дотримуючись відповідей нижче цього (сканування / 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-й знайдений внутрішній, а другий - зовнішній ...
Caner

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

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

37

У мене був додаток, який використовував a, ListPreferenceде користувачеві потрібно було вибрати місце, де він хотів щось зберегти.

У цьому додатку я просканував /proc/mountsі /system/etc/vold.fstabвиявив точки монтажу sdcard. Я зберігав точки монтажу з кожного файлу у два окремих ArrayLists.

Потім я порівняв один список з іншим і відкинув предмети, які не були в обох списках. Це дало мені список кореневих шляхів до кожного sdcard.

Звідти, я перевірив шляху з File.exists(), File.isDirectory()і File.canWrite(). Якщо будь-який із цих тестів був помилковим, я відкинув цей шлях зі списку.

Що б не залишилося у списку, я перетворив на String[]масив, щоб його можна було використовувати за ListPreferenceатрибутом значень.

Переглянути код можна тут: http://sapienmobile.com/?p=204


FYI, це не працює на Galaxy S3, 2 SD-картах, лише одна перерахована в vold.conf
3c71

1
@ 3c71 - Чи можете ви надіслати мені файли vold та кріплення для Galaxy S3? Я налаштувати код, щоб покрити його.
Барон

У Galaxy S всі знайдені шляхи не були записані, дивні. Знайдено два сховища, за замовчуванням / mnt / sdcard та / storage / sdcard0, обидва не вдалося перевірити
5

1
Я налаштував код, щоб ігнорувати файл mounts. Це було проблемою на пристроях Motorola та Samsung. Файл mounts не охоплював регістр external_sd, але він IS вказаний у 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/

EDIT: Ось більш створений я спосіб створити шляхи 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 ()" - це лише контекст програми, який я встановив у всьому світі, оскільки мені не подобається всюди додавати параметр контексту.
андроїд розробник

1
Чудово! Працює для мого пристрою v4.4 Samsung GT S Advance, сподіваюся, він буде працювати для інших
користувач25

@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")також можуть мати деякі посилання, оскільки це здається недокументованим.
Sz.

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/…
Паоло

Що я мав на увазі, не будучи впевненим у картках sdcards, - це те, що /mntтам можуть бути і різні інші дерева дерев, а не лише SD-карти та USB-накопичувачі. Ваш код також перелічить будь-які внутрішні (можливо, навіть віртуальні) кріплення файлової системи, якщо я правильно це розумію, тоді як питання хоче лише sdcards .
Sz.

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. Крім того, точка монтажу картки може відрізнятися від ROM до ROM. Також може бути кілька інших розділів VFAT ...
borisstr

1
@borisstr: Хм, насправді Android використовує vold , тому дивлячись на його конфігурацію також доречно.
Ян Худек

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

1
@borisstr, насправді ні, читання / proc / mount є більш портативним на пристроях 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класу, що дозволяє програмам зберігати дані / файли на мікро SD картах.

Android 4.4 - це перший реліз платформи, який фактично дозволив додаткам використовувати SD-карти для зберігання. Будь-який доступ до SD-карт до рівня 19 API був за допомогою приватних непідтримуваних API.

getExternalFilesDirs (тип рядка) повертає абсолютні шляхи до каталогів конкретних додатків на всіх спільних / зовнішніх пристроях зберігання даних. Це означає, що він поверне шляхи до внутрішньої та зовнішньої пам'яті. Як правило, другим повернутим шляхом буде шлях зберігання карти microSD (якщо такий є).

Але зауважте,

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

За цими файлами не забезпечується захист. Наприклад, будь-який холдинг додатків WRITE_EXTERNAL_STORAGEможе записувати в ці файли.

Термінологія внутрішнього та зовнішнього зберігання згідно з Google / офіційними документами Android сильно відрізняється від того, що ми думаємо.


"Термінологія внутрішнього та зовнішнього зберігання згідно з Google / офіційними документами Android сильно відрізняється від того, що ми думаємо". Так, насправді назва питання пояснює, що ОП запитує про знімну SD-карту. getExternalFilesDirs()часто повертає SD-карти, які не є знімними, тому ні, це не універсальний спосіб пошуку місця розташування знімної SD-карти.
LarsH

"getExternalFilesDirs (тип String) повертає абсолютні шляхи до специфічних каталогів додатків на всіх спільних / зовнішніх запам'ятовуючих пристроях. Це означає, що повертає шляхи до внутрішньої та зовнішньої пам'яті." Ця пара речень є дуже оманливою, оскільки для того, щоб вони були істинними, "зовнішні" повинні означати дві різні і суперечливі речі.
LarsH

4

Ось як я використовую для пошуку зовнішню карту. Використовуйте повернення 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);
    }
});

root буде містити всі кореневі каталоги, що записуються в системі, включаючи будь-які usb-підключені пристрої.

ПРИМІТКА. Метод canWrite потребує дозволу android.permission.WRITE_EXTERNAL_STORAGE.


Метод isSymlink (File) не визначений для нового типу FileFilter () {}
Omid Omidi

Будь-яка ідея, якщо це не вдасться знайти зовнішні 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;
        }

    }

}

Здравствуйте, спочатку спробуйте. Якщо це не працює, то прокоментуйте свій пристрій. ми використовуємо його понад тисячу пристроїв на платформі 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 використовувати if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {File [] file = context.getExternalFilesDirs (null); return file.length> 1? file [1]: null; }
Ajeet47

Що ви думаєте про stackoverflow.com/a/27197248/753575 ? Чи є такий підхід більш вичерпним для випадку Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
isabsent

так. це гарантовано, що ви отримаєте змістовний шлях з контексту.getExternalFilesDirs (null), але у вас є обрізка його для кореневого шляху (він поверне шлях для каталогу додатків.
Обріжте

1

Написавши код нижче, ви отримаєте місцезнаходження:

/ зберігання / 663D-554E / Android / дані / app_package_name / файли /

який зберігає дані вашої програми за адресою / android / data всередині 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" />в маніфесті. А на рівні 23 і вище API обов'язково використовуйте checkSelfPermission/ requestPermissions.
  • Встановіть KNOWNFILE = "myappfile", якщо на SD-картці є файл або папка, яку ви очікуєте. Це робить виявлення більш точним.
  • Очевидно, ви хочете кешувати значення, findSdCardPath(),а не перераховувати його кожного разу, коли вам це потрібно.
  • У Log.d()наведеному вище коді є купа журналу ( ). Це допомагає діагностувати будь-які випадки, коли правильний шлях не знайдений. Прокоментуйте це, якщо ви не хочете входити в систему.

Чи можете ви запропонувати спосіб покращити цю відповідь?
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!
Беруз.М

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

Я створив метод утиліти, щоб перевірити, чи 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, але сказати, що "ви, ймовірно, можете покластися на це, що це більшість правдивих", абсолютно неправильно.
Геобіт

неправильно. /sdcardє символьним посиланням на зовнішній на моїй sony 2305.
jiggunjer

2
Хіба я не сказав, що це може бути не так?
robbyoconnor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.