이동식 SD 카드의 위치 찾기


204

외부 SD 카드의 위치를 ​​찾을 수있는 보편적 인 방법이 있습니까?

외부 저장소 와 혼동하지 마십시오 .

Environment.getExternalStorageState()"/ mnt / sdcard"와 같은 내부 SD 마운트 지점에 대한 경로를 반환합니다. 그러나 문제는 외부 SD에 관한 것입니다. "/ mnt / sdcard / external_sd"와 같은 경로를 얻으려면 어떻게해야합니까 (장치마다 다를 수 있음)?

mount파일 시스템 이름으로 명령 출력을 필터링하는 것으로 끝날 것 입니다. 그러나 나는이 방법이 충분히 강력하다는 것을 확신하지 못한다.


누가까지 작동하는 내 솔루션은 다음과 같습니다. stackoverflow.com/a/40205116/5002496
Gokul NC

'Environment.getExternalStorageState ()는 "/ mnt / sdcard"와 같은 내부 SD 마운트 지점에 대한 경로를 반환합니다.' 글쎄, 안드로이드가이 용어를 사용한다는 의미에서 내부적 인 것은 아니다. 당신이 찾고있는 용어는 "이동식이 아닙니다"라고 생각합니다.
LarsH

답변:


162

Environment.getExternalStorageState() "/ mnt / sdcard"와 같은 내부 SD 마운트 지점으로 경로를 반환합니다

아니요, Environment.getExternalStorageDirectory()장치 제조업체가 "외부 저장소"로 간주 한 모든 것을 말합니다. 일부 장치에서는 SD 카드와 같은 이동식 미디어입니다. 일부 장치에서는 장치 플래시의 일부입니다. 여기서 "외부 저장 장치"는 "Android 1.x 및 2.x에 대해"호스트 시스템에 장착 된 경우 USB 대용량 저장 모드를 통해 액세스 할 수있는 항목 "을 의미합니다.

그러나 문제는 외부 SD에 관한 것입니다. "/ mnt / sdcard / external_sd"와 같은 경로를 얻는 방법 (장치마다 다를 수 있음)?

안드로이드는 위에서 설명한 것처럼 외부 저장소 외에 "외부 SD"라는 개념이 없습니다.

장치 제조업체에서 외장 스토리지를 온보드 플래시로 선택하고 SD 카드를 사용하기로 선택한 경우 해당 제조업체에 문의하여 SD 카드를 사용할 수 있는지 여부 (보증되지 않음) 및 규칙이 무엇인지 확인해야합니다. 사용할 경로와 같은 사용.


최신 정보

최근 주목할 사항 :

먼저 Android 4.4 이상에서는 getExternalFilesDirs()및에서 반환 할 수있는 미디어의 위치를 ​​제외하고 이동식 미디어 (예 : "외부 SD")에 대한 쓰기 액세스 권한이 없습니다 getExternalCacheDirs(). 참조 데이브 스미스의 우수한 분석 은 낮은 수준의 세부 정보를 원하는 경우 특히이의를.

둘째, 착탈식 미디어 액세스가 Android SDK의 일부인지 여부에 대해 다른 사람이 얽매이지 않도록 Dianne Hackborn의 평가는 다음과 같습니다.

... 유의 사항 : Android 4.4까지 공식 Android 플랫폼은 두 가지 특별한 경우를 제외하고는 SD 카드를 전혀 지원하지 않았습니다 . 외부 저장소가 SD 카드 인 구식 학교 저장소 레이아웃 (현재 플랫폼에서 여전히 지원됨) Android 3.0에 추가 된 작은 기능으로 SD 카드를 추가로 스캔하여 미디어 제공 업체에 추가하고 앱에 파일에 대한 읽기 전용 액세스 권한을 부여합니다 (현재 플랫폼에서도 여전히 지원됨).

Android 4.4는 애플리케이션이 실제로 SD 카드를 사용하여 저장하도록 허용 한 최초의 플랫폼 릴리스입니다. 그 전에는 지원되지 않는 개인 API를 통해 액세스했습니다. 우리는 이제 플랫폼에서 상당히 풍부한 API를 사용하여 애플리케이션이 SD 카드를 이전보다 더 나은 방식으로 지원되는 방식으로 사용할 수있게되었습니다. 앱에서 권한을 가지며 특별한 권한이 없어도 파일 선택기를 통과하는 한 SD 카드의 다른 파일에 액세스 할 수 있습니다.


4
HC와 ICS 장치가 "ExternalStorageDirectory"와 그 밖의 모든 것을 내부 물리적 스토리지로 가져 오면서이 문제는 더욱 문제가되고 있습니다. 대부분의 사용자는 sdcard가 파일 시스템에서 어디에 있는지 찾는 방법을 전혀 모릅니다.
Tony Maro

283
따라서 귀하의 대답은 기본적으로 '제조업체에 문의하십시오'입니다. 유용하지 않다.
dragonroot

6
답변의 마지막 부분은 정확하지 않습니다. 실제로 아래의 답변 (/ proc / mounts, /system/etc/vold.fstab 등)을 따라 SD 카드 경로를 감지하는 것이 가능합니다.
OpenGL ES 배우기

8
@CommonsWare : 그럼에도 불구하고, 많은 장치에서 작동하는 솔루션이있을 때 제조업체에 연락해야하는 "필요한"것은 여전히 ​​정확하지 않으며, SDK 자체가 모든 장치에서 일관되지 않으므로 보증하지 않습니다. 이러한 솔루션이 모든 기기에서 작동하지 않더라도 시중에 나와있는 많은 Android 앱이 외부 SD 카드 경로를 감지하기 위해 이러한 기술에 의존하는 충분한 기기에서 작동합니다. 이 모든 개발자들을 바보라고 부르는 것은 조금 거칠고 시기상조라고 생각합니다. 고객이 반드시 최종 판단자가 아닙니까?
OpenGL ES를 배우십시오

5
@CommonsWare 일이 벌어 질만큼 충분합니다. 개발자가 항상 모든 곳에서 작동한다고 가정 할 수는 없으며 그러한 코드가 모든 기기 또는 모든 버전의 Android에서 작동한다고 보장 할 수는 없다는 점에 동의합니다. 바라건대 SDK에서 수정되기를 바랍니다! 그 동안 많은 장치에서 작동하고 최종 사용자 경험을 향상시킬 수있는 옵션이 여전히 있으며 80 % 성공과 0 % 성공 중에서 선택하면 80 %를 사용합니다.
OpenGL ES를 배우십시오

64

여기에있는 몇 가지 답변을 기반으로 다음 솔루션을 생각해 냈습니다.

암호:

public class ExternalStorage {

