Znajdź lokalizację wymiennej karty SD


204

Czy istnieje uniwersalny sposób na znalezienie lokalizacji zewnętrznej karty SD?

Proszę nie mylić z pamięcią zewnętrzną .

Environment.getExternalStorageState()zwraca ścieżkę do wewnętrznego punktu montowania SD, na przykład „/ mnt / sdcard”. Ale pytanie dotyczy zewnętrznego SD. Jak uzyskać ścieżkę typu „/ mnt / sdcard / external_sd” (może się różnić w zależności od urządzenia)?

Myślę, że skończę z filtrowaniem danych wyjściowych mountpolecenia według nazwy systemu plików. Ale nie jestem pewien, czy ten sposób jest wystarczająco solidny.


Oto moje rozwiązanie, które działa do Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

„Environment.getExternalStorageState () zwraca ścieżkę do wewnętrznego punktu podłączenia SD, np.„ / Mnt / sdcard ”.” Cóż, to nie jest wewnętrzne w tym sensie, że Android używa tego terminu. Uważam, że poszukiwanego terminu nie można usunąć.
LarsH

Odpowiedzi:


162

Environment.getExternalStorageState() zwraca ścieżkę do wewnętrznego punktu montowania SD, np. „/ mnt / sdcard”

Nie, Environment.getExternalStorageDirectory() odnosi się do wszystkiego, co producent urządzenia uznał za „pamięć zewnętrzną”. Na niektórych urządzeniach są to nośniki wymienne, takie jak karta SD. W niektórych urządzeniach jest to część pamięci flash na urządzeniu. Tutaj „pamięć zewnętrzna” oznacza „rzeczy dostępne w trybie pamięci masowej USB po zamontowaniu na komputerze hosta”, przynajmniej dla Androida 1.xi 2.x.

Ale pytanie dotyczy zewnętrznego SD. Jak uzyskać ścieżkę typu „/ mnt / sdcard / external_sd” (może się różnić w zależności od urządzenia)?

Android nie ma pojęcia „zewnętrznego SD” poza pamięcią zewnętrzną, jak opisano powyżej.

Jeśli producent urządzenia wybrał, aby pamięć zewnętrzna była wbudowaną pamięcią flash, a także ma kartę SD, musisz skontaktować się z tym producentem, aby ustalić, czy możesz użyć karty SD (nie jest to gwarantowane) i jakie są zasady używając go, np. jakiej ścieżki użyć do tego.


AKTUALIZACJA

Dwie ważne rzeczy:

Po pierwsze, w Androidzie 4.4+ nie masz dostępu do zapisu na nośnikach wymiennych (np. „Zewnętrznej karcie SD”), z wyjątkiem miejsc na tych nośnikach, które mogą zostać zwrócone przez getExternalFilesDirs()i getExternalCacheDirs(). Zobacz doskonałą analizę Dave'a Smitha ten temat, szczególnie jeśli potrzebujesz szczegółów niskiego poziomu.

Po drugie, aby nikt nie miał wątpliwości, czy dostęp do nośników wymiennych jest w inny sposób częścią zestawu SDK systemu Android, oto ocena Dianne Hackborn :

... pamiętaj: do Android 4.4 oficjalna platforma Android nie obsługiwała kart SD w wszystko z wyjątkiem dwóch przypadków szczególnych: starej szkoły, gdzie układ pamięci jest pamięć zewnętrzna karta SD (który nadal jest obsługiwany przez platformę dzisiaj) oraz niewielką funkcję dodaną do Androida 3.0, która skanowałaby dodatkowe karty SD i dodawała je do dostawcy mediów i dawała aplikacjom dostęp tylko do odczytu do ich plików (co jest nadal obsługiwane na platformie).

