Temukan lokasi kartu SD yang dapat dilepas

204

Apakah ada cara universal untuk menemukan lokasi kartu SD eksternal?

Tolong, jangan bingung dengan Penyimpanan Eksternal .

Environment.getExternalStorageState()mengembalikan path ke titik mount SD internal, seperti "/ mnt / sdcard". Tetapi pertanyaannya adalah tentang SD eksternal. Bagaimana cara mendapatkan jalur seperti "/ mnt / sdcard / external_sd" (ini mungkin berbeda dari perangkat ke perangkat)?

Saya kira saya akan mengakhiri dengan memfilter output dari mountperintah dengan nama filesystem. Tapi saya tidak yakin cara ini cukup kuat.

borisstr
sumber
Inilah solusi saya yang berfungsi hingga Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC
'Environment.getExternalStorageState () mengembalikan jalur ke titik pemasangan SD internal seperti "/ mnt / sdcard".' Yah, itu bukan internal dalam arti bahwa Android menggunakan istilah tersebut. Saya percaya istilah yang Anda cari adalah "tidak dapat dilepas."
LarsH

Jawaban:

162

Environment.getExternalStorageState() mengembalikan jalur ke titik mount SD internal seperti "/ mnt / sdcard"

Tidak, Environment.getExternalStorageDirectory()mengacu pada apa pun yang oleh produsen perangkat dianggap sebagai "penyimpanan eksternal". Pada beberapa perangkat, ini adalah media yang dapat dilepas, seperti kartu SD. Pada beberapa perangkat, ini adalah sebagian dari flash pada perangkat. Di sini, "penyimpanan eksternal" berarti "barang-barang yang dapat diakses melalui mode Penyimpanan Massal USB ketika dipasang pada mesin host", setidaknya untuk Android 1.x dan 2.x.

Tetapi pertanyaannya adalah tentang SD eksternal. Bagaimana cara mendapatkan path seperti "/ mnt / sdcard / external_sd" (ini mungkin berbeda dari perangkat ke perangkat)?

Android tidak memiliki konsep "SD eksternal", selain dari penyimpanan eksternal, seperti dijelaskan di atas.

Jika pabrikan perangkat telah memilih untuk memiliki penyimpanan eksternal menjadi flash on-board dan juga memiliki kartu SD, Anda harus menghubungi pabrikan itu untuk menentukan apakah Anda dapat menggunakan kartu SD (tidak dijamin) dan apa aturannya untuk menggunakannya, seperti jalur apa yang digunakan untuk itu.


MEMPERBARUI

Dua hal terbaru yang perlu diperhatikan:

Pertama, pada Android 4.4+, Anda tidak memiliki akses tulis ke media yang dapat dipindahkan (mis., "SD eksternal"), kecuali untuk lokasi mana pun di media yang mungkin dikembalikan oleh getExternalFilesDirs()dan getExternalCacheDirs(). Lihat analisis luar biasa Dave Smith mengenai hal ini, terutama jika Anda menginginkan detail tingkat rendah.

Kedua, jangan sampai ada orang yang berdalih tentang apakah akses media yang dapat dilepas atau tidak merupakan bagian dari Android SDK, di sini adalah penilaian Dianne Hackborn :

... perlu diingat: sampai Android 4.4, platform Android resmi belum mendukung kartu SD di sekali kecuali untuk dua kasus khusus: tata letak penyimpanan sekolah lama di mana penyimpanan eksternal adalah kartu SD (yang masih didukung oleh platform saat ini) , dan fitur kecil ditambahkan ke Android 3.0 di mana ia akan memindai kartu SD tambahan dan menambahkannya ke penyedia media dan memberikan aplikasi akses baca-saja ke file mereka (yang juga masih didukung di platform saat ini).

Android 4.4 adalah rilis pertama dari platform yang sebenarnya memungkinkan aplikasi untuk menggunakan kartu SD untuk penyimpanan. Akses apa pun ke mereka sebelum itu adalah melalui API pribadi yang tidak didukung. Kami sekarang memiliki API yang cukup kaya di platform yang memungkinkan aplikasi untuk menggunakan kartu SD dengan cara yang didukung, dengan cara yang lebih baik daripada sebelumnya: mereka dapat menggunakan area penyimpanan khusus aplikasi secara gratis tanpa memerlukan izin dalam aplikasi, dan dapat mengakses file lain pada kartu SD selama mereka pergi melalui pemilih file, lagi tanpa memerlukan izin khusus.