    public static final String SD_CARD = "sdCard";
    public static final String EXTERNAL_SD_CARD = "externalSdCard";

    /**
     * @return True if the external storage is available. False otherwise.
     */
    public static boolean isAvailable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

    public static String getSdCardPath() {
        return Environment.getExternalStorageDirectory().getPath() + "/";
    }

    /**
     * @return True if the external storage is writable. False otherwise.
     */
    public static boolean isWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;

    }

    /**
     * @return A map of all storage locations available
     */
    public static Map<String, File> getAllStorageLocations() {
        Map<String, File> map = new HashMap<String, File>(10);

        List<String> mMounts = new ArrayList<String>(10);
        List<String> mVold = new ArrayList<String>(10);
        mMounts.add("/mnt/sdcard");
        mVold.add("/mnt/sdcard");

        try {
            File mountFile = new File("/proc/mounts");
            if(mountFile.exists()){
                Scanner scanner = new Scanner(mountFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("/dev/block/vold/")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[1];

                        // don't add the default mount path
                        // it's already in the list.
                        if (!element.equals("/mnt/sdcard"))
                            mMounts.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            File voldFile = new File("/system/etc/vold.fstab");
            if(voldFile.exists()){
                Scanner scanner = new Scanner(voldFile);
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("dev_mount")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[2];

                        if (element.contains(":"))
                            element = element.substring(0, element.indexOf(":"));
                        if (!element.equals("/mnt/sdcard"))
                            mVold.add(element);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


        for (int i = 0; i < mMounts.size(); i++) {
            String mount = mMounts.get(i);
            if (!mVold.contains(mount))
                mMounts.remove(i--);
        }
        mVold.clear();

        List<String> mountHash = new ArrayList<String>(10);

        for(String mount : mMounts){
            File root = new File(mount);
            if (root.exists() && root.isDirectory() && root.canWrite()) {
                File[] list = root.listFiles();
                String hash = "[";
                if(list!=null){
                    for(File f : list){
                        hash += f.getName().hashCode()+":"+f.length()+", ";
                    }
                }
                hash += "]";
                if(!mountHash.contains(hash)){
                    String key = SD_CARD + "_" + map.size();
                    if (map.size() == 0) {
                        key = SD_CARD;
                    } else if (map.size() == 1) {
                        key = EXTERNAL_SD_CARD;
                    }
                    mountHash.add(hash);
                    map.put(key, root);
                }
            }
        }

        mMounts.clear();

        if(map.isEmpty()){
                 map.put(SD_CARD, Environment.getExternalStorageDirectory());
        }
        return map;
    }
}

용법:

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

1
넥서스 4, 넥서스 s, 갤럭시 s2, 갤럭시 s3, htc desire =)로 테스트 됨
Richard

2
다시 한번, Richard-믿거 나 말거나 물어봐야합니다. 실제로 파일을 작성하고 다시 읽지 않았습니까? 기존의 "/ sdcard0"문제를 기억하십니까? 이 코드를 사용해 보았고 작성한 파일을 다시 읽으려고 할 때 S3에서 실패했습니다 . ... 이것은 매우 기이합니다 ... 그리고 고통 스럽습니다 :))
Howard Pautz

10
2 개의 SD 카드가없는 장치에서는 실패합니다. 첫 번째 발견은 내부이고 두 번째 발견은 외부라고 가정합니다.
Caner

넥서스 5와 넥서스 7에 OTG 케이블을 통해 연결된 USB 장치에 대한 작동하지 않았다
Khawar 라자

4
/system/etc/vold.fstab은 안드로이드 4.3 이상에서 액세스 할 수 없습니다
Ali

37

ListPreference사용자가 무언가를 저장하려는 위치를 선택 해야하는 위치 를 사용하는 응용 프로그램이 있습니다.

이 응용 프로그램에서 나는 스캔 /proc/mounts/system/etc/vold.fstabsdcard에 대한 마운트 지점을. 각 파일의 마운트 지점을 두 개의 별도 ArrayLists에 저장했습니다.

그런 다음 한 목록을 다른 목록과 비교하고 두 목록에 모두없는 버려진 항목을 비교했습니다. 그것은 각 sdcard에 대한 루트 경로 목록을 제공했습니다.

거기에서, 내가 가진 경로를 테스트 File.exists(), File.isDirectory()File.canWrite(). 해당 테스트 중 하나라도 거짓이면 목록에서 해당 경로를 삭제했습니다.

목록에 남은 것이 무엇이든, 나는 값 속성에 String[]의해 사용될 수 있도록 배열 로 변환했습니다 ListPreference.

여기에서 코드를 볼 수 있습니다 : http://sapienmobile.com/?p=204


참고로, 이것은 Galaxy S3, 2 개의 SD 카드, vold.conf에 나열된 카드 중 하나에서만 작동하지 않습니다
3c71

1
@ 3c71-Galaxy S3 용 vold 및 mounts 파일을 보내 주시겠습니까? 코드를 다루기 위해 코드를 조정할 것입니다.
Baron

갤럭시 S, 발견 된 모든 경로는 쓸 수없고 이상하지 않았다. 두 개의 스토리지를 발견 모두 테스트에 실패 기본 / MNT / SDCARD 및 / 저장 / sdcard0 있었다
다섯 번째

1
mounts 파일을 무시하도록 코드를 조정했습니다. 모토로라와 삼성 기기의 문제였습니다. 마운트 파일은 external_sd 케이스를 다루지 않았지만 vold에 나열되어 있습니다. 내 클래스의 초기 버전은 마운트와 두 가지 공통적이지 않은 버려진 항목을 마운트와 비교했습니다. 위의 동일한 링크에서 업데이트 된 클래스를 가져옵니다.
남작

1
고마워 Baron, 이것은 "the"답변입니다. 적어도 유용한 것.
pstoppani

23

ContextCompat.getExternalFilesDirs () 라는 지원 라이브러리 함수를 사용하려고 시도 할 수 있습니다 .

      final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
      final ArrayList<File> extRootPaths=new ArrayList<>();
      for(final File file : appsDir)
        extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());

첫 번째는 기본 외부 저장소이고 나머지는 실제 SD 카드 경로 여야합니다.

여러 ".getParentFile ()"의 이유는 원래 경로가 다음과 같기 때문에 다른 폴더로 이동하기 때문입니다.

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

