Tìm vị trí của thẻ SD rời


204

Có cách nào để tìm vị trí của thẻ SD bên ngoài không?

Xin đừng nhầm lẫn với Bộ nhớ ngoài .

Environment.getExternalStorageState()trả về đường dẫn đến điểm gắn SD bên trong, như "/ mnt / sdcard". Nhưng câu hỏi là về SD bên ngoài. Làm cách nào để tôi có được một đường dẫn như "/ mnt / sdcard / bên ngoài_sd" (nó có thể khác với thiết bị này đến thiết bị khác)?

Tôi đoán tôi sẽ kết thúc bằng việc lọc đầu ra của mountlệnh theo tên hệ thống tập tin. Nhưng tôi không chắc cách này đủ mạnh.


Đây là giải pháp của tôi hoạt động cho đến Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

'Môi trường.getExternalStorageState () trả đường dẫn đến điểm gắn SD bên trong như "/ mnt / sdcard".' Chà, nó không phải là nội bộ theo nghĩa là Android sử dụng thuật ngữ này. Tôi tin rằng thuật ngữ bạn đang tìm kiếm là "không thể tháo rời."
LarsH

Câu trả lời:


162

Environment.getExternalStorageState() trả về đường dẫn đến điểm gắn SD bên trong như "/ mnt / sdcard"

Không, Environment.getExternalStorageDirectory()đề cập đến bất cứ điều gì nhà sản xuất thiết bị coi là "bộ nhớ ngoài". Trên một số thiết bị, đây là phương tiện di động, như thẻ SD. Trên một số thiết bị, đây là một phần của đèn flash trên thiết bị. Ở đây, "bộ nhớ ngoài" có nghĩa là "nội dung có thể truy cập qua chế độ Bộ nhớ chung USB khi được gắn trên máy chủ", ít nhất là cho Android 1.x và 2.x.

Nhưng câu hỏi là về SD bên ngoài. Làm cách nào để có được một đường dẫn như "/ mnt / sdcard / bên ngoài_sd" (nó có thể khác với thiết bị này)?

Android không có khái niệm về "SD bên ngoài", ngoài bộ nhớ ngoài, như được mô tả ở trên.

Nếu nhà sản xuất thiết bị đã chọn có bộ lưu trữ ngoài là flash trên máy bay và cũng có thẻ SD, bạn sẽ cần liên hệ với nhà sản xuất đó để xác định xem bạn có thể sử dụng thẻ SD hay không (không được bảo đảm) và các quy tắc dành cho sử dụng nó, chẳng hạn như sử dụng đường dẫn nào cho nó.


CẬP NHẬT

Hai điều cần lưu ý gần đây:

Đầu tiên, trên Android 4.4 trở lên, bạn không có quyền truy cập ghi vào phương tiện lưu động (ví dụ: "SD bên ngoài"), ngoại trừ bất kỳ vị trí nào trên phương tiện đó có thể được trả về getExternalFilesDirs()getExternalCacheDirs(). Xem phân tích tuyệt vời của Dave Smith về điều này, đặc biệt nếu bạn muốn các chi tiết cấp thấp.

Thứ hai, kẻo bất kỳ ai ngụy biện về việc có truy cập phương tiện di động hay không là một phần của SDK Android, đây là đánh giá của Dianne Hackborn :

... Lưu ý: cho đến khi Android 4.4, nền tảng Android chính thức đã không được hỗ trợ thẻ SD ở tất cả ngoại trừ hai trường hợp đặc biệt: bố trí lưu trữ trường cũ, nơi lưu trữ bên ngoài là một thẻ SD (mà vẫn còn được hỗ trợ bởi nền tảng hiện nay) và một tính năng nhỏ được thêm vào Android 3.0, nơi nó sẽ quét thêm thẻ SD và thêm chúng vào nhà cung cấp phương tiện và cung cấp cho ứng dụng quyền truy cập chỉ đọc vào tệp của họ (hiện vẫn được hỗ trợ trong nền tảng).