CommonsWare
sumber
4
Dan masalah ini menjadi lebih banyak masalah karena perangkat HC dan ICS keluar bahwa "ExternalStorageDirectory" dan segala sesuatu yang lain seperti itu ke penyimpanan fisik internal. Top it off bahwa sebagian besar pengguna sama sekali tidak tahu bagaimana menemukan di mana sdcard mereka berada di sistem file.
Tony Maro
284
Jadi jawaban Anda pada dasarnya adalah 'hubungi pabrikan'. Tidak berguna.
Dragonroot
6
Bagian terakhir dari jawabannya tidak cukup akurat - memang mungkin untuk mendeteksi jalur kartu SD dengan mengikuti jawaban di bawah ini (pemindaian / proc / mount, /system/etc/vold.fstab, dll ...).
Pelajari OpenGL ES
8
@CommonsWare: Meskipun demikian, masih belum akurat bahwa "kebutuhan" untuk menghubungi produsen ketika ada solusi yang bekerja pada banyak perangkat di luar sana, dan selain itu, SDK itu sendiri tidak konsisten pada semua perangkat sehingga tidak ada jaminan. Sekalipun solusi ini tidak bekerja pada semua perangkat, mereka bekerja pada cukup banyak perangkat yang banyak mengandalkan aplikasi Android di pasaran pada teknik ini, antara lain, untuk mendeteksi jalur kartu SD eksternal. Saya pikir itu agak keras dan prematur untuk menyebut semua pengembang ini bodoh - bukankah pelanggan pasti hakim terakhir untuk itu?
Pelajari OpenGL ES
5
@CommonsWare Itu cukup adil, seperti yang terjadi. Saya setuju dengan Anda bahwa pengembang tidak dapat menganggap ini akan selalu berfungsi di mana saja, dan bahwa kode semacam itu tidak dapat dijamin berfungsi di semua perangkat atau untuk semua versi Android. Semoga bisa diperbaiki di SDK! Sementara itu, masih ada opsi yang berfungsi pada banyak perangkat dan dapat meningkatkan pengalaman pengguna akhir, dan diberi pilihan antara keberhasilan 80% dan 0% sukses, saya akan mengambil 80%.
Pelajari OpenGL ES
64

Saya datang dengan solusi berikut berdasarkan beberapa jawaban yang ditemukan di sini.

KODE:

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

PEMAKAIAN:

Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
Richard
sumber
1
diuji dengan nexus 4, nexus s, galaxy s2, galaxy s3, htc desire =)
Richard
2
Hai lagi, Richard - percaya atau tidak, saya harus bertanya: apakah Anda benar-benar mencoba menulis dan membaca kembali file dengan cara ini, tidak hanya mendapatkan petunjuk? Ingat masalah "/ sdcard0" lama kami? Saya mencoba kode ini dan gagal pada S3 ketika saya mencoba membaca kembali file yang ditulisnya. ... ini sangat aneh ... dan menyakitkan :))
Howard Pautz
10
Ini gagal pada perangkat yang tidak memiliki 2 kartu SD. Diasumsikan 1 ditemukan internal, dan 2 ditemukan eksternal ...
Caner
TIDAK bekerja untuk perangkat USB yang terpasang melalui kabel OTG pada Nexus 5 dan Nexus 7.
Khawar Raza
4
/system/etc/vold.fstab tidak dapat diakses di android 4.3+
Ali
37

Saya memiliki aplikasi yang menggunakan ListPreferencetempat pengguna diminta untuk memilih lokasi tempat mereka ingin menyimpan sesuatu.

Dalam aplikasi itu, saya memindai /proc/mountsdan /system/etc/vold.fstabuntuk mount point sdcard. Saya menyimpan poin mount dari setiap file menjadi dua yang terpisahArrayList s .

Kemudian, saya membandingkan satu daftar dengan yang lain dan membuang barang-barang yang tidak ada di kedua daftar. Itu memberi saya daftar jalur root untuk setiap sdcard.