편집 : 여기 SD 카드 경로를 얻는 더 포괄적 인 방법이 있습니다.

  /**
   * returns a list of all available sd cards paths, or null if not found.
   *
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
    {
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return null;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return null;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return null;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return null;
      }
    final List<String> result=new ArrayList<>();
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    if(result.isEmpty())
      return null;
    return result;
    }

  /** Given any file/folder inside an sd card, this will return the path of the sd card */
  private static String getRootOfInnerSdCardFolder(File file)
    {
    if(file==null)
      return null;
    final long totalSpace=file.getTotalSpace();
    while(true)
      {
      final File parentFile=file.getParentFile();
      if(parentFile==null||parentFile.getTotalSpace()!=totalSpace||!parentFile.canRead())
        return file.getAbsolutePath();
      file=parentFile;
      }
    }

매우 좋은 대답 인 것 같지만 간단한 활동으로 어떻게 통합 할 수 있습니까? 정의되지 않은 여러 변수 등이있다 App, ContextCompact,EnvironmentCompact
안토니오

@Antonio ContextCompact, EnvironmentCompact는 지원 라이브러리를 통해 제공됩니다. "App.global ()"은 응용 프로그램 컨텍스트 일 ​​뿐이며, 어디서나 Context 매개 변수를 추가하는 것을 좋아하지 않기 때문에 전역 적으로 설정했습니다.
안드로이드 개발자

1
큰! 내 장치 v4.4 Samsung GT S Advance에서 작동합니다. 다른 사용자도 사용할 수 있기를 바랍니다
user25

@androiddeveloper 수정 된 답변이 모든 기기 및 SD 카드 크기에서 작동합니까?
Rahulrr2602

1
이것은 나를 위해 완벽하게 작동했습니다-허용되는 대답이어야합니다.
역설

17

모든을 검색하려면 외부 저장소 ( SD 카드 또는 내부 비 이동식 저장소 이든 ) 다음 코드를 사용할 수 있습니다.

final String state = Environment.getExternalStorageState();

if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) {  // we can read the External Storage...           
    //Retrieve the primary External Storage:
    final File primaryExternalStorage = Environment.getExternalStorageDirectory();

    //Retrieve the External Storages root directory:
    final String externalStorageRootDir;
    if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) {  // no parent...
        Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
    }
    else {
        final File externalStorageRoot = new File( externalStorageRootDir );
        final File[] files = externalStorageRoot.listFiles();

        for ( final File file : files ) {
            if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) {  // it is a real directory (not a USB drive)...
                Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
            }
        }
    }
}

또는 System.getenv ( "EXTERNAL_STORAGE") 를 사용하여 기본 외부 저장소 디렉토리 (예 : "/ storage / sdcard0" ) 및 System.getenv ( "SECONDARY_STORAGE") 를 검색하여 모든 보조 디렉토리 (예 : " / storage / extSdCard : / storage / UsbDriveA : / storage / UsbDriveB " ). 이 경우에도 USB 드라이브를 제외하기 위해 보조 디렉토리 목록을 필터링 할 수 있습니다.

어쨌든 하드 코딩 된 경로를 사용하는 것은 항상 나쁜 접근 방식입니다 (특히 모든 제조업체가 원하는대로 변경할 수있는 경우).


2
트롤을 댓글로 남기지 않는 다운 보트를 고려해 보았습니다. ;) 그러나 귀하의 방법은 다소 임의적이라고 생각합니다. "USB 드라이브"를 건너 뛰고 다른 모든 것을 실제로 유지하는 것은 질문에서 묻는 것처럼 "sdcards"와 동일하다는 것을 어떻게 알 수 있습니까? 또한 System.getenv("SECONDARY_STORAGE")문서화되지 않은 것처럼 일부 참조와 함께 제안 할 수도 있습니다.
Sz.

1
내가 아는 한, Android API에는 모든 외부 저장소를 검색하는 표준 방법에 대한 참조가 없습니다. 그러나 제안 된 방법은 전혀 임의적이지 않다. 안드로이드에서는 모든 유닉스 / 리눅스 시스템에서와 같이 모든 마운트 스토리지 장치가 공통 디렉토리 "/ mnt"(스토리지 디바이스 마운트를위한 표준 유닉스 / 리눅스 디렉토리) 또는 최신 버전 인 "/에 저장 / 링크됩니다 저장". 따라서이 폴더에 연결된 모든 SD 카드를 찾을 수 있습니다.
Paolo Rovelli

1
System.getenv ( "EXTERNAL_STORAGE") 방법에 관한 것은, 나는 어떤 기준이 아닌 (많이 설명하지 않음) API 페이지가 없습니다 : developer.android.com/reference/java/lang/... 내가 어떤을 찾을 수 없습니다 안드로이드 시스템 환경 변수에 대한 공식 페이지. 그러나 여기에 짧은 목록이 있습니다 : herongyang.com/Android/…
Paolo Rovelli

내가 sdcards에 대해 확신하지 않고 의미하는 것은 /mntSD 카드와 USB 드라이브뿐만 아니라 다양한 다른 fs 트리가있을 수 있다는 것입니다. 코드가 올바르게 이해하면 내부 (아마도 가상의) 파일 시스템 마운트도 나열하지만 질문은 sdcard 원합니다 .
Sz.

1
내가 참조. 네, 맞아요. 내 방법을 사용하면 내부 (이동식이 아닌) SD 메모리도 검색합니다.
Paolo Rovelli

15

Richard와 마찬가지로 / proc / mounts 파일을 사용하여 사용 가능한 스토리지 옵션 목록을 가져옵니다.

public class StorageUtils {

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {

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

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

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

    public static List<StorageInfo> getStorageList() {

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

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

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

        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (buf_reader != null) {
                try {
                    buf_reader.close();
                } catch (IOException ex) {}
            }
        }
        return list;
    }    
}

감사. 완벽하게 작동했습니다. 그리고 StorageInfo를 변경 불가능하게 만드는 방식이 마음에 듭니다. 반면에 printStackTrace? 우리가 언제 android.util.Log.e?
Martin

1
넥서스 5와 넥서스 7에 OTG 케이블을 통해 연결된 USB 장치에 대한 작동하지 않았다
Khawar 라자

1
SDCard에 파일을 쓰는 데 이것을 사용할 수 없습니다
Eu Vid

@EuVid가 VM / AVD에서 작동하지만 하드웨어에서는 작동하지 않는 것과 동일한 문제
spy

11

읽기 /proc/mounts(표준 Linux 파일) 및 vold 데이터 ( /system/etc/vold.conf) 에 대한 교차 검사를 통해 추가 SD 카드가 마운트 된 위치를 찾을 수 있습니다. 그리고 반환 된 위치 Environment.getExternalStorageDirectory()는 vold 구성 (일부 장치에서는 마운트 해제 할 수없는 내부 저장 장치)에 나타나지 않을 수 있지만 여전히 목록에 포함되어야합니다. 그러나 이를 사용자 에게 설명 하는 좋은 방법을 찾지 못했습니다 .