Android 4.4 là phiên bản đầu tiên của nền tảng đã thực sự cho phép các ứng dụng sử dụng thẻ SD để lưu trữ. Bất kỳ quyền truy cập nào trước đó đều thông qua các API riêng tư, không được hỗ trợ. Hiện tại chúng tôi có API khá phong phú trong nền tảng cho phép các ứng dụng sử dụng thẻ SD theo cách được hỗ trợ, theo cách tốt hơn so với trước đây: chúng có thể sử dụng miễn phí khu vực lưu trữ dành riêng cho ứng dụng của mình mà không yêu cầu bất kỳ quyền trong ứng dụng và có thể truy cập bất kỳ tệp nào khác trên thẻ SD miễn là chúng đi qua bộ chọn tệp mà không cần bất kỳ quyền đặc biệt nào.


4
Và vấn đề này đang trở thành một vấn đề khi các thiết bị HC và ICS xuất hiện vào thời điểm đó "InternalStorageDirectory" và mọi thứ khác giống như nó cho bộ nhớ vật lý nội bộ. Trên hết, hầu hết người dùng hoàn toàn không biết cách xác định vị trí sdcard của họ trong hệ thống tập tin.
Tony Maro

283
Vì vậy, câu trả lời của bạn về cơ bản là 'liên hệ với nhà sản xuất'. Không hữu ích.
Dragonroot

6
Phần cuối của câu trả lời không hoàn toàn chính xác - thực sự có thể phát hiện đường dẫn thẻ SD bằng cách làm theo các câu trả lời bên dưới (quét / Proc / mounts, /system/etc/vold.fstab, v.v.).
Tìm hiểu OpenGL ES

8
@CommonsWare: Tuy nhiên, vẫn chưa chính xác rằng một người "cần" liên hệ với nhà sản xuất khi có các giải pháp hoạt động trên nhiều thiết bị ngoài đó và bên cạnh đó, bản thân SDK không nhất quán trên tất cả các thiết bị nên không đảm bảo. Ngay cả khi các giải pháp này không hoạt động trên tất cả các thiết bị, chúng vẫn hoạt động trên đủ các thiết bị mà nhiều ứng dụng Android trên thị trường dựa vào các kỹ thuật này, trong số các giải pháp khác, để phát hiện đường dẫn thẻ SD bên ngoài. Tôi nghĩ rằng sẽ hơi khó khăn và sớm để gọi tất cả những nhà phát triển ngu ngốc này - không phải khách hàng chắc chắn là thẩm phán cuối cùng của điều đó sao?
Tìm hiểu OpenGL ES

5
@CommonsWare Thế là đủ công bằng, khi mọi việc diễn ra. Tôi chắc chắn đồng ý với bạn rằng nhà phát triển không thể cho rằng điều này sẽ luôn hoạt động ở mọi nơi và mọi mã như vậy không thể được đảm bảo để hoạt động trên tất cả các thiết bị hoặc cho tất cả các phiên bản Android. Hy vọng rằng nó đã được sửa trong SDK! Trong khi đó, vẫn có các tùy chọn hoạt động trên nhiều thiết bị và có thể cải thiện trải nghiệm của người dùng cuối và đưa ra lựa chọn giữa 80% thành công và 0% thành công tôi sẽ chiếm 80%.
Tìm hiểu OpenGL ES

64

Tôi đã đưa ra giải pháp sau đây dựa trên một số câu trả lời được tìm thấy ở đây.

MÃ:

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

SỬ DỤNG:

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

1
đã thử nghiệm với nexus 4, nexus s, galaxy s2, galaxy s3, htc mong muốn =)
Richard