Android 4.4 to pierwsza wersja platformy, która pozwoliła aplikacjom na używanie kart SD do przechowywania danych. Każdy dostęp do nich wcześniej był przez prywatne, nieobsługiwane interfejsy API. Mamy teraz na platformie dość bogaty interfejs API, który pozwala aplikacjom na korzystanie z kart SD w obsługiwany sposób, na lepsze sposoby niż były wcześniej: mogą swobodnie korzystać z obszaru pamięci specyficznego dla aplikacji, nie wymagając żadnej uprawnienia w aplikacji i może uzyskiwać dostęp do innych plików na karcie SD, o ile przechodzą one przez selektor plików, ponownie bez potrzeby posiadania specjalnych uprawnień.


4
Problem ten staje się coraz większym problemem, gdy urządzenia HC i ICS wychodzą z tego, że „ExternalStorageDirectory” i wszystko inne podoba się to wewnętrznej pamięci fizycznej. Co więcej, większość użytkowników absolutnie nie ma pojęcia, jak zlokalizować swoją kartę SD w systemie plików.
Tony Maro,

283
Zatem twoja odpowiedź brzmi „skontaktuj się z producentem”. Nieużyteczny.
Dragonroot

6
Ostatnia część odpowiedzi nie jest całkiem dokładna - rzeczywiście można wykryć ścieżkę karty SD, postępując zgodnie z odpowiedziami poniżej (skanowanie / proc / mounts, /system/etc/vold.fstab itp.).
Dowiedz się OpenGL ES,

8
@CommonsWare: Niemniej jednak nadal nie jest dokładne, że „trzeba” skontaktować się z producentem, gdy istnieją rozwiązania działające na wielu urządzeniach, a poza tym sam SDK nie jest spójny na wszystkich urządzeniach, więc nie ma gwarancji. Nawet jeśli te rozwiązania nie działają na wszystkich urządzeniach, działają na wystarczającej liczbie urządzeń, aby wiele aplikacji Android na rynku polegało na tych technikach, między innymi, do wykrywania ścieżki zewnętrznej karty SD. Sądzę, że nazwanie wszystkich tych programistów głupcami jest trochę surowe i przedwczesne - czy klient z pewnością nie jest ostatecznym sądem?
Naucz się OpenGL ES

5
@CommonsWare To jest dość sprawiedliwe, w miarę upływu czasu. Zdecydowanie zgadzam się z Tobą, że programista nie może założyć, że zawsze będzie to działać wszędzie i że nie można zagwarantować, że taki kod będzie działał na wszystkich urządzeniach lub wszystkich wersjach Androida. Mam nadzieję, że zostanie naprawione w SDK! W międzyczasie nadal istnieją opcje, które działają na wielu urządzeniach i mogą poprawić komfort użytkownika końcowego, a biorąc pod uwagę wybór między sukcesem 80% a sukcesem 0%, wezmę 80%.
Naucz się OpenGL ES

64

Wymyśliłem następujące rozwiązanie w oparciu o niektóre odpowiedzi tutaj.

KOD:

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

STOSOWANIE:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);

1
testowane z Nexusem 4, Nexusem S, Galaktyką S2, Galaktyką S3, HTC Desire =)
Richard

2
Cześć, Richard - uwierz lub nie, muszę zapytać: czy próbowałeś napisać i w ten sposób ponownie przeczytać plik, a nie tylko dostać reż. Czy pamiętasz nasz stary problem „/ sdcard0”? Próbowałem tego kodu i nie udało się go na S3, gdy próbowałem odczytać z powrotem plik, który napisał. ... to jest bardzo dziwne ... i bolesne :))
Howard Pautz

10
Nie działa to na urządzeniach, które nie mają 2 kart SD. Zakłada się, że pierwsze znalezisko jest wewnętrzne, a drugie znalezione - zewnętrzne ...
Caner

NIE działało w przypadku urządzeń USB podłączonych kablem OTG do Nexusa 5 i Nexusa 7.
Khawar Raza

4
/system/etc/vold.fstab nie jest dostępny w Androidzie 4.3+
Ali

37

Miałem aplikację, w ListPreferencektórej użytkownik musiał wybrać lokalizację, w której chciał coś zapisać.

W tej aplikacji skanowałem /proc/mountsi szukałem /system/etc/vold.fstabpunktów montowania sdcard. Przechowałem punkty montowania z każdego pliku w dwóch osobnychArrayList .