Imo, 파일 시스템을 mount읽는 것보다 사용 이 더 호환됩니다 /proc. 문제는 SD 카드를 FAT로 포맷 할 필요가 없다는 것입니다. 또한 카드 장착 지점은 ROM마다 다를 수 있습니다. 또한 여러 다른 VFAT 파티션이있을 수 있습니다.
borisstr

1
@ borisstr : 흠, 실제로 안드로이드는 vold를 사용 하므로 구성을 보는 것도 적절합니다.
Jan Hudec

위의 게시물에서 공유 한 코드 파일에는 사용자에게 검색된 루트 경로를 설명하는 방법이 포함되어 있습니다. 상기 봐 () SetProperties를 하는 방법.
Baron

1
실제로 @borisstr은 / exe / mounts를 읽는 것이 mount실행 파일을 시작하는 것보다 안드로이드 장치에서 더 이식성이 뛰어납니다. 특히 실행 파일을 시작하지 않는 것이 좋습니다.
Chris Stratton

7

이번에는이 주제의 모든 솔루션을 시도합니다. 그러나 하나의 외부 (이동식) 카드와 하나의 내부 (이동식이 아닌) 카드가있는 장치에서는 모두 제대로 작동하지 않았습니다. 외부 카드의 경로는 'mount'명령, 'proc / mounts'파일 등에서 얻을 수 없습니다.