2
Xin chào lần nữa, Richard - tin hay không, tôi phải hỏi: bạn đã thực sự thử viết ra và đọc lại trong một tập tin theo cách này, không chỉ nhận được các thư mục? Nhớ lại vấn đề "/ sdcard0" cũ của chúng tôi? Tôi đã thử mã này và nó đã thất bại trên S3 khi tôi cố đọc lại trong tệp mà nó đã ghi. ... điều này rất kỳ quái ... và đau đớn :))
Howard Pautz

10
Điều này không thành công trên các thiết bị không có 2 thẻ SD. Nó giả sử thứ 1 được tìm thấy là nội bộ và thứ 2 được tìm thấy bên ngoài ...
Caner

KHÔNG làm việc cho các thiết bị USB được gắn qua cáp OTG trên Nexus 5 và Nexus 7.
Khawar Raza

4
/system/etc/vold.fstab không thể truy cập trong Android 4.3+
Ali

37

Tôi đã có một ứng dụng sử dụng một ListPreferencenơi mà người dùng được yêu cầu chọn vị trí nơi họ muốn lưu thứ gì đó.

Trong ứng dụng đó, tôi đã quét /proc/mounts/system/etc/vold.fstabtìm điểm gắn thẻ sdcard. Tôi lưu trữ các điểm gắn kết từ mỗi tệp thành hai ArrayLists riêng biệt .

Sau đó, tôi so sánh một danh sách với các mục khác và các mục bị loại bỏ không có trong cả hai danh sách. Điều đó đã cho tôi một danh sách các đường dẫn gốc đến từng sdcard.

Từ đó, tôi đã kiểm tra các đường dẫn với File.exists(), File.isDirectory(), và File.canWrite(). Nếu bất kỳ thử nghiệm nào trong số đó là sai, tôi sẽ loại bỏ đường dẫn đó khỏi danh sách.

Bất cứ thứ gì còn lại trong danh sách, tôi đã chuyển đổi thành một String[]mảng để ListPreferencethuộc tính giá trị có thể được sử dụng .

Bạn có thể xem mã tại đây: http://sapienmobile.com/?p=204


FYI, điều này không hoạt động trên Galaxy S3, 2 thẻ SD, chỉ có một thẻ được liệt kê trong vold.conf
3c71

1
@ 3c71 - Bạn có thể gửi cho tôi tệp vold và mount cho Galaxy S3 không? Tôi sẽ điều chỉnh mã để che nó.
tước

Galaxy S, tất cả các đường dẫn được tìm thấy không thể ghi, kỳ lạ. Có hai bộ nhớ được tìm thấy, mặc định / mnt / sdcard và / Storage / sdcard0, cả hai đều không thể kiểm tra
thứ năm

1
Tôi đã điều chỉnh mã để bỏ qua tập tin gắn kết. Đó là vấn đề trên các thiết bị của Motorola và Samsung. Tệp gắn kết không bao gồm trường hợp bên ngoài, nhưng nó được liệt kê trong vold. Phiên bản ban đầu của lớp tôi đã so sánh các thú cưỡi với các vật phẩm vold và vứt bỏ không phổ biến cho cả hai. Lấy lớp cập nhật từ cùng một liên kết ở trên.
tước

1
Cảm ơn Baron, đây là "câu trả lời"; ít nhất là một trong những hữu ích.
pstoppani

23

Bạn có thể thử sử dụng chức năng thư viện hỗ trợ có tên là 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());

Cái đầu tiên là bộ lưu trữ ngoài chính và phần còn lại được coi là đường dẫn thẻ SD thực.

Lý do cho nhiều ".getParentFile ()" là để đi lên một thư mục khác, vì đường dẫn ban đầu là

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

EDIT: đây là một cách toàn diện hơn mà tôi đã tạo, để có được các đường dẫn thẻ 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;
      }
    }

Có vẻ như một câu trả lời rất hay, nhưng làm thế nào người ta có thể tích hợp điều này vào một hoạt động đơn giản? Có một số biến không xác định, như App, ContextCompact,EnvironmentCompact
Antonio