Następnie porównałem jedną listę z pozostałymi i odrzuconymi elementami, których nie było na obu listach. To dało mi listę ścieżek głównych do każdej karty SD.

Stamtąd, ja testowałem ścieżek File.exists(), File.isDirectory()i File.canWrite(). Jeśli którykolwiek z tych testów był fałszywy, odrzuciłem tę ścieżkę z listy.

Cokolwiek pozostało na liście, przekonwertowałem na String[]tablicę, aby mogła zostać wykorzystana przez ListPreferenceatrybut wartości.

Możesz zobaczyć kod tutaj: http://sapienmobile.com/?p=204


Do twojej wiadomości, to nie działa na Galaxy S3, 2 karty SD, tylko jedna wymieniona w vold.conf
3c71

1
@ 3c71 - Czy możesz wysłać mi volda i montuje pliki dla Galaxy S3? Poprawię kod, aby go zakrył.
Baron

Galaxy S, wszystkie znalezione ścieżki nie były zapisywalne, dziwne. Znaleziono dwie pamięci, domyślną / mnt / sdcard i / storage / sdcard0, obie nie zostały przetestowane
piąta

1
Poprawiłem kod, aby zignorować plik mounts. To był problem na urządzeniach Motorola i Samsung. Plik mounts nie obejmował przypadku external_sd, ale jest wymieniony w wersji vold. Pierwsza wersja mojej klasy porównywała wierzchowce z vold i odrzuconymi przedmiotami, które nie były wspólne dla obu. Pobierz zaktualizowaną klasę z tego samego linku powyżej.
Baron

1
Dzięki Baron, to jest „odpowiedź”; przynajmniej przydatny.
pstoppani

23

Możesz spróbować użyć funkcji biblioteki pomocniczej o nazwie 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());

Pierwszy to podstawowa pamięć zewnętrzna, a reszta to prawdziwe ścieżki kart SD.

Powodem wielu „.getParentFile ()” jest przejście do innego folderu, ponieważ pierwotna ścieżka to

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

EDYCJA: oto bardziej wszechstronny sposób, który stworzyłem, aby uzyskać ścieżki kart 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;
      }
    }

Wydaje się to bardzo dobrą odpowiedzią, ale jak zintegrować to w proste działanie? Istnieje kilka zmiennych niezdefiniowane, jak App, ContextCompact,EnvironmentCompact
Antonio

@Antonio ContextCompact, EnvironmentCompact są dostępne za pośrednictwem biblioteki wsparcia. „App.global ()” to tylko kontekst aplikacji, który ustawiłem globalnie, ponieważ nie lubię dodawać parametru Context wszędzie.
programista Androida

1
Wspaniały! Działa z moim urządzeniem v4.4 Samsung GT S Advance, mam nadzieję, że będzie działać dla innych
25

@androiddeveloper Czy edytowana odpowiedź będzie działać dla wszystkich rozmiarów urządzeń i kart SD?
Rahulrr2602,

1
To działało idealnie dla mnie - powinna być zaakceptowana odpowiedź.
Paradoks

17

Aby odzyskać wszystkie zewnętrzne magazyny (bez względu na to, czy są to karty SD, czy wewnętrzne nieusuwalne magazyny ), możesz użyć następującego kodu:

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

Alternatywnie możesz użyć System.getenv („EXTERNAL_STORAGE”), aby pobrać główny katalog zewnętrznej pamięci masowej (np. „/ Storage / sdcard0” ) i System.getenv („SECONDARY_STORAGE”), aby pobrać listę wszystkich katalogów dodatkowych (np. „ / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB ” ). Pamiętaj, że również w tym przypadku możesz przefiltrować listę katalogów dodatkowych, aby wykluczyć dyski USB.

W każdym razie należy pamiętać, że korzystanie ze ścieżek zakodowanych na stałe jest zawsze złym podejściem (szczególnie, gdy każdy producent może to zmienić z zadowoleniem).