그리고 내 자신의 솔루션을 작성합니다 (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();

6

소스 코드를 android.os.Environment보면 Android가 경로의 환경 변수에 크게 의존한다는 것을 알 수 있습니다. "SECONDARY_STORAGE"환경 변수를 사용하여 이동식 sd 카드의 경로를 찾을 수 있습니다.

/**
 * Get a file using an environmental variable.
 *
 * @param variableName
 *         The Environment variable name.
 * @param paths
 *         Any paths to the file if the Environment variable was not found.
 * @return the File or {@code null} if the File could not be located.
 */
private static File getDirectory(String variableName, String... paths) {
    String path = System.getenv(variableName);
    if (!TextUtils.isEmpty(path)) {
        if (path.contains(":")) {
            for (String _path : path.split(":")) {
                File file = new File(_path);
                if (file.exists()) {
                    return file;
                }
            }
        } else {
            File file = new File(path);
            if (file.exists()) {
                return file;
            }
        }
    }
    if (paths != null && paths.length > 0) {
        for (String _path : paths) {
            File file = new File(_path);
            if (file.exists()) {
                return file;
            }
        }
    }
    return null;
}

사용법 예 :

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

5

간단히 이것을 사용하십시오 :

String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
    Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
    Log.i("SECONDARY_STORAGE", secondary_sd)

일부 장치 SECONDARY_STORAGE에는 콜론 ( ":")으로 구분 된 여러 경로가 있습니다. 이것이 내가 문자열을 나누는 이유입니다 (위의 답변 참조).
Jared Rummler

둘 다 나를 위해 null을 반환합니다.
Tim Cooper

5

외부 SD 카드의 위치를 ​​찾을 수있는 보편적 인 방법이 있습니까?

으로 보편적 인 방법 , 당신은 공식적인 방법을 의미하는 경우; 예, 하나 있습니다.

API 레벨 19에서, 즉 Android 버전 4.4 Kitkat File[] getExternalFilesDirs (String type)에서Context 클래스에 앱이 마이크로 SD 카드에 데이터 / 파일을 저장할 수 있도록합니다.

Android 4.4는 앱이 실제로 SD 카드를 사용하여 저장하도록 허용 한 최초의 플랫폼 릴리스입니다. API 레벨 19 이전의 SD 카드에 대한 모든 액세스는 지원되지 않는 개인 API를 통해 이루어졌습니다.

getExternalFilesDirs (String type) 는 모든 공유 / 외부 저장 장치의 응용 프로그램 특정 디렉토리에 대한 절대 경로를 반환합니다. 즉, 내부 및 외부 메모리 모두에 대한 경로를 반환합니다. 일반적으로 두 번째 리턴 경로 는 microSD 카드 (있는 경우)의 저장 경로입니다.

그러나

사용자가 이동식 미디어를 꺼낼 수 있으므로 공유 스토리지를 항상 사용할 수있는 것은 아닙니다. 미디어 상태를 사용하여 확인할 수 있습니다 getExternalStorageState(File) .

이러한 파일에는 보안이 적용되지 않습니다. 예를 들어, 모든 응용 프로그램 보유 WRITE_EXTERNAL_STORAGE는이 파일에 쓸 수 있습니다.

Google / 공식 Android 문서에 따른 내부 및 외부 저장소 용어 는 생각 과 다릅니다 .


"Google / 공식 Android 문서에 따른 내부 및 외부 저장소 용어는 우리가 생각하는 것과 상당히 다릅니다." 예, 실제로 질문 제목에 따라 OP에서 이동식 SD 카드를 요구하고 있습니다. getExternalFilesDirs()이동식이 아닌 SD 카드를 반환하는 경우가 많으므로 아니요, 이동식 SD 카드의 위치를 ​​찾는 일반적인 방법은 아닙니다.
LarsH

"getExternalFilesDirs (String type)은 모든 공유 / 외부 저장 장치의 응용 프로그램 특정 디렉토리에 대한 절대 경로를 반환합니다. 이는 내부 및 외부 메모리 모두에 대한 경로를 반환합니다." 이 문장 쌍은 둘 다 사실이기 위해서는 "외부"가 서로 다른 상충되는 두 가지를 의미해야하기 때문에 오해의 소지가 있습니다.
LarsH

4

외부 카드를 찾는 방법은 다음과 같습니다. mount cmd return을 사용하여 vfat 부분을 구문 분석하십시오.

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

process.waitFor();

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

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

4

이 솔루션은 System.getenv("SECONDARY_STORAGE")마시멜로에서 사용하지 않는 사실을 처리합니다 .

테스트 및 작업 :

  • Samsung Galaxy Tab 2 (Android 4.1.1-재고)
  • Samsung Galaxy Note 8.0 (Android 4.2.2-재고)
  • Samsung Galaxy S4 (Android 4.4-재고)
  • 삼성 Galaxy S4 (Android 5.1.1-Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1-재고)

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

2

위의 원래 답변이므로 vold 스캔은 더 이상 다양한 제조업체에서 실행할 수 없습니다.

보다 안정적이고 간단한 방법을 개발했습니다.

File mnt = new File("/storage");
if (!mnt.exists())
    mnt = new File("/mnt");

File[] roots = mnt.listFiles(new FileFilter() {

    @Override
    public boolean accept(File pathname) {
        return pathname.isDirectory() && pathname.exists()
                && pathname.canWrite() && !pathname.isHidden()
                && !isSymlink(pathname);
    }
});

root는 usb로 연결된 USB 장치를 포함하여 시스템의 모든 쓰기 가능한 루트 디렉토리를 포함합니다.

참고 : canWrite 메서드에는 android.permission.WRITE_EXTERNAL_STORAGE 권한이 필요합니다.


isSymlink (File) 메소드는 new FileFilter () {} 유형에 대해 정의되지 않았습니다.
Omid Omidi

이것이 canWrite로 인해 Android 4.4에서 외부 sd 카드를 찾지 못하는 경우 어떤 아이디어가 있습니까?
Anthony

이것은 다른 방법보다 훨씬 간단하지만 신뢰할 수 있습니까? 예를 들어 일부 삼성 기기에서는 /external_sd외부 microSD 카드를 읽었습니다 . 일부 LGS에, 그건 /_ExternalSD; 일부 장치에서는 /sdcard입니다. 후자는 심볼릭 링크 /storage/sdcard0또는 이와 유사한 링크 일 수 있지만 이러한 다른 것들은 실제로 확실하게 /storage/*/mount/*?
LarsH 2016 년

또한 pathname.canWrite()WRITE_EXTERNAL_STORAGE 권한 을 사용해야 하고 필요합니까? 왜 전화하지 pathname.canRead()?
LarsH

1

그것은 너무 늦었지만 마침내 나는 안드로이드 2.2 이상에서 작동하는 대부분의 장치 (제조업체 및 안드로이드 버전)를 테스트 한 것을 얻었습니다. 작동하지 않는 경우 장치 이름으로 주석 처리하십시오. 내가 고칠 것이다. 관심있는 사람이라면 어떻게 작동하는지 설명 할 것입니다.

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

import android.util.Log;


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

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

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

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

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

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

                        }
                    }

                }
            }

        }

        return sdpath;
    }

    static class MmcblkFilter implements FilenameFilter {
        private String pattern;

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

        }

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

    }

}

downvoter 안녕하세요, 시도하십시오. 작동하지 않으면 장치에 의견을 말하십시오. Android 2.2 이상에서 수천 대 이상의 기기를 사용합니다
Ajeet47

수업은 Samsung Galaxy S4, GT-I9500, Android 5.0.1에서 / mnt / media_rw / extSdCard를 제공합니다 (기기는 루팅되지 않음). 그러나 ES 파일 관리자를 사용하여 / mnt / media_rw 폴더에는 아무것도 보이지 않습니다.
isabsent

@isabsent if if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT) {File [] file = context.getExternalFilesDirs (null); return file.length> 1? file [1] : 널; }
Ajeet47

stackoverflow.com/a/27197248/753575 에 대해 어떻게 생각 하십니까? 이 접근 방식이 Build.VERSION.SDK_INT> = Build.VERSION_CODES.KITKAT의 경우 더 포괄적입니까?
isasent

예. context.getExternalFilesDirs (null)에서 의미있는 경로를 얻을 수 있지만 루트 경로에 대해서는 트리밍을해야합니다 (앱 디렉토리의 경로를 반환합니다. "/ Android"에 트리밍)
Ajeet47

1

아래 코드를 작성하면 위치를 얻을 수 있습니다.

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

sd_card 내의 / android / data 위치에 앱 데이터를 저장합니다.

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

list[1]+"/fol" 

내부의 위치 패스 0을 얻고 sdcard 대 파일 배열의 경우 1을 전달합니다.

나는이 코드를 moto g4 plus 및 Samsung 장치에서 테스트했습니다 (모두 잘 작동합니다).

이것이 도움이되기를 바랍니다.


때로는 SD 카드 경로는 인덱스 1에, 나는 다른 것을 따르지는 인덱스 0 나은에 있던 경우 본되지 않는다
래 가브 Satyadev

1

이동식 을 찾는 데 사용하는 방법은 다음과 같습니다. SD 카드 . 복잡하고 일부 상황에서는 과도하게 사용되지만 지난 몇 년 동안 테스트 한 다양한 Android 버전 및 장치 제조업체에서 작동합니다. API 레벨 15 이후 SD 카드를 찾지 못한 장치가 마운트되어 있으면 장치를 알 수 없습니다. 특히 알려진 파일의 이름을 지정하면 대부분의 경우 오 탐지를 반환하지 않습니다.

그래도 작동하지 않는 경우 알려주십시오.

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.regex.Pattern;

public class SDCard {
    private static final String TAG = "SDCard";

    /** In some scenarios we can expect to find a specified file or folder on SD cards designed
     * to work with this app. If so, set KNOWNFILE to that filename. It will make our job easier.
     * Set it to null otherwise. */
    private static final String KNOWNFILE = null;

    /** Common paths for microSD card. **/
    private static String[] commonPaths = {
            // Some of these taken from
            // /programming/13976982/removable-storage-external-sdcard-path-by-manufacturers
            // These are roughly in order such that the earlier ones, if they exist, are more sure
            // to be removable storage than the later ones.
            "/mnt/Removable/MicroSD",
            "/storage/removable/sdcard1", // !< Sony Xperia Z1
            "/Removable/MicroSD", // Asus ZenPad C
            "/removable/microsd",
            "/external_sd", // Samsung
            "/_ExternalSD", // some LGs
            "/storage/extSdCard", // later Samsung
            "/storage/extsdcard", // Main filesystem is case-sensitive; FAT isn't.
            "/mnt/extsd", // some Chinese tablets, e.g. Zeki
            "/storage/sdcard1", // If this exists it's more likely than sdcard0 to be removable.
            "/mnt/extSdCard",
            "/mnt/sdcard/external_sd",
            "/mnt/external_sd",
            "/storage/external_SD",
            "/storage/ext_sd", // HTC One Max
            "/mnt/sdcard/_ExternalSD",
            "/mnt/sdcard-ext",

            "/sdcard2", // HTC One M8s
            "/sdcard1", // Sony Xperia Z
            "/mnt/media_rw/sdcard1",   // 4.4.2 on CyanogenMod S3
            "/mnt/sdcard", // This can be built-in storage (non-removable).
            "/sdcard",
            "/storage/sdcard0",
            "/emmc",
            "/mnt/emmc",
            "/sdcard/sd",
            "/mnt/sdcard/bpemmctest",
            "/mnt/external1",
            "/data/sdext4",
            "/data/sdext3",
            "/data/sdext2",
            "/data/sdext",
            "/storage/microsd" //ASUS ZenFone 2

            // If we ever decide to support USB OTG storage, the following paths could be helpful:
            // An LG Nexus 5 apparently uses usb://1002/UsbStorage/ as a URI to access an SD
            // card over OTG cable. Other models, like Galaxy S5, use /storage/UsbDriveA
            //        "/mnt/usb_storage",
            //        "/mnt/UsbDriveA",
            //        "/mnt/UsbDriveB",
    };

    /** Find path to removable SD card. */
    public static File findSdCardPath(Context context) {
        String[] mountFields;
        BufferedReader bufferedReader = null;
        String lineRead = null;

        /** Possible SD card paths */
        LinkedHashSet<File> candidatePaths = new LinkedHashSet<>();

        /** Build a list of candidate paths, roughly in order of preference. That way if
         * we can't definitively detect removable storage, we at least can pick a more likely
         * candidate. */

        // Could do: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists and has contents,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // I moved hard-coded paths toward the end, but we need to make sure we put the ones in
        // backwards order that are returned by the OS. And make sure the iterators respect
        // the order!
        // This is because when multiple "external" storage paths are returned, it's always (in
        // experience, but not guaranteed by documentation) with internal/emulated storage
        // first, removable storage second.

        // Add value of environment variables as candidates, if set:
        // EXTERNAL_STORAGE, SECONDARY_STORAGE, EXTERNAL_SDCARD_STORAGE
        // But note they are *not* necessarily *removable* storage! Especially EXTERNAL_STORAGE.
        // And they are not documented (API) features. Typically useful only for old versions of Android.

        String val = System.getenv("SECONDARY_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
        val = System.getenv("EXTERNAL_SDCARD_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        // Get listing of mounted devices with their properties.
        ArrayList<File> mountedPaths = new ArrayList<>();
        try {
            // Note: Despite restricting some access to /proc (http://stackoverflow.com/a/38728738/423105),
            // Android 7.0 does *not* block access to /proc/mounts, according to our test on George's Alcatel A30 GSM.
            bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));

            // Iterate over each line of the mounts listing.
            while ((lineRead = bufferedReader.readLine()) != null) {
                Log.d(TAG, "\nMounts line: " + lineRead);
                mountFields = lineRead.split(" ");

                // columns: device, mountpoint, fs type, options... Example:
                // /dev/block/vold/179:97 /storage/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
                String device = mountFields[0], path = mountFields[1], fsType = mountFields[2];

                // The device, path, and fs type must conform to expected patterns.
                if (!(devicePattern.matcher(device).matches() &&
                        pathPattern.matcher(path).matches() &&
                        fsTypePattern.matcher(fsType).matches()) ||
                        // mtdblock is internal, I'm told.
                        device.contains("mtdblock") ||
                        // Check for disqualifying patterns in the path.
                        pathAntiPattern.matcher(path).matches()) {
                    // If this mounts line fails our tests, skip it.
                    continue;
                }

                // TODO maybe: check options to make sure it's mounted RW?
                // The answer at http://stackoverflow.com/a/13648873/423105 does.
                // But it hasn't seemed to be necessary so far in my testing.

                // This line met the criteria so far, so add it to candidate list.
                addPath(path, null, mountedPaths);
            }
        } catch (IOException ignored) {
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ignored) {
                }
            }
        }

        // Append the paths from mount table to candidate list, in reverse order.
        if (!mountedPaths.isEmpty()) {
            // See https://stackoverflow.com/a/5374346/423105 on why the following is necessary.
            // Basically, .toArray() needs its parameter to know what type of array to return.
            File[] mountedPathsArray = mountedPaths.toArray(new File[mountedPaths.size()]);
            addAncestors(candidatePaths, mountedPathsArray);
        }

        // Add hard-coded known common paths to candidate list:
        addStrings(candidatePaths, commonPaths);

        // If the above doesn't work we could try the following other options, but in my experience they
        // haven't added anything helpful yet.

        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.

        // This may be non-removable.
        Log.d(TAG, "Environment.getExternalStorageDirectory():");
        addPath(null, ancestor(Environment.getExternalStorageDirectory()), candidatePaths);

        // Context.getExternalFilesDirs() is only available from API level 19. You can use
        // ContextCompat.getExternalFilesDirs() on earlier APIs, but it only returns one dir anyway.
        Log.d(TAG, "context.getExternalFilesDir(null):");
        addPath(null, ancestor(context.getExternalFilesDir(null)), candidatePaths);

        // "Returns absolute paths to application-specific directories on all external storage
        // devices where the application can place persistent files it owns."
        // We might be able to use these to deduce a higher-level folder that isn't app-specific.
        // Also, we apparently have to call getExternalFilesDir[s](), at least in KITKAT+, in order to ensure that the
        // "external files" directory exists and is available.
        Log.d(TAG, "ContextCompat.getExternalFilesDirs(context, null):");
        addAncestors(candidatePaths, ContextCompat.getExternalFilesDirs(context, null));
        // Very similar results:
        Log.d(TAG, "ContextCompat.getExternalCacheDirs(context):");
        addAncestors(candidatePaths, ContextCompat.getExternalCacheDirs(context));

        // TODO maybe: use getExternalStorageState(File path), with and without an argument, when
        // available. With an argument is available since API level 21.
        // This may not be necessary, since we also check whether a directory exists,
        // which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.

        // A "public" external storage directory. But in my experience it doesn't add anything helpful.
        // Note that you can't pass null, or you'll get an NPE.
        final File publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
        // Take the parent, because we tend to get a path like /pathTo/sdCard/Music.
        addPath(null, publicDirectory.getParentFile(), candidatePaths);
        // EXTERNAL_STORAGE: may not be removable.
        val = System.getenv("EXTERNAL_STORAGE");
        if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);

        if (candidatePaths.isEmpty()) {
            Log.w(TAG, "No removable microSD card found.");
            return null;
        } else {
            Log.i(TAG, "\nFound potential removable storage locations: " + candidatePaths);
        }

        // Accept or eliminate candidate paths if we can determine whether they're removable storage.
        // In Lollipop and later, we can check isExternalStorageRemovable() status on each candidate.
        if (Build.VERSION.SDK_INT >= 21) {
            Iterator<File> itf = candidatePaths.iterator();
            while (itf.hasNext()) {
                File dir = itf.next();
                // handle illegalArgumentException if the path is not a valid storage device.
                try {
                    if (Environment.isExternalStorageRemovable(dir)
                        // && containsKnownFile(dir)
                            ) {
                        Log.i(TAG, dir.getPath() + " is removable external storage");
                        return dir;
                    } else if (Environment.isExternalStorageEmulated(dir)) {
                        Log.d(TAG, "Removing emulated external storage dir " + dir);
                        itf.remove();
                    }
                } catch (IllegalArgumentException e) {
                    Log.d(TAG, "isRemovable(" + dir.getPath() + "): not a valid storage device.", e);
                }
            }
        }

        // Continue trying to accept or eliminate candidate paths based on whether they're removable storage.
        // On pre-Lollipop, we only have singular externalStorage. Check whether it's removable.
        if (Build.VERSION.SDK_INT >= 9) {
            File externalStorage = Environment.getExternalStorageDirectory();
            Log.d(TAG, String.format(Locale.ROOT, "findSDCardPath: getExternalStorageDirectory = %s", externalStorage.getPath()));
            if (Environment.isExternalStorageRemovable()) {
                // Make sure this is a candidate.
                // TODO: Does this contains() work? Should we be canonicalizing paths before comparing?
                if (candidatePaths.contains(externalStorage)
                    // && containsKnownFile(externalStorage)
                        ) {
                    Log.d(TAG, "Using externalStorage dir " + externalStorage);
                    return externalStorage;
                }
            } else if (Build.VERSION.SDK_INT >= 11 && Environment.isExternalStorageEmulated()) {
                Log.d(TAG, "Removing emulated external storage dir " + externalStorage);
                candidatePaths.remove(externalStorage);
            }
        }

        // If any directory contains our special test file, consider that the microSD card.
        if (KNOWNFILE != null) {
            for (File dir : candidatePaths) {
                Log.d(TAG, String.format(Locale.ROOT, "findSdCardPath: Looking for known file in candidate path, %s", dir));
                if (containsKnownFile(dir)) return dir;
            }
        }

        // If we don't find the known file, still try taking the first candidate.
        if (!candidatePaths.isEmpty()) {
            Log.d(TAG, "No definitive path to SD card; taking the first realistic candidate.");
            return candidatePaths.iterator().next();
        }

        // If no reasonable path was found, give up.
        return null;
    }

    /** Add each path to the collection. */
    private static void addStrings(LinkedHashSet<File> candidatePaths, String[] newPaths) {
        for (String path : newPaths) {
            addPath(path, null, candidatePaths);
        }
    }

    /** Add ancestor of each File to the collection. */
    private static void addAncestors(LinkedHashSet<File> candidatePaths, File[] files) {
        for (int i = files.length - 1; i >= 0; i--) {
            addPath(null, ancestor(files[i]), candidatePaths);
        }
    }

    /**
     * Add a new candidate directory path to our list, if it's not obviously wrong.
     * Supply path as either String or File object.
     * @param strNew path of directory to add (or null)
     * @param fileNew directory to add (or null)
     */
    private static void addPath(String strNew, File fileNew, Collection<File> paths) {
        // If one of the arguments is null, fill it in from the other.
        if (strNew == null) {
            if (fileNew == null) return;
            strNew = fileNew.getPath();
        } else if (fileNew == null) {
            fileNew = new File(strNew);
        }

        if (!paths.contains(fileNew) &&
                // Check for paths known not to be removable SD card.
                // The antipattern check can be redundant, depending on where this is called from.
                !pathAntiPattern.matcher(strNew).matches()) {

            // Eliminate candidate if not a directory or not fully accessible.
            if (fileNew.exists() && fileNew.isDirectory() && fileNew.canExecute()) {
                Log.d(TAG, "  Adding candidate path " + strNew);
                paths.add(fileNew);
            } else {
                Log.d(TAG, String.format(Locale.ROOT, "  Invalid path %s: exists: %b isDir: %b canExec: %b canRead: %b",
                        strNew, fileNew.exists(), fileNew.isDirectory(), fileNew.canExecute(), fileNew.canRead()));
            }
        }
    }

    private static final String ANDROID_DIR = File.separator + "Android";

    private static File ancestor(File dir) {
        // getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
        //   /storage/sdcard1/Android/data/com.mybackuparchives.android/files
        // so we want the great-great-grandparent folder.
        if (dir == null) {
            return null;
        } else {
            String path = dir.getAbsolutePath();
            int i = path.indexOf(ANDROID_DIR);
            if (i == -1) {
                return dir;
            } else {
                return new File(path.substring(0, i));
            }
        }
    }

    /** Returns true iff dir contains the special test file.
     * Assumes that dir exists and is a directory. (Is this a necessary assumption?) */
    private static boolean containsKnownFile(File dir) {
        if (KNOWNFILE == null) return false;

        File knownFile = new File(dir, KNOWNFILE);
        return knownFile.exists();
    }

    private static Pattern
            /** Pattern that SD card device should match */
            devicePattern = Pattern.compile("/dev/(block/.*vold.*|fuse)|/mnt/.*"),
    /** Pattern that SD card mount path should match */
    pathPattern = Pattern.compile("/(mnt|storage|external_sd|extsd|_ExternalSD|Removable|.*MicroSD).*",
            Pattern.CASE_INSENSITIVE),
    /** Pattern that the mount path should not match.
     * 'emulated' indicates an internal storage location, so skip it.
     * 'asec' is an encrypted package file, decrypted and mounted as a directory. */
    pathAntiPattern = Pattern.compile(".*(/secure|/asec|/emulated).*"),
    /** These are expected fs types, including vfat. tmpfs is not OK.
     * fuse can be removable SD card (as on Moto E or Asus ZenPad), or can be internal (Huawei G610). */
    fsTypePattern = Pattern.compile(".*(fat|msdos|ntfs|ext[34]|fuse|sdcard|esdfs).*");
}