Dari sana, saya diuji jalan dengan File.exists(), File.isDirectory(), danFile.canWrite() . Jika salah satu dari tes itu salah, saya membuang jalur itu dari daftar.

Apa pun yang tersisa dalam daftar, saya mengkonversi ke String[]array sehingga dapat digunakan olehListPreference atribut nilai.

Anda dapat melihat kode di sini: http://sapienmobile.com/?p=204

Baron
sumber
FYI, ini tidak berfungsi pada Galaxy S3, 2 kartu SD, hanya satu yang terdaftar di vold.conf
3c71
1
@ 3c71 - Bisakah Anda mengirim saya fileold dan mount untuk Galaxy S3? Saya akan mengubah kode untuk menutupinya.
Baron
Galaxy S, semua jalur yang ditemukan tidak dapat ditulisi, aneh. Ada dua penyimpanan yang ditemukan, default / mnt / sdcard dan / storage / sdcard0, keduanya gagal diuji
kelima
1
Saya telah mengubah kode untuk mengabaikan file mounts. Itulah masalah pada perangkat Motorola dan Samsung. File mounts tidak mencakup case external_sd, tetapi itu IS terdaftar dalam vold. Versi awal kelas saya membandingkan mount dengan item vold dan dibuang yang tidak umum untuk keduanya. Raih kelas yang diperbarui dari tautan yang sama di atas.
Baron
1
Terima kasih Baron, ini adalah jawaban "the"; setidaknya yang bermanfaat.
pstoppani
23

Anda dapat mencoba menggunakan fungsi pustaka dukungan yang disebut 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());

Yang pertama adalah penyimpanan eksternal utama, dan sisanya seharusnya menjadi jalur SD-card nyata.

Alasan untuk beberapa ".getParentFile ()" adalah untuk naik folder lain, karena jalur aslinya adalah

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