@Antonio ContextCompact, Môi trườngCompact có sẵn thông qua thư viện hỗ trợ. "App.global ()" chỉ là bối cảnh ứng dụng mà tôi đã đặt trên toàn cầu vì tôi không muốn thêm tham số Ngữ cảnh ở mọi nơi.
nhà phát triển Android

1
Tuyệt quá! Hoạt động cho thiết bị của tôi v4.4 Samsung GT S Advance, hy vọng nó sẽ hoạt động cho người khác
dùng25

@androiddeveloper Câu trả lời được chỉnh sửa có hoạt động cho tất cả Kích cỡ Thẻ SD và Thiết bị không?
Rahulrr2602

1
Điều này làm việc hoàn hảo cho tôi - nên là câu trả lời được chấp nhận.
Nghịch lý

17

Để truy xuất tất cả các Kho lưu trữ bên ngoài (cho dù chúng là thẻ SD hoặc kho lưu trữ không thể tháo rời bên trong ), bạn có thể sử dụng mã sau:

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

Ngoài ra, bạn có thể sử dụng System.getenv ("EXTERNAL_STORAGE") để truy xuất thư mục Lưu trữ ngoài chính (ví dụ: "/ Storage / sdcard0" ) và System.getenv ("SECONDARY_STORAGE") để lấy danh sách của tất cả các thư mục phụ (ví dụ: " / dung lượng lưu trữ / extSdCard: / dung lượng lưu trữ / USBDriveA: / dung lượng lưu trữ / USBDriveB " ). Hãy nhớ rằng, trong trường hợp này, bạn có thể muốn lọc danh sách các thư mục thứ cấp để loại trừ các ổ USB.

Trong mọi trường hợp, xin lưu ý rằng sử dụng các đường dẫn được mã hóa cứng luôn là một cách tiếp cận tồi (đặc biệt là khi mọi nhà sản xuất có thể thay đổi nó theo ý muốn).


2
Chỉ cần xem xét bất kỳ downvoter nào không để lại bình luận một troll, vì vậy tôi đã ủng hộ để bù đắp cho nó. ;) NHƯNG, tôi đoán phương pháp của bạn khá độc đoán: làm thế nào chúng ta có thể biết rằng bỏ qua các "ổ USB" đó nhưng giữ tất cả những thứ khác thực sự bằng với "sdcards", như được hỏi trong câu hỏi? Ngoài ra đề xuất của bạn System.getenv("SECONDARY_STORAGE")cũng có thể làm với một số tài liệu tham khảo, vì nó dường như không có giấy tờ.
Sz.

1
Theo như tôi biết, trong API Android không có tài liệu tham khảo về một phương pháp tiêu chuẩn để lấy tất cả các Storages bên ngoài. Tuy nhiên, phương pháp được đề xuất hoàn toàn không tùy tiện. Trong Android, giống như trong mọi hệ thống Unix / Linux, TẤT CẢ các thiết bị lưu trữ gắn kết được lưu trữ / liên kết trong một thư mục chung: "/ mnt" (thư mục Unix / Linux tiêu chuẩn để gắn thiết bị lưu trữ) hoặc, trong các phiên bản mới nhất, "/ lưu trữ". Đó là lý do tại sao bạn có thể khá chắc chắn rằng bạn sẽ tìm thấy tất cả các thẻ SD được liên kết trong thư mục này.
Paolo Rovelli

1
Về phương thức System.getenv ("EXTERNAL_STORAGE"), tôi không có bất kỳ tài liệu tham khảo nào ngoài trang API (không giải thích nhiều): developer.android.com/reference/java/lang/, tôi không thể tìm thấy bất kỳ trang chính thức cho các biến môi trường hệ thống Android. Tuy nhiên, tại đây, bạn có thể tìm thấy một danh sách ngắn về họ: herongyang.com/Android/ Kẻ
Paolo Rovelli