추신

  • <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />매니페스트를 잊지 마십시오 . API 레벨 23 이상에서는 checkSelfPermission/ 를 사용해야 requestPermissions합니다.
  • SD 카드에있는 파일이나 폴더가 있으면 KNOWNFILE = "myappfile"로 설정하십시오. 보다 정확하게 감지 할 수 있습니다.
  • 분명히 findSdCardPath(),필요할 때마다 다시 계산하는 대신 값을 캐시하고 싶을 것입니다.
  • Log.d()위의 코드 에는 많은 로깅 ( )이 있습니다. 올바른 경로를 찾지 못한 경우를 진단하는 데 도움이됩니다. 로깅을 원하지 않으면 주석 처리하십시오.

Downvoters,이 답변을 개선 할 수있는 방법을 제안 할 수 있습니까?
LarsH

1

내가 찾은 유일한 작업 솔루션 은 리플렉션을 사용 하는 솔루션이었습니다.

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

    StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
    Class<?> storageVolumeClazz = null;
    try {
        storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
        Method getPath = storageVolumeClazz.getMethod("getPath");
        Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
        Object result = getVolumeList.invoke(mStorageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            Object storageVolumeElement = Array.get(result, i);
            String path = (String) getPath.invoke(storageVolumeElement);
            boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
            if (is_removable == removable) {
                return path;
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}

Google은 새로운 버전의 Android에서 이전 버전과의 호환성을 높이 평가하지 않기 때문에 개인적으로 리플렉션을 사용하지 않습니다!
Behrouz. M

0

왜 그런지 모르겠지만 공용 저장소 디렉토리에서 만든 파일에서 .createNewFile ()을 호출해야합니다. 프레임 워크에서 해당 방법에 대한 의견은 유용하지 않다고 말합니다. 샘플은 다음과 같습니다.


 String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS) + File.separator + "My Directory";
            final File myDir = new File(myPath);
            try {
                myDir.mkdirs();
            } catch (Exception ex) {
                Toast.makeText(this, "error: " + ex.getMessage(), Toast.LENGTH_LONG).show();
            }

        String fname = "whatever";
        File newFile = new File(myDir, fname);

        Log.i(TAG, "File exists --> " + newFile.exists()) //will be false  
    try {
            if (newFile.createNewFile()) {

                 //continue 

              } else {

                Log.e(TAG, "error creating file");

            }

        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }


0

SD 카드가 장치에서 사용 가능한지 여부를 확인하고 장치에서 SD 카드 경로를 얻을 수있는 유틸리티 방법을 만들었습니다.

필요한 두 가지 방법을 프로젝트 클래스에 복사 할 수 있습니다. 그게 다야.

public String isRemovableSDCardAvailable() {
    final String FLAG = "mnt";
    final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
    final String EXTERNAL_STORAGE_DOCOMO = System.getenv("EXTERNAL_STORAGE_DOCOMO");
    final String EXTERNAL_SDCARD_STORAGE = System.getenv("EXTERNAL_SDCARD_STORAGE");
    final String EXTERNAL_SD_STORAGE = System.getenv("EXTERNAL_SD_STORAGE");
    final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE");

    Map<Integer, String> listEnvironmentVariableStoreSDCardRootDirectory = new HashMap<Integer, String>();
    listEnvironmentVariableStoreSDCardRootDirectory.put(0, SECONDARY_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(1, EXTERNAL_STORAGE_DOCOMO);
    listEnvironmentVariableStoreSDCardRootDirectory.put(2, EXTERNAL_SDCARD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(3, EXTERNAL_SD_STORAGE);
    listEnvironmentVariableStoreSDCardRootDirectory.put(4, EXTERNAL_STORAGE);

    File externalStorageList[] = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        externalStorageList = getContext().getExternalFilesDirs(null);
    }
    String directory = null;
    int size = listEnvironmentVariableStoreSDCardRootDirectory.size();
    for (int i = 0; i < size; i++) {
        if (externalStorageList != null && externalStorageList.length > 1 && externalStorageList[1] != null)
            directory = externalStorageList[1].getAbsolutePath();
        else
            directory = listEnvironmentVariableStoreSDCardRootDirectory.get(i);

        directory = canCreateFile(directory);
        if (directory != null && directory.length() != 0) {
            if (i == size - 1) {
                if (directory.contains(FLAG)) {
                    Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
                    return directory;
                } else {
                    return null;
                }
            }
            Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
            return directory;
        }
    }
    return null;
}

/**
 * Check if can create file on given directory. Use this enclose with method
 * {@link BeginScreenFragement#isRemovableSDCardAvailable()} to check sd
 * card is available on device or not.
 * 
 * @param directory
 * @return
 */
public String canCreateFile(String directory) {
    final String FILE_DIR = directory + File.separator + "hoang.txt";
    File tempFlie = null;
    try {
        tempFlie = new File(FILE_DIR);
        FileOutputStream fos = new FileOutputStream(tempFlie);
        fos.write(new byte[1024]);
        fos.flush();
        fos.close();
        Log.e(getClass().getSimpleName(), "Can write file on this directory: " + FILE_DIR);
    } catch (Exception e) {
        Log.e(getClass().getSimpleName(), "Write file error: " + e.getMessage());
        return null;
    } finally {
        if (tempFlie != null && tempFlie.exists() && tempFlie.isFile()) {
            // tempFlie.delete();
            tempFlie = null;
        }
    }
    return directory;
}

-1

모든 외부 장치에서 작동하지만 외부 장치 폴더 이름 만 가져 오면 File 클래스를 사용하여 지정된 위치에서 파일을 가져와야합니다.

public static List<String> getExternalMounts() {
        final List<String> out = new ArrayList<>();
        String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
        String s = "";
        try {
            final Process process = new ProcessBuilder().command("mount")
                    .redirectErrorStream(true).start();
            process.waitFor();
            final InputStream is = process.getInputStream();
            final byte[] buffer = new byte[1024];
            while (is.read(buffer) != -1) {
                s = s + new String(buffer);
            }
            is.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }

        // parse output
        final String[] lines = s.split("\n");
        for (String line : lines) {
            if (!line.toLowerCase(Locale.US).contains("asec")) {
                if (line.matches(reg)) {
                    String[] parts = line.split(" ");
                    for (String part : parts) {
                        if (part.startsWith("/"))
                            if (!part.toLowerCase(Locale.US).contains("vold"))
                                out.add(part);
                    }
                }
            }
        }
        return out;
    }

부름:

List<String> list=getExternalMounts();
        if(list.size()>0)
        {
            String[] arr=list.get(0).split("/");
            int size=0;
            if(arr!=null && arr.length>0) {
                size= arr.length - 1;
            }
            File parentDir=new File("/storage/"+arr[size]);
            if(parentDir.listFiles()!=null){
                File parent[] = parentDir.listFiles();

                for (int i = 0; i < parent.length; i++) {

                    // get file path as parent[i].getAbsolutePath());

                }
            }
        }

외부 저장소에 액세스

외부 저장소에서 파일을 읽거나 쓰려면 앱이 READ_EXTERNAL_STORAGE 또는 WRITE_EXTERNAL_STORAGE 시스템 권한을 얻어야합니다. 예를 들면 다음과 같습니다.

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

-2

/ sdcard => 내부 저장소 (Symlink이지만 작동해야 함)

/ mnt / extSdCard => 외부 SD 카드

삼성 갤럭시 S3 용

아마도 대부분의 경우 이것이 사실이라고 생각할 수 있습니다 ... 그러나 이중 확인!


8
나는 몇 가지 다른 안드로이드 폰을 가지고 있었고, 그 중 절반은 삼성이며,이 위치가 사용되는 것을 보지 못했습니다. S3에서는 사실 일 수도 있지만 "대부분의 경우 이것이 사실 일 수 있습니다"라고 말하는 것은 완전히 잘못된 것입니다.
Geobits

잘못된. /sdcard내 소니 2305에 외부에 대한 심볼릭 링크입니다.
jiggunjer

2
내가 아닐 수도 있다고 말하지 않았습니까?
robbyoconnor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.