EDIT: inilah cara yang lebih komprehensif yang saya buat, untuk mendapatkan jalur sd-card:

  /**
   * 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;
      }
    }
pengembang android
sumber
Tampaknya ini jawaban yang sangat bagus, tetapi bagaimana kita mengintegrasikan ini dalam kegiatan sederhana? Ada beberapa variabel tidak didefinisikan, seperti App, ContextCompact,EnvironmentCompact
Antonio
@Antonio ContextCompact, EnvironmentCompact tersedia melalui perpustakaan dukungan. "App.global ()" hanyalah konteks aplikasi, yang telah saya tetapkan secara global karena saya tidak suka menambahkan parameter Konteks di mana-mana.
pengembang android
1
Bagus! Berfungsi untuk perangkat tambang v4.4 Samsung GT S Advance, harap ini akan bekerja untuk orang lain
user25
@androiddeveloper Akankah jawaban yang diedit berfungsi untuk semua Perangkat dan Ukuran Kartu SD?
Rahulrr2602
1
Ini berfungsi dengan baik untuk saya - harus menjadi jawaban yang diterima.
Paradox
17

Untuk mengambil semua Penyimpanan Eksternal (apakah itu kartu SD atau penyimpanan internal yang tidak dapat dilepas ), Anda dapat menggunakan kode berikut:

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

Atau, Anda dapat menggunakan System.getenv ("EXTERNAL_STORAGE") untuk mengambil direktori Penyimpanan Eksternal utama (mis. "/ Storage / sdcard0" ) dan System.getenv ("SECONDARY_STORAGE") untuk menerima daftar semua direktori sekunder (mis. " / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB " ). Ingat bahwa, juga dalam hal ini, Anda mungkin ingin memfilter daftar direktori sekunder untuk mengecualikan drive USB.

Bagaimanapun, harap dicatat bahwa menggunakan jalur kode-keras selalu merupakan pendekatan yang buruk (khususnya ketika setiap produsen dapat mengubahnya sesuka hati).

Paolo Rovelli
sumber
2
Anggap saja downvoter yang tidak memberikan komentar troll, jadi saya memilih untuk mengkompensasinya. ;) TETAPI, saya kira metode Anda agak sewenang-wenang: bagaimana kita bisa tahu bahwa melewatkan "drive USB" itu tetapi menjaga semua yang lain benar-benar sama dengan "sdcard", seperti yang ditanyakan dalam pertanyaan? Saran Anda System.getenv("SECONDARY_STORAGE")juga bisa dilakukan dengan beberapa referensi, karena tampaknya tidak berdokumen.
Sz.
1
Sejauh yang saya tahu, di Android API tidak ada referensi metode standar untuk mengambil semua Penyimpanan Eksternal. Namun, metode yang diusulkan tidak sembarangan sama sekali. Di Android, seperti di setiap sistem Unix / Linux, SEMUA perangkat penyimpanan pemasangan disimpan / ditautkan di direktori umum: "/ mnt" (direktori standar Unix / Linux untuk pemasangan perangkat penyimpanan) atau, dalam versi terbaru, "/ penyimpanan". Itu sebabnya Anda bisa sangat yakin bahwa Anda akan menemukan semua kartu SD yang tertaut dalam folder ini.
Paolo Rovelli
1
Mengenai metode System.getenv ("EXTERNAL_STORAGE"), saya tidak memiliki referensi daripada halaman API (yang tidak menjelaskan banyak hal): developer.android.com/reference/java/lang/… Saya tidak dapat menemukan apa pun halaman resmi untuk variabel lingkungan sistem Android. Di sini, bagaimanapun, Anda dapat menemukan daftar pendek dari mereka: herongyang.com/Android/…
Paolo Rovelli
Yang saya maksud dengan tidak yakin tentang kartu sd adalah bahwa di /mntsana bisa ada berbagai pohon fs lainnya, juga, tidak hanya kartu SD dan drive USB. Kode Anda juga akan mencantumkan mount sistem file internal (bahkan mungkin virtual), jika saya memahaminya dengan benar, sementara pertanyaannya hanya menginginkan kartu sd saja .
Sz.
1
Saya melihat. Ya kamu benar. Dengan metode saya, Anda juga akan mengambil memori SD (non-removable) internal.
Paolo Rovelli
15

Seperti Richard, saya juga menggunakan file / proc / mounts untuk mendapatkan daftar opsi penyimpanan yang tersedia

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

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

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

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

    public static List<StorageInfo> getStorageList() {

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

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

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

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (buf_reader != null) {
                try {
                    buf_reader.close();
                } catch (IOException ex) {}
            }
        }
        return list;
    }    
}
Vitaliy Polchuk
sumber
Terima kasih. Bekerja dengan sempurna. Dan saya suka cara Anda membuat StorageInfo tidak bisa berubah. Di sisi lain printStackTrace? Kapan kita punya android.util.Log.e?
Martin
1
TIDAK bekerja untuk perangkat USB yang terpasang melalui kabel OTG pada Nexus 5 dan Nexus 7.
Khawar Raza
1
Saya tidak dapat menggunakan ini untuk menulis file pada SDCard
Eu Vid
masalah yang sama seperti @EuVid bekerja pada VM / AVD tetapi tidak pada perangkat keras
mata
11

Hal ini dimungkinkan untuk menemukan di mana kartu SD tambahan yang dipasang dengan membaca /proc/mounts(file standar Linux) dan cross-checking terhadap vold data ( /system/etc/vold.conf). Dan perhatikan, bahwa lokasi yang dikembalikan oleh Environment.getExternalStorageDirectory()mungkin tidak muncul dalam konfigurasi yang lama (di beberapa perangkat penyimpanan internal yang tidak dapat dilepas), tetapi masih harus dimasukkan dalam daftar. Namun kami tidak menemukan cara yang baik untuk menggambarkannya kepada pengguna .

Jan Hudec
sumber
Imo, penggunaan mountlebih kompatibel daripada membaca /procfilesystem. Masalahnya adalah kartu SD tidak perlu diformat sebagai FAT. Selain itu, titik pemasangan kartu dapat bervariasi dari ROM ke ROM. Juga, mungkin ada beberapa partisi VFAT lainnya ...
borisstr
1
@borisstr: Hm, sebenarnya Android menggunakan vold , jadi melihat konfigurasi itu juga tepat.
Jan Hudec
File kode yang saya bagikan dari posting saya di atas mencakup metode untuk menggambarkan jalur root yang ditemukan kepada pengguna. Lihatlah metode setProperties () .
Baron
1
@borisstr, sebenarnya, tidak, membaca / proc / mounts lebih portabel pada perangkat Android daripada meluncurkan mountexecutable, terutama karena launchable executable tidak disarankan.
Chris Stratton
7

Saya mencoba semua solusi di dalam topik ini saat ini. Tetapi semuanya tidak bekerja dengan benar pada perangkat dengan satu kartu eksternal (dapat dilepas) dan satu kartu internal (tidak dapat dilepas). Jalur kartu eksternal tidak mungkin diperoleh dari perintah 'mount', dari file 'proc / mounts' dll.

Dan saya membuat solusi saya sendiri (di Paulo Luan):

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

Jika Anda melihat kode sumber untuk android.os.EnvironmentAnda akan melihat bahwa Android sangat bergantung pada variabel lingkungan untuk jalur. Anda dapat menggunakan variabel lingkungan "SECONDARY_STORAGE" untuk menemukan jalur ke kartu sd yang dapat dilepas.

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

Contoh penggunaan:

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

Cukup gunakan ini:

String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
    Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
    Log.i("SECONDARY_STORAGE", secondary_sd)
Behrouz.M
sumber
Pada beberapa perangkat SECONDARY_STORAGEmemiliki beberapa jalur yang dipisahkan dengan titik dua (":"). Inilah sebabnya saya membagi String (lihat jawaban saya di atas).
Jared Rummler
Keduanya menghasilkan null untuk saya.
Tim Cooper
5

Apakah ada cara universal untuk menemukan lokasi kartu SD eksternal?

Secara universal , jika Anda maksud dengan cara resmi; ya ada satu.

Dalam API level 19 yaitu dalam versi Android 4.4 Kitkat, mereka telah menambahkan File[] getExternalFilesDirs (String type)diContext Kelas yang memungkinkan aplikasi untuk menyimpan data / file dalam kartu micro SD.

Android 4.4 adalah rilis pertama dari platform yang sebenarnya memungkinkan aplikasi untuk menggunakan kartu SD untuk penyimpanan. Setiap akses ke kartu SD sebelum API level 19 adalah melalui API pribadi yang tidak didukung.

getExternalFilesDirs (tipe String) mengembalikan jalur absolut ke direktori khusus aplikasi di semua perangkat penyimpanan bersama / eksternal. Ini berarti, itu akan mengembalikan jalur ke memori internal dan eksternal. Umumnya, jalur balik kedua balik penyimpanan untuk kartu microSD (jika ada).

Tapi perhatikan itu,

Penyimpanan bersama mungkin tidak selalu tersedia, karena media yang dapat dilepas dapat dikeluarkan oleh pengguna. Status media dapat diperiksa menggunakan getExternalStorageState(File).

Tidak ada keamanan yang diberlakukan dengan file-file ini. Misalnya, semua aplikasi yang ditahan WRITE_EXTERNAL_STORAGEdapat menulis ke file-file ini.

Terminologi Penyimpanan Internal dan Eksternal menurut Google / dokumen Android resmi sangat berbeda dari yang kami pikirkan.

ANV
sumber
"Terminologi Penyimpanan Internal dan Eksternal menurut Google / dokumen Android resmi sangat berbeda dari yang kami pikirkan." Ya, sebenarnya judul pertanyaan mengklarifikasi bahwa OP menanyakan tentang kartu SD yang dapat dilepas . getExternalFilesDirs()sering mengembalikan kartu SD yang tidak dapat dilepas, jadi tidak, ini bukan cara universal untuk menemukan lokasi kartu SD yang dapat dilepas.
LarsH
"getExternalFilesDirs (tipe String) mengembalikan jalur absolut ke direktori khusus aplikasi pada semua perangkat penyimpanan bersama / eksternal. Ini berarti, itu akan mengembalikan jalur ke memori internal dan eksternal." Sepasang kalimat ini sangat menyesatkan, karena agar keduanya benar, "eksternal" harus berarti dua hal yang berbeda dan saling bertentangan.
LarsH
4

Inilah cara yang saya gunakan untuk menemukan kartu eksternal. Gunakan mount cmd return lalu parsing bagian vfat.

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

process.waitFor();

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

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

Solusi ini menangani kenyataan itu System.getenv("SECONDARY_STORAGE") tidak ada gunanya dengan Marshmallow.

Diuji dan bekerja pada:

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

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

Karena jawaban asli saya di atas, pemindaian vold tidak lagi dapat digunakan di berbagai produsen.

Saya telah mengembangkan metode yang lebih andal dan lurus ke depan.

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 akan berisi semua direktori root yang dapat ditulis pada sistem, termasuk semua perangkat usb yang terhubung.

CATATAN: Metode canWrite membutuhkan izin android.permission.WRITE_EXTERNAL_STORAGE.

Baron
sumber
Metode isSymlink (File) tidak ditentukan untuk jenis FileFilter baru () {}
Omid Omidi
Adakah ide jika ini akan gagal menemukan kartu sd eksternal pada Android 4.4 karena canWrite?
Anthony
Ini tentu saja lebih mudah daripada metode Anda yang lain, tetapi apakah ini dapat diandalkan? Misalnya saya pernah membaca bahwa pada beberapa perangkat Samsung, /external_sdadalah kartu microSD eksternal; pada beberapa Pemda, itu /_ExternalSD; pada beberapa perangkat itu /sdcard. Mungkin yang terakhir adalah symlink ke /storage/sdcard0atau serupa, tetapi akankah orang lain ini benar-benar dilindungi oleh /storage/*dan /mount/*?
LarsH
Selain itu, apakah perlu menggunakan pathname.canWrite()dan meminta izin WRITE_EXTERNAL_STORAGE? Kenapa tidak telepon saja pathname.canRead()?
LarsH
1

sudah sangat terlambat tetapi akhirnya saya mendapatkan sesuatu yang telah saya uji sebagian besar perangkat (oleh produsen dan versi android) yang bekerja pada Android 2.2+. jika ternyata tidak berfungsi, beri komentar dengan nama perangkat Anda. saya akan memperbaikinya. kalau ada yang tertarik saya akan menjelaskan cara kerjanya.

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

import android.util.Log;


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

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

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

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

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

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

                        }
                    }

                }
            }

        }

        return sdpath;
    }

    static class MmcblkFilter implements FilenameFilter {
        private String pattern;

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

        }

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

    }

}
Ajeet47
sumber
Hai downvoter, silakan coba dulu. Jika tidak berfungsi, beri komentar perangkat Anda. kami menggunakannya lebih dari seribu perangkat dengan Android 2.2+
Ajeet47
Kelas Anda memberi saya / mnt / media_rw / extSdCard pada Samsung Galaxy S4, GT-I9500, Android 5.0.1 (perangkat TIDAK di-root). Tetapi tidak ada yang terlihat di folder / mnt / media_rw dengan ES File Manager ...
isabsent
@salahgunakan menggunakan if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {File [] file = context.getExternalFilesDirs (null); return file.length> 1? file [1]: null; }
Ajeet47
Apa pendapat Anda tentang stackoverflow.com/a/27197248/753575 ? Apakah pendekatan ini lebih komprehensif untuk kasus Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT?
isabent
Iya. dijamin Anda mendapatkan path yang bermakna dari context.getExternalFilesDirs (null), tetapi Anda telah memotongnya untuk path root (itu akan mengembalikan path untuk direktori aplikasi Anda. trim pada "/ Android")
Ajeet47
1

Dengan menulis kode di bawah ini Anda akan mendapatkan lokasi:

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

yang menyimpan data aplikasi Anda di / android / lokasi data di dalam sd_card.

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

list[1]+"/fol" 

untuk mendapatkan lokasi lulus 0 untuk internal dan 1 untuk sdcard ke file array.

Saya telah menguji kode ini pada moto g4 plus dan perangkat Samsung (semuanya berfungsi dengan baik).

Semoga ini bisa membantu.

Rk215 Tech
sumber
kadang-kadang jalur kartu sd tidak pada indeks 1, saya telah melihat kasus-kasus di mana itu pada indeks 0. lebih baik untuk mengikuti sesuatu yang lain
Raghav Satyadev
1

Berikut adalah metode yang saya gunakan untuk menemukan yang dapat dilepas kartu SD yang . Ini rumit, dan mungkin berlebihan untuk beberapa situasi, tetapi bekerja pada berbagai versi Android dan produsen perangkat yang telah saya uji selama beberapa tahun terakhir. Saya tidak tahu perangkat apa pun karena API level 15 yang tidak menemukan kartu SD, jika ada yang terpasang. Dalam kebanyakan kasus, itu tidak akan menghasilkan positif palsu, terutama jika Anda memberikannya nama file yang dikenal untuk dicari.

Tolong beri tahu saya jika Anda mengalami kasus yang tidak berfungsi.

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

  • Jangan lupa <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />di manifes. Dan pada API level 23 dan lebih tinggi, pastikan untuk menggunakan checkSelfPermission/ requestPermissions.
  • Setel KNOWNFILE = "myappfile" jika ada file atau folder yang Anda harapkan ada pada kartu SD. Itu membuat deteksi lebih akurat.
  • Tentunya Anda akan ingin men-cache nilai findSdCardPath(),daripada menghitung ulang setiap kali Anda membutuhkannya.
  • Ada banyak logging ( Log.d()) dalam kode di atas. Ini membantu mendiagnosis setiap kasus di mana jalan yang benar tidak ditemukan. Beri komentar jika Anda tidak ingin masuk.
Lars
sumber
Para downvoters, dapatkah Anda menyarankan cara agar jawaban ini ditingkatkan?
LarsH
1

Satu-satunya solusi yang saya temukan adalah yang menggunakan refleksi

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

    StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
    Class<?> storageVolumeClazz = null;
    try {
        storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
        Method getPath = storageVolumeClazz.getMethod("getPath");
        Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
        Object result = getVolumeList.invoke(mStorageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            Object storageVolumeElement = Array.get(result, i);
            String path = (String) getPath.invoke(storageVolumeElement);
            boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
            if (is_removable == removable) {
                return path;
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}
Habib Kazemi
sumber
Saya pribadi tidak suka menggunakan refleksi karena google tidak menghargai kompatibilitas mundur dalam versi baru Android!
Behrouz.M
0

Saya tidak tahu mengapa tetapi saya harus memanggil .createNewFile () pada File yang dibuat di direktori penyimpanan publik sebelum menggunakannya. Dalam kerangka komentar untuk metode itu mengatakan itu tidak berguna. Ini contohnya ...


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

pengguna1743524
sumber
0

Saya telah membuat metode utils untuk memeriksa kartu SD tersedia pada perangkat atau tidak, dan mendapatkan jalur kartu SD pada perangkat jika tersedia.

Anda dapat menyalin 2 metode di bawah ini ke kelas proyek yang Anda butuhkan. Itu saja.

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;
}
pengguna3161772
sumber
-1

Ini berfungsi untuk semua perangkat eksternal, tetapi pastikan hanya mendapatkan nama folder perangkat eksternal dan kemudian Anda perlu mendapatkan file dari lokasi yang diberikan menggunakan kelas 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;
    }

Panggilan:

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

                }
            }
        }

Mendapatkan akses ke penyimpanan eksternal

Untuk membaca atau menulis file pada penyimpanan eksternal, aplikasi Anda harus memperoleh izin sistem READ_EXTERNAL_STORAGE atau WRITE_EXTERNAL_STORAGE . Sebagai contoh:

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

/ sdcard => Penyimpanan Internal (Ini adalah symlink tetapi harusnya berfungsi)

/ mnt / extSdCard => Sdcard Eksternal

Ini untuk Samsung Galaxy S3

Anda mungkin dapat mengandalkan ini menjadi kenyataan bagi sebagian besar ... namun periksa!

robbyoconnor
sumber
8
Saya memiliki beberapa ponsel Android yang berbeda, sekitar setengahnya adalah Samsung, dan belum pernah melihat lokasi ini digunakan. Mungkin benar pada S3, tetapi mengatakan "Anda mungkin dapat mengandalkan ini menjadi benar untuk sebagian besar" benar - benar salah.
Geobits
salah. /sdcardadalah symlink ke eksternal pada sony
2305
2
Bukankah saya mengatakan bahwa itu mungkin tidak?
robbyoconnor