2
Zastanów się, czy każdy downvoter, który nie zostawia komentarza, jest trollem, więc głosowałem za jego zrekompensowaniem. ;) ALE, wydaje mi się, że twoja metoda jest raczej arbitralna: skąd możemy wiedzieć, że pomijanie tych „napędów USB”, ale zachowanie całej reszty jest równe „sdcard”, jak zadano w pytaniu? Twoja sugestia System.getenv("SECONDARY_STORAGE")może również dotyczyć niektórych odniesień, ponieważ wydaje się nieudokumentowana.
Sz.

1
O ile mi wiadomo, w interfejsie API Androida nie ma odniesienia do standardowej metody pobierania wszystkich zewnętrznych magazynów. Jednak proponowana metoda wcale nie jest arbitralna. W Androidzie, podobnie jak w każdym systemie Unix / Linux, WSZYSTKIE montowane urządzenia pamięci są przechowywane / połączone we wspólnym katalogu: „/ mnt” (standardowy katalog Unix / Linux do montażu urządzeń pamięci) lub, w najnowszych wersjach, „/ przechowywanie". Dlatego możesz być całkiem pewien, że znajdziesz wszystkie karty SD połączone w tym folderze.
Paolo Rovelli,

1
Jeśli chodzi o metodę System.getenv („EXTERNAL_STORAGE”), nie mam żadnych odniesień, a nie stronę API (która nie wyjaśnia zbyt wiele): developer.android.com/reference/java/lang/… Nie mogłem znaleźć żadnej oficjalna strona zmiennych środowiskowych systemu Android. Tutaj jednak można znaleźć ich krótką listę: herongyang.com/Android/…
Paolo Rovelli,

Nie mając pewności co do /mntkart SD, miałem na myśli to, że mogą tam być także inne drzewa fs, nie tylko karty SD i napędy USB. Twój kod wyświetlałby również informacje o wszelkich montowaniach wewnętrznych (być może nawet wirtualnych) systemów plików, jeśli dobrze to rozumiem, podczas gdy pytanie dotyczy tylko kart SD .
Sz.

1
Widzę. Tak masz rację. Za pomocą mojej metody będziesz odzyskiwać również wewnętrzne (nieusuwalne) pamięci SD.
Paolo Rovelli

15

Podobnie jak Richard, używam również pliku / proc / mounts , aby uzyskać listę dostępnych opcji przechowywania

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

Dzięki. Działał idealnie. Podoba mi się sposób, w jaki uczyniłeś StorageInfo niemodyfikowalnym. Z drugiej strony printStackTrace? Gdy mamy android.util.Log.e?
Martin

1
NIE działało w przypadku urządzeń USB podłączonych kablem OTG do Nexusa 5 i Nexusa 7.
Khawar Raza

1
Nie mogę tego użyć do zapisania pliku na karcie SD
Eu Vid

ten sam problem, co @EuVid działa na VM / AVD, ale nie na sprzęcie
szpieguje

11

Gdzie można zamontować dodatkowe karty SD, można dowiedzieć się, odczytując /proc/mounts(standardowy plik Linux) i porównując dane z voldem ( /system/etc/vold.conf). I zwróć uwagę, że zwrócona lokalizacja Environment.getExternalStorageDirectory()może nie pojawić się w konfiguracji Vold (na niektórych urządzeniach jest to pamięć wewnętrzna, której nie można odmontować), ale nadal musi zostać uwzględniona na liście. Nie znaleźliśmy jednak dobrego sposobu na opisanie ich użytkownikowi .


Imo, użycie mountjest bardziej kompatybilne niż czytanie /procsystemu plików. Problem polega na tym, że karta SD nie musi być sformatowana jako FAT. Ponadto punkt montowania karty może się różnić w zależności od ROM. Ponadto może istnieć kilka innych partycji VFAT ...
borisstr

1
@borisstr: Hm, tak naprawdę Android używa volda , więc sprawdzenie jego konfiguracji również jest właściwe.
Jan Hudec

Plik kodu, który udostępniłem z mojego powyższego postu, zawiera metodę opisywania wykrytych ścieżek użytkownika do użytkownika. Spójrz na metodę setProperties () .
Baron,