Điều tôi muốn nói là không chắc chắn về sdcards là trong /mntđó cũng có thể có nhiều cây fs khác, không chỉ có thẻ SD và ổ USB. Mã của bạn cũng sẽ liệt kê bất kỳ (có lẽ thậm chí ảo) gắn kết hệ thống tập tin nội bộ, nếu tôi hiểu đúng, trong khi câu hỏi muốn sdcards chỉ .
Sz.

1
Tôi hiểu rồi. Vâng, bạn đúng. Với phương pháp của tôi, bạn cũng sẽ truy xuất bộ nhớ SD bên trong (không thể tháo rời).
Paolo Rovelli

15

Giống như Richard, tôi cũng sử dụng tập tin / Proc / mounts để lấy danh sách các tùy chọn lưu trữ có sẵn

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

Cảm ơn. Làm việc hoàn hảo. Và tôi thích cách bạn tạo StorageInfo không thể thay đổi. Mặt khác printStackTrace? Khi nào chúng ta có android.util.Log.e?
Martin

1
KHÔNG làm việc cho các thiết bị USB được gắn qua cáp OTG trên Nexus 5 và Nexus 7.
Khawar Raza

1
Tôi không thể sử dụng tệp này để ghi tệp trên SDCard
Eu Vid

vấn đề tương tự như @EuVid hoạt động trên VM / AVD nhưng không phải trên phần cứng
gián điệp

11

Có thể tìm thấy bất kỳ thẻ SD bổ sung nào được gắn bằng cách đọc /proc/mounts(tệp Linux tiêu chuẩn) và kiểm tra chéo đối với dữ liệu vold ( /system/etc/vold.conf). Và lưu ý rằng vị trí được trả về Environment.getExternalStorageDirectory()có thể không xuất hiện trong cấu hình vold (trong một số thiết bị, bộ nhớ trong không thể đếm được), nhưng vẫn phải được đưa vào danh sách. Tuy nhiên, chúng tôi đã không tìm thấy một cách tốt để mô tả chúng cho người dùng .


Imo, sử dụng mountlà tương thích hơn so với đọc /prochệ thống tập tin. Vấn đề là thẻ SD không cần thiết được định dạng là FAT. Ngoài ra, điểm gắn thẻ có thể thay đổi từ ROM sang ROM. Ngoài ra, có thể có một số phân vùng VFAT khác ...
borisstr

1
@borisstr: Hừm, thực ra Android sử dụng vold , vì vậy nhìn vào cấu hình của nó cũng phù hợp.
Jan Hudec

Tệp mã tôi chia sẻ từ bài đăng của tôi ở trên bao gồm một phương pháp để mô tả các đường dẫn gốc được phát hiện cho người dùng. Nhìn vào phương thức setProperIES () .
tước

1
@borisstr, trên thực tế, không, việc đọc / Proc / mounts dễ mang theo hơn trên các thiết bị Android hơn là khởi chạy chương trình mountthực thi, đặc biệt là khi khởi chạy các tệp thực thi không được khuyến khích.
Chris Stratton

7

Tôi thử tất cả các giải pháp bên trong chủ đề này vào thời điểm này. Nhưng tất cả chúng không hoạt động chính xác trên các thiết bị có một thẻ ngoài (có thể tháo rời) và một thẻ bên trong (không thể tháo rời). Không thể lấy đường dẫn của thẻ ngoài từ lệnh 'mount', từ tệp 'Proc / mounts, v.v.

Và tôi tạo ra giải pháp của riêng mình (trên Paulo Luan's):

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

Nếu bạn nhìn vào mã nguồn cho android.os.Environmentbạn sẽ thấy rằng Android phụ thuộc rất nhiều vào các biến môi trường cho các đường dẫn. Bạn có thể sử dụng biến môi trường "SECONDARY_STORAGE" để tìm đường dẫn đến thẻ sd có thể tháo rời.

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

Ví dụ sử dụng:

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

5

Chỉ cần sử dụng cái này:

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)

