인디노트

Android 의 Storage Path 및 코드 구현 기술 본문

소스 팁/Java, Android, Kotlin

Android 의 Storage Path 및 코드 구현 기술

인디개발자 2016. 5. 16. 08:24

안드로이드 앱에서 스토리지에 파일을 쓰고 읽기 위해서는 메니페스트 (AndroidManifest.xml) 파일에 다음과 같은 권한을 기록해줘야 한다.


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

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



# 외장 메모리 (SD CARD)의 절대 경로 알아내기


String sdcardPath = null;

String sdcardStat = Environment.getExternalStorageState();

if(sdcardStat.equals(Environment.MEDIA_MOUNTED))

{

sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath();

}


여기에서 생각해야 할 내용이 있다. 만약 안드로이드 기기에 외장 메모리가 한개 이상인 경우에는 어떠할까...

Android 에서 제공되는 Environment 클래스에서 getExternalStorageDirectory() 함수의 코드를 살펴보자.


    public static File getExternalStorageDirectory() {

        throwIfUserRequired();

        return sCurrentUser.getExternalDirsForApp()[0];

    }


이렇게 getExternalStorageDirectory 함수는 현재의 사용자 (기기에 로그온 한 사용자, 초기에는 이런 내용이 없었는데 모바일 기기를 개인용 혹은 업무용으로 병용으로 사용할 수 있게 하기 위해서 사용자 개념을 도입함) 의 해당 앱(App) 에 제공되는 외장 디렉토리 파일들의 핸들 첫번째를 가져오게 되어 있다.


현재 대부분의 안드로이드 기기는 외장 메모리를 1개 밖에 탑재하지 못한다. 그것은 기기의 크기 및 휴대성 등을 고려하고 또한 SD 카드의 단일 용량이 계속 증가하는 추세이기 때문에 굳이 앱에서 이를 고려할 필요는 없어 보인다. 하지만, 미래에는 어떤 기기가 나올지 모르기 때문에 대처하는 현명함이 필요할 것 같다.


참고로 getExternalStorageState() 함수도 첫번째 외장 스토리지 패스의 상태를 돌려준다.


    public static String getExternalStorageState() {

        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];

        return getExternalStorageState(externalDir);

    }



이제 sdcardPath 를 가져온 결과의 예를 살펴보자.


Android SDK: 19 (4.4.4)

/storage/emulated/0


Android SDK: 21 (5.0.2)

/storage/sdcard


Android SDK: 23 (6.0)

/storage/emulated/0


이와같이 안드로이드의 빌드 버전 혹은 디바이스의 제조사 등에 따라서 다를 수 있다는것에 유의를 해야 할 것이다.


그런데 여기서 또하나 주의해야 할 사항이 있다. 과연 getExternalStorageDirectory 가 우리가 생각하는 외장 SD 카드를 뜻할까? 그렇게 오해할 수 있다. 함수 이름에 ExternalStorage 라는 내용이 있기 때문에 당연히 그렇게 오해 할 수 있다.


하지만 실상은 그렇지 않다. 여기서 말하는 External Storage 는 디바이스 장치의 내장 스토리지의 패스이다.

안드로이드는 크게 2 가지의 (혹은 그 이상)의 스토리지 영역을 가진다.


- System Storage

- External Storage(s)


"/" 로 표현되는 루트 폴더가 System 영역이다. 또한 모든 Storage Path 는 System 영역에 특정 패스로 마운트 되어진다.





# 슬롯에 꽂은 외장 메모리의 절대 경로 알아내기


아쉽게도 지금까지의 안드로이드에서 제공하는 API 중에 슬롯에 꽂아져 있는 외장 메모리의 패스 (마운트 포인트) 를 알려주는 API 는 없는것 같다.

단, 다음과 같은 방법으로 해당 패스를 알아내는 방법이다.


String legacyPath = System.getenv("EXTERNAL_STORAGE");

String secondaryPath = System.getenv("SECONDARY_STORAGE");


각각의 출력은 다음과 같다. (제조사 마다 다를 수 있음)

/storage/emulated/legacy

/storage/extSdCard


여기서 우리는 두번째 스토리지의 뜻인 "SECONDARY_STORAGE" 값으로 가져온 결과를 사용하면 될 것이다. 아쉽게도 지원이 안되는 디바이스의 경우가 많다. 따라서, 이 방법으로 앱을 만든다면 원치않게 불량 앱이 될 수 있다.

결국 다음과 같은 클래스 함수를 만들어 사용하면 된다.

public class SDCard 

{

public static String getExternalSDCardPath()

{

HashSet<String> hs = getExternalMounts();

for(String extSDCardPath : hs)

{

return extSDCardPath;

}

return null;

}


public static HashSet<String> getExternalMounts()

{

final HashSet<String> out = new HashSet<String>();

//String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";

String reg = "(?i).*media_rw.*(storage).*(sdcardfs).*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") && !part.toLowerCase(Locale.US).contains("/mnt/"))

{

out.add(part);

}

}

}

}

}

}


return out;

}

}






반응형
Comments