1
@borisstr, w rzeczywistości nie, czytanie / proc / mounts jest bardziej przenośne na urządzeniach z Androidem niż uruchamianie mountpliku wykonywalnego, zwłaszcza, że ​​odradza się uruchamianie plików wykonywalnych.
Chris Stratton,

7

Tym razem wypróbowuję wszystkie rozwiązania zawarte w tym temacie. Ale wszystkie z nich nie działały poprawnie na urządzeniach z jedną kartą zewnętrzną (wymienną) i jedną wewnętrzną (niewymienną). Ścieżka karty zewnętrznej jest niemożliwa do uzyskania z polecenia „mount”, z pliku „proc / mounts” itp.

I tworzę własne rozwiązanie (na temat Paulo Luana):

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

Jeśli spojrzysz na kod źródłowy android.os.Environment, zobaczysz, że Android w dużym stopniu opiera się na zmiennych środowiskowych dla ścieżek. Możesz użyć zmiennej środowiskowej „SECONDARY_STORAGE”, aby znaleźć ścieżkę do wymiennej karty 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;
}

Przykładowe użycie:

public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");

5

Po prostu użyj tego:

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)

Na niektórych urządzeniach SECONDARY_STORAGEjest kilka ścieżek oddzielonych dwukropkiem („:”). Właśnie dlatego podzieliłem String (patrz moja odpowiedź powyżej).
Jared Rummler,

Oba zwracają dla mnie wartość zerową.
Tim Cooper

5

Czy istnieje uniwersalny sposób na znalezienie lokalizacji zewnętrznej karty SD?

W sposób uniwersalny , jeśli masz na myśli sposób oficjalny; tak jest jeden.

W interfejsie API na poziomie 19, tj. W wersji Android KitKat 4.4, dodano File[] getExternalFilesDirs (String type)w Contextklasie, która pozwala aplikacjom przechowywać dane / pliki na kartach micro SD.

Android 4.4 to pierwsza wersja platformy, która pozwoliła aplikacjom na używanie kart SD do przechowywania danych. Jakikolwiek dostęp do kart SD przed 19 poziomem API odbywał się za pośrednictwem prywatnych, nieobsługiwanych interfejsów API.

getExternalFilesDirs (typ ciągu) zwraca ścieżki bezwzględne do katalogów specyficznych dla aplikacji na wszystkich współużytkowanych / zewnętrznych urządzeniach pamięci masowej. Oznacza to, że zwróci ścieżki zarówno do pamięci wewnętrznej, jak i zewnętrznej. Zasadniczo druga zwracana ścieżka byłaby przechowywania karty microSD (jeśli istnieje).

Ale zauważ, że

Pamięć współdzielona może nie zawsze być dostępna, ponieważ użytkownik może wysunąć nośnik wymienny. Stan nośnika można sprawdzić za pomocą getExternalStorageState(File).

Z tymi plikami nie jest egzekwowane bezpieczeństwo. Na przykład dowolne wstrzymanie aplikacji WRITE_EXTERNAL_STORAGEmoże zapisywać w tych plikach.

Terminologia dotycząca pamięci wewnętrznej i zewnętrznej według Google / oficjalnych dokumentów Androida jest zupełnie inna niż myślimy.


„Terminologia dotycząca pamięci wewnętrznej i zewnętrznej według Google / oficjalnych dokumentów Androida jest zupełnie inna niż myślimy”. Tak, w rzeczywistości tytuł pytania wyjaśnia, że ​​OP pyta o wymienną kartę SD. getExternalFilesDirs()często zwraca karty SD, których nie można wyjąć, więc nie, nie jest to uniwersalny sposób na znalezienie lokalizacji wymiennej karty SD.
LarsH

„getExternalFilesDirs (typ String) zwraca ścieżki bezwzględne do katalogów specyficznych dla aplikacji na wszystkich współużytkowanych / zewnętrznych urządzeniach pamięci. Oznacza to, że zwróci ścieżki zarówno do pamięci wewnętrznej, jak i zewnętrznej.” Ta para zdań jest bardzo myląca, ponieważ aby oba były prawdziwe, „zewnętrzny” musi oznaczać dwie różne i sprzeczne rzeczy.
LarsH