Trên một số thiết bị SECONDARY_STORAGEcó một số đường dẫn được phân tách bằng dấu hai chấm (":"). Đây là lý do tại sao tôi chia chuỗi (xem câu trả lời của tôi ở trên).
Jared Rummler

Cả hai trả về null cho tôi.
Tim Cooper

5

Có cách nào để tìm vị trí của thẻ SD bên ngoài không?

Theo cách phổ quát , nếu bạn có nghĩa là cách chính thức; vâng có một.

Trong API cấp 19 tức là trong phiên bản Android 4.4 Kitkat, họ đã thêm File[] getExternalFilesDirs (String type)vào ContextClass cho phép các ứng dụng lưu trữ dữ liệu / tệp trong thẻ micro SD.

Android 4.4 là phiên bản đầu tiên của nền tảng đã thực sự cho phép các ứng dụng sử dụng thẻ SD để lưu trữ. Mọi quyền truy cập vào thẻ SD trước API cấp 19 đều thông qua các API riêng tư, không được hỗ trợ.

getExternalFilesDirs (Kiểu chuỗi) trả về các đường dẫn tuyệt đối đến các thư mục dành riêng cho ứng dụng trên tất cả các thiết bị lưu trữ chia sẻ / bên ngoài. Nó có nghĩa là, nó sẽ trả về các đường dẫn đến cả bộ nhớ trong và bộ nhớ ngoài. Nói chung, đường dẫn trở lại thứ hai được sẽ là đường dẫn lưu trữ cho thẻ nhớ microSD (nếu có).

Nhưng lưu ý rằng,

Lưu trữ được chia sẻ có thể không phải lúc nào cũng có sẵn, vì phương tiện di động có thể bị đẩy ra bởi người dùng. Trạng thái phương tiện có thể được kiểm tra bằng cách sử dụng getExternalStorageState(File).

Không có bảo mật được thi hành với các tập tin này. Ví dụ, bất kỳ ứng dụng giữ WRITE_EXTERNAL_STORAGEcó thể ghi vào các tệp này.

Thuật ngữ Lưu trữ Nội bộ và Bên ngoài theo tài liệu Google / Android chính thức hoàn toàn khác với những gì chúng ta nghĩ.


"Thuật ngữ lưu trữ nội bộ và bên ngoài theo tài liệu chính thức của Google / Android hoàn toàn khác với những gì chúng tôi nghĩ." Có, trên thực tế, tiêu đề của câu hỏi làm rõ rằng OP đang hỏi về thẻ SD có thể tháo rời . getExternalFilesDirs()thường trả về thẻ SD không thể tháo rời, vì vậy không, đây không phải là cách phổ biến để tìm vị trí của thẻ SD có thể tháo rời.
LarsH

"getExternalFilesDirs (Kiểu chuỗi) trả về các đường dẫn tuyệt đối đến các thư mục dành riêng cho ứng dụng trên tất cả các thiết bị lưu trữ chia sẻ / bên ngoài. Điều đó có nghĩa là, nó sẽ trả về các đường dẫn đến cả bộ nhớ trong và bộ nhớ ngoài." Cặp câu này rất sai lệch, bởi vì để cả hai đều đúng, "bên ngoài" phải có nghĩa là hai điều khác nhau và mâu thuẫn.
LarsH

4

Đây là cách tôi sử dụng để tìm thẻ bên ngoài. Sử dụng mount cmd return sau đó phân tích phần 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

Giải pháp này xử lý thực tế System.getenv("SECONDARY_STORAGE")là không sử dụng với Marshmallow.

Đã thử nghiệm và làm việc trên:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - Chứng khoán)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - Cổ phiếu)
  • Samsung Galaxy S4 (Android 4.4 - Cổ phiếu)
  • Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1 - Cổ phiếu)

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

Vì câu trả lời ban đầu của tôi ở trên, quét vold không còn khả thi trên các nhà sản xuất khác nhau.