4

Oto sposób, w jaki używam do znalezienia karty zewnętrznej. Użyj polecenia mount cmd return, a następnie przeanalizuj część 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

To rozwiązanie radzi sobie z faktem, że System.getenv("SECONDARY_STORAGE")nie ma zastosowania w przypadku Marshmallow.

Testowane i pracujące nad:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - dostępny)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - dostępny)
  • Samsung Galaxy S4 (Android 4.4 - w magazynie)
  • Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1 - dostępny)

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

Od mojej pierwotnej odpowiedzi powyżej skanowanie volda nie jest już wykonalne u różnych producentów.

Opracowałem bardziej niezawodną i prostą metodę.

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 będzie zawierał wszystkie zapisywane katalogi root w systemie, w tym wszelkie urządzenia USB podłączone.

UWAGA: Metoda canWrite wymaga pozwolenia android.permission.WRITE_EXTERNAL_STORAGE.


Metoda isSymlink (File) jest niezdefiniowana dla typu new FileFilter () {}
Omid Omidi

Masz pomysł, czy nie uda się znaleźć zewnętrznych kart SD w Androidzie 4.4 z powodu CanWrite?
Anthony

Jest to z pewnością prostsze niż w przypadku innych metod, ale czy jest wiarygodne? Np. Przeczytałem, że na niektórych urządzeniach Samsung /external_sdjest zewnętrzna karta microSD; na niektórych LG to jest /_ExternalSD; na niektórych urządzeniach to /sdcard. Może to drugie jest dowiązaniem symbolicznym /storage/sdcard0lub podobnym, ale czy te inne naprawdę będą naprawdę niezawodnie objęte przez /storage/*i /mount/*?
LarsH

Czy konieczne jest także użycie pathname.canWrite()i wymaganie pozwolenia WRITE_EXTERNAL_STORAGE? Dlaczego nie po prostu zadzwonić pathname.canRead()?
LarsH

1

było już tak późno, ale w końcu dostałem coś, co przetestowałem na większości urządzeń (od wersji producenta i Androida) na Androida 2.2+. jeśli okaże się, że to nie działa, skomentuj je nazwą urządzenia. naprawię to. jeśli ktoś jest zainteresowany, wyjaśnię, jak to działa.

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

    }

}

Hej downvoter, spróbuj najpierw. Jeśli to nie działa, to skomentuj swoje urządzenie. korzystamy z niego ponad tysiąc urządzeń z Androidem
2.2+

Twoja klasa daje mi / mnt / media_rw / extSdCard na Samsung Galaxy S4, GT-I9500, Android 5.0.1 (urządzenie NIE jest zrootowane). Ale w folderze / mnt / media_rw z ES File Manager nic nie jest widoczne ...
isabsent

@ nieobecne użyj if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {Plik [] plik = kontekst.getExternalFilesDirs (null); return file.length> 1? file [1]: null; }
Ajeet47,

Co sądzisz o stackoverflow.com/a/27197248/753575 ? Czy to podejście jest bardziej kompleksowe w przypadku Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
isabsent

tak. masz gwarancję, że uzyskasz znaczącą ścieżkę z kontekstu.getExternalFilesDirs (null), ale masz przycinanie ścieżki katalogu głównego (zwróci ścieżkę do katalogu aplikacji. przyciąć ją na „/ Android”)
Ajeet47

1

Pisząc poniższy kod otrzymasz lokalizację:

/ storage / 663D-554E / Android / data / nazwa_pakietu aplikacji / pliki /

który przechowuje dane Twojej aplikacji w lokalizacji / android / data wewnątrz karty SD.

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

list[1]+"/fol" 

dla uzyskania położenia przekaż wartość 0 dla wewnętrznej i 1 dla sdcard do tablicy plików.

Testowałem ten kod na moto g4 plus i urządzeniu Samsung (wszystko działa dobrze).

mam nadzieję, że to może pomóc.


czasami ścieżka karty SD nie jest w indeksie 1, widziałem przypadki, w których była ona w indeksie 0. Lepiej podążać za czymś innym
Raghav Satyadev

1

Oto metoda, której używam do znalezienia wymiennej karty SD. Jest skomplikowany i prawdopodobnie przesadza w niektórych sytuacjach, ale działa na wielu wersjach Androida i producentach urządzeń, które testowałem w ciągu ostatnich kilku lat. Nie znam żadnych urządzeń od poziomu API 15, na którym nie znajduje karty SD, jeśli jest ona zamontowana. W większości przypadków nie zwróci wyników fałszywie dodatnich, szczególnie jeśli nadasz mu nazwę znanego pliku do wyszukania.

Daj mi znać, jeśli napotkasz przypadki, w których to nie działa.

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

  • Nie zapomnij <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />w manifeście. Na poziomie API 23 i wyższym upewnij się, że używasz checkSelfPermission/requestPermissions .
  • Ustaw KNOWNFILE = "myappfile", jeśli na karcie SD znajduje się plik lub folder. Dzięki temu wykrywanie jest dokładniejsze.
  • Oczywiście będziesz chciał buforować wartość findSdCardPath(), zamiast ją przeliczać za każdym razem, gdy jej potrzebujesz.
  • W Log.d()powyższym kodzie jest kilka funkcji logowania ( ). Pomaga zdiagnozować wszystkie przypadki, w których nie można znaleźć właściwej ścieżki. Skomentuj, jeśli nie chcesz się logować.

Downvoters, czy możesz zasugerować sposób na poprawienie tej odpowiedzi?
LarsH

1

Jedynym działającym rozwiązaniem, które znalazłem, było to, które wykorzystuje odbicie

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

Osobiście nie wolę używać refleksji, ponieważ Google nie docenia kompatybilności wstecznej w nowych wersjach Androida!
Behrouz.M

0

Nie wiem dlaczego, ale przed użyciem muszę wywołać funkcję .createNewFile () dla pliku utworzonego w publicznych katalogach pamięci. W ramach komentarzy do tej metody stwierdzono, że nie jest ona użyteczna. Oto próbka ...


 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

Stworzyłem metodę utils, aby sprawdzić, czy karta SD jest dostępna na urządzeniu, czy nie, i uzyskać ścieżkę karty SD na urządzeniu, jeśli jest dostępna.

Możesz skopiować 2 metody poniżej do klasy projektu, której potrzebujesz. To wszystko.

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

Działa na wszystkich urządzeniach zewnętrznych, ale upewnij się, że otrzymujesz tylko nazwę folderu zewnętrznego urządzenia, a następnie musisz pobrać plik z danej lokalizacji za pomocą klasy 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;
    }

Powołanie:

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

                }
            }
        }

Uzyskiwanie dostępu do pamięci zewnętrznej

Aby odczytywać lub zapisywać pliki w pamięci zewnętrznej, aplikacja musi uzyskać uprawnienia systemowe READ_EXTERNAL_STORAGE lub WRITE_EXTERNAL_STORAGE . Na przykład:

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

-2

/ sdcard => Pamięć wewnętrzna (to dowiązanie symboliczne, ale powinno działać)

/ mnt / extSdCard => Zewnętrzna karta Sd

To jest dla Samsung Galaxy S3

Prawdopodobnie możesz polegać na tym, że jest to prawdą w przypadku większości ... podwójnych czeków jednak!


8
Miałem kilka różnych telefonów z Androidem, około połowa z nich Samsung, i nigdy nie widziałem, aby ta lokalizacja była używana. To może być prawda na S3, ale powiedzenie „prawdopodobnie możesz polegać na tym, że jest to prawda dla większości” jest całkowicie błędne.
Geobits

źle. /sdcardjest dowiązaniem symbolicznym do zewnętrznego na moim Sony
2305.

2
Czy nie powiedziałem, że może nie być?
robbyoconnor
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.