Tôi đã phát triển một phương pháp đáng tin cậy và thẳng tiến hơn.

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 sẽ chứa tất cả các thư mục gốc có thể ghi trên hệ thống, bao gồm mọi thiết bị usb được kết nối với usb.

LƯU Ý: Phương thức canWrite cần có sự cho phép của ERIC.WRITE_EXTERNAL_STORAGE.


Phương thức isSymlink (Tệp) không được xác định cho loại FileFilter mới () {}
Omid Omidi

Bất kỳ ý tưởng nào nếu điều này sẽ không tìm thấy thẻ sd bên ngoài trên Android 4.4 do canWrite?
Anthony

Điều này chắc chắn là đơn giản hơn so với phương pháp khác của bạn, nhưng nó có đáng tin cậy không? Ví dụ, tôi đã đọc trên một số thiết bị Samsung, /external_sdlà thẻ nhớ ngoài; trên một số LG, nó /_ExternalSD; trên một số thiết bị /sdcard. Có thể cái sau là một liên kết tượng trưng /storage/sdcard0hoặc tương tự, nhưng những cái khác này có thực sự được bao phủ một cách đáng tin cậy /storage/*/mount/*không?
LarsH

Ngoài ra, có cần thiết phải sử dụng pathname.canWrite()và yêu cầu quyền WRITE_EXTERNAL_STORAGE không? Tại sao không gọi pathname.canRead()?
LarsH

1

đã quá muộn nhưng cuối cùng tôi đã nhận được một cái gì đó tôi đã thử nghiệm hầu hết các thiết bị (của nhà sản xuất và phiên bản Android) nó hoạt động trên Android 2.2+. nếu bạn thấy nó không hoạt động, hãy bình luận nó với tên thiết bị của bạn. tôi sẽ sửa nó. Nếu ai quan tâm tôi sẽ giải thích cách nó hoạt động.

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

    }

}

Hey downvoter vui lòng thử trước. Nếu nó không hoạt động thì hãy bình luận thiết bị của bạn. chúng tôi sử dụng hơn một nghìn thiết bị chạy Android 2.2+
Ajeet47

Lớp của bạn cung cấp cho tôi / mnt / media_rw / extSdCard trên Samsung Galaxy S4, GT-I9500, Android 5.0.1 (thiết bị KHÔNG được root). Nhưng không có gì hiển thị trong thư mục / mnt / media_rw với ES File Manager ...
isabsent

@isabsent sử dụng if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {File [] file = bối cảnh.getExternalFilesDirs (null); return file.length> 1? file [1]: null; }
Ajeet47

Bạn nghĩ gì về stackoverflow.com/a/27197248/753575 ? Cách tiếp cận này có toàn diện hơn đối với trường hợp Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT không?
isabent

Đúng. nó được đảm bảo rằng bạn có được đường dẫn có ý nghĩa từ context.getExternalFilesDirs (null), nhưng bạn đã cắt nó cho đường dẫn gốc (nó sẽ trả về đường dẫn cho thư mục ứng dụng của bạn. Cắt nó trên "/ Android")
Ajeet47

1

Bằng cách viết mã dưới đây, bạn sẽ nhận được vị trí:

/ Storage / 663D-554E / Android / data / app_package_name / files /

nơi lưu trữ dữ liệu ứng dụng của bạn tại / android / vị trí dữ liệu bên trong sd_card.

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

list[1]+"/fol" 

để lấy vị trí vượt qua 0 cho nội bộ và 1 cho sdcard vào mảng tệp.

Tôi đã thử nghiệm mã này trên thiết bị moto g4 plus và Samsung (tất cả đều hoạt động tốt).

hy vọng điều này có thể hữu ích.


đôi khi đường dẫn thẻ sd không nằm trong chỉ mục 1, tôi đã thấy các trường hợp nằm trên chỉ số 0. tốt hơn để theo dõi thứ khác
Raghav Satyadev

1

Đây là phương pháp tôi sử dụng để tìm thẻ SD rời . Nó phức tạp và có thể quá mức cần thiết cho một số tình huống, nhưng nó hoạt động trên nhiều phiên bản Android và nhà sản xuất thiết bị mà tôi đã thử nghiệm trong vài năm qua. Tôi không biết bất kỳ thiết bị nào kể từ API cấp 15 mà nó không tìm thấy thẻ SD, nếu có một thiết bị được gắn. Nó sẽ không trả về dương tính giả trong hầu hết các trường hợp, đặc biệt nếu bạn đặt cho nó tên của một tệp đã biết để tìm kiếm.

Xin vui lòng cho tôi biết nếu bạn gặp phải bất kỳ trường hợp nào nó không hoạt động.

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

  • Đừng quên <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />trong bảng kê khai. Và ở cấp độ API 23 trở lên, hãy đảm bảo sử dụng checkSelfPermission/requestPermissions .
  • Đặt KNOWNFILE = "myappfile" nếu có tệp hoặc thư mục bạn muốn tìm trên thẻ SD. Điều đó làm cho phát hiện chính xác hơn.
  • Rõ ràng bạn sẽ muốn lưu trữ giá trị của findSdCardPath(), thay vì tính toán lại mỗi khi bạn cần.
  • Có một loạt các bản ghi ( Log.d()) trong đoạn mã trên. Nó giúp chẩn đoán bất kỳ trường hợp nào không tìm thấy đường dẫn đúng. Bình luận nếu bạn không muốn đăng nhập.

Downvoters, bạn có thể đề xuất một cách để câu trả lời này được cải thiện?
LarsH

1

Giải pháp làm việc duy nhất tôi tìm thấy là giải pháp này sử dụng sự phản chiếu

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

Cá nhân tôi không thích sử dụng sự phản chiếu vì google không đánh giá cao khả năng tương thích ngược trong các phiên bản mới của Android!
Behrouz.M

0

Tôi không biết tại sao nhưng tôi cần gọi .createNewFile () trên Tệp được tạo trong thư mục lưu trữ công cộng trước khi sử dụng. Trong khuôn khổ, các bình luận cho phương pháp đó nói rằng nó không hữu ích. Đây là một mẫu ...


 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

Tôi đã tạo một phương thức utils để kiểm tra thẻ SD có khả dụng trên thiết bị hay không và nhận đường dẫn thẻ SD trên thiết bị nếu có.

Bạn có thể sao chép 2 phương thức dưới đây vào lớp dự án mà bạn cần. Đó là tất cả.

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

Nó hoạt động cho tất cả các thiết bị bên ngoài, nhưng đảm bảo chỉ lấy tên thư mục thiết bị bên ngoài và sau đó bạn cần lấy tệp từ vị trí đã cho bằng lớp Tệp.

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

Gọi điện thoại:

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

                }
            }
        }

Truy cập vào bộ nhớ ngoài

Để đọc hoặc ghi các file vào bộ nhớ ngoài, ứng dụng của bạn phải có các READ_EXTERNAL_STORAGE hoặc WRITE_EXTERNAL_STORAGE hệ thống cho phép. Ví dụ:

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

-2

/ sdcard => Bộ nhớ trong (Đây là một liên kết tượng trưng nhưng sẽ hoạt động)

/ mnt / extSdCard => Thẻ ngoài

Cái này dành cho Samsung Galaxy S3

Bạn có thể ngân hàng về điều này là đúng đối với hầu hết ... kiểm tra kép!


8
Tôi đã có một số điện thoại Android khác nhau, khoảng một nửa trong số đó là Samsung và chưa từng thấy vị trí này được sử dụng. Nó có thể đúng trên S3, nhưng nói rằng "bạn có thể ngân hàng về điều này đúng với hầu hết" là hoàn toàn sai.
Geobits

Sai lầm. /sdcardlà một liên kết đến bên ngoài trên chiếc sony
2305

2
Tôi không nói rằng nó có thể không?
robbyoconnor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.