외부 저장소에 파일 저장

외부 저장소 사용은 다른 앱과 공유하려는 파일이나 사용자가 컴퓨터에서 액세스할 수 있는 파일에 적합합니다.

외부 저장소는 일반적으로 이동식 기기(예: SD 카드)를 통해 사용할 수 있습니다. Android는 /sdcard와 같은 경로를 사용하여 이러한 기기를 표시합니다.

저장소 권한을 요청하고 저장소 사용 여부를 확인하고 나면 다음 파일 유형을 저장할 수 있습니다.

  • 공개 파일: 다른 앱에서 그리고 사용자가 자유롭게 사용할 수 있는 파일입니다. 사용자가 앱을 제거해도 공개 파일은 사용자가 계속 사용할 수 있어야 합니다. 예를 들어 앱에서 캡처한 사진은 공개 파일로 저장되어야 합니다.
  • 비공개 파일: Context.getExternalFilesDir()를 사용하여 액세스하는 앱별 디렉터리에 저장된 파일입니다. 비공개 파일은 사용자가 앱을 제거할 때 정리됩니다. 이 파일은 외부 저장소에 있기 때문에 사용자가 그리고 다른 앱에서 기술적으로 액세스할 수는 있지만 앱 외부의 사용자에게 가치가 있는 것은 아닙니다. 다른 앱과 공유하지 않으려는 파일에 이 디렉터리를 사용하세요.

이 가이드에서는 기기의 외부 저장소 기기에서 사용할 수 있는 파일 관리 방법을 설명합니다. 내부 저장소의 파일을 작업하는 방법에 관한 안내는 내부 저장소의 파일 관리 방법에 관한 가이드를 참조하세요.

가상 외부 저장소 기기 설정

이동식 외부 저장소가 없는 기기에서는 다음 명령어를 사용하여 테스트 목적으로 가상 디스크를 사용 설정합니다.

    adb shell sm set-virtual-disk true
    

외부 저장소 권한 요청

Android에는 외부 저장소의 파일에 액세스하기 위한 다음 권한이 포함되어 있습니다.

READ_EXTERNAL_STORAGE
앱에서 외부 저장소 기기 내의 파일에 액세스할 수 있도록 허용합니다.
WRITE_EXTERNAL_STORAGE
앱에서 외부 저장소 기기 내의 파일을 작성 및 수정할 수 있도록 허용합니다. 이 권한이 있는 앱에는 READ_EXTERNAL_STORAGE 권한도 자동으로 부여됩니다.

Android 4.4(API 레벨 19)부터는 앱별 디렉터리에서 파일을 읽거나 쓰는 데 저장소 관련 권한이 필요하지 않습니다. 따라서 앱에서 Android 4.3(API 레벨 18) 이하를 지원하고 앱별 디렉터리에만 액세스하려면 maxSdkVersion 속성을 추가하여 하위 버전의 Android에서만 권한이 요청되었다고 선언해야 합니다.

    <manifest ...>
        <!-- If you need to modify files in external storage, request
             WRITE_EXTERNAL_STORAGE instead. -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
                         android:maxSdkVersion="18" />
    </manifest>
    

외부 저장소의 사용 여부 확인

사용자가 저장소를 다른 컴퓨터에 마운트했거나 외부 저장소를 제공하는 SD 카드를 삭제한 경우와 같이 외부 저장소를 사용하지 못할 수 있으므로 액세스하기 전에 항상 볼륨이 사용 가능한지 확인해야 합니다. getExternalStorageState()를 호출하여 외부 저장소 상태를 쿼리할 수 있습니다. 반환된 상태가 MEDIA_MOUNTED라면 파일을 읽고 쓸 수 있습니다. 반환된 상태가 MEDIA_MOUNTED_READ_ONLY라면 파일을 읽을 수만 있습니다.

예를 들어 다음 메서드는 저장소의 사용 가능 여부를 확인하는 데 유용합니다.

Kotlin

    /* Checks if external storage is available for read and write */
    fun isExternalStorageWritable(): Boolean {
        return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
    }

    /* Checks if external storage is available to at least read */
    fun isExternalStorageReadable(): Boolean {
         return Environment.getExternalStorageState() in
            setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY)
    }
    

자바

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    /* Checks if external storage is available to at least read */
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
            Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }
    

공개 디렉터리에 저장

다른 앱에서 액세스할 수 있는 외부 저장소에 파일을 저장하려면 다음 API 중 하나를 사용하세요.

  • 사진이나 오디오 파일, 동영상을 저장하는 경우 MediaStore API를 사용합니다.
  • 기타 다른 파일(예: PDF 문서)을 저장하는 경우 저장소 액세스 프레임워크의 일부인 ACTION_CREATE_DOCUMENT 인텐트를 사용합니다.

미디어 스캐너에서 파일을 숨기려면 앱별 디렉터리에 파일 이름이 .nomedia인 빈 파일을 포함합니다(파일 이름 맨 앞의 점에 주의하세요). 이로써 미디어 스캐너에서 MediaStore API를 통해 미디어 파일을 읽고 다른 앱에 제공하는 것을 방지합니다.

비공개 디렉터리에 저장

앱에 비공개인 파일을 외부 저장소에 저장하려면 getExternalFilesDir()를 호출하고 원하는 디렉터리 유형을 나타내는 이름을 전달하여 앱별 디렉터리를 획득할 수 있습니다. 이 방식으로 만들어진 각 디렉터리는 앱의 모든 외부 저장소 파일을 캡슐화하는 상위 디렉터리에 추가되며 이러한 파일은 사용자가 앱을 제거할 때 시스템에서 정리됩니다.

다음 코드 스니펫은 개별 사진 앨범의 디렉터리를 만드는 방법을 보여줍니다.

Kotlin

    fun getPrivateAlbumStorageDir(context: Context, albumName: String): File? {
        // Get the directory for the app's private pictures directory.
        val file = File(context.getExternalFilesDir(
                Environment.DIRECTORY_PICTURES), albumName)
        if (!file?.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created")
        }
        return file
    }
    

자바

    public File getPrivateAlbumStorageDir(Context context, String albumName) {
        // Get the directory for the app's private pictures directory.
        File file = new File(context.getExternalFilesDir(
                Environment.DIRECTORY_PICTURES), albumName);
        if (!file.mkdirs()) {
            Log.e(LOG_TAG, "Directory not created");
        }
        return file;
    }
    

DIRECTORY_PICTURES와 같은 API 상수에서 제공한 디렉터리 이름을 사용하는 것이 중요합니다. 이러한 디렉터리 이름은 시스템에서 파일이 올바르게 처리되도록 합니다. 예를 들어 DIRECTORY_RINGTONES에 저장된 파일은 시스템 미디어 스캐너에서 음악이 아니라 벨소리로 분류됩니다.

미리 정의된 하위 디렉터리 이름 중 파일에 알맞은 이름이 없을 경우 대신 getExternalFilesDir()을 호출하여 null을 전달할 수 있습니다. 이렇게 해서 외부 저장소에 있는 앱의 비공개 디렉터리의 루트 디렉터리가 반환됩니다.

여러 저장소 위치 중에서 선택

때로는 외부 저장소처럼 사용하기 위해 내부 메모리의 파티션을 할당하는 기기에도 SD카드 슬롯이 있습니다. 즉, 기기에는 서로 다른 외부 저장소 디렉터리가 두 개 있으므로 '비공개' 파일을 외부 저장소에 작성할 때 사용할 디렉터리를 선택해야 합니다.

Android 4.4(API 레벨 19)부터는 getExternalFilesDirs()를 호출하여 두 위치에 모두 액세스할 수 있고 그러면 각 저장소 위치의 항목이 있는 File 배열이 반환됩니다. 배열의 첫 번째 항목은 기본 외부 저장소로 간주되며 가득 차거나 사용할 수 없는 경우가 아니라면 이 위치를 사용해야 합니다.

앱이 Android 4.3 이하를 지원하는 경우 지원 라이브러리의 정적 메서드 ContextCompat.getExternalFilesDirs()를 사용해야 합니다. 이렇게 하면 항상 File 배열이 반환되지만 기기에서 Android 4.3 이하를 실행하는 경우 기본 외부 저장소의 항목이 하나만 포함됩니다. 두 번째 저장 위치가 있으면 Android 4.3 이하에서는 액세스할 수 없습니다.

고유한 볼륨 이름

Android 10(API 레벨 29) 이상을 타겟팅하는 앱은 시스템에서 각 외부 저장소 기기에 할당한 고유 이름에 액세스할 수 있습니다. 이 이름 지정 시스템을 통해 효율적으로 콘텐츠를 구성하고 콘텐츠 색인을 생성하며 새 콘텐츠의 저장 위치를 관리할 수 있습니다.

기본 공유 저장공간 기기의 이름은 항상 VOLUME_EXTERNAL_PRIMARY입니다. 다른 볼륨은 MediaStore.getExternalVolumeNames()를 호출하여 탐색할 수 있습니다.

특정 볼륨을 쿼리하거나 삽입, 업데이트, 삭제하려면 다음 코드 스니펫과 같이 MediaStore API에서 제공하는 getContentUri() 메서드에 볼륨 이름을 전달합니다.

    // Assumes that the storage device of interest is the 2nd one
    // that your app recognizes.
    val volumeNames = MediaStore.getExternalVolumeNames(context)
    val selectedVolumeName = volumeNames[1]
    val collection = MediaStore.Audio.Media.getContentUri(selectedVolumeName)
    // ... Use a ContentResolver to add items to the returned media collection.
    

추가 리소스

기기의 저장소에 파일을 저장하는 방법에 관한 자세한 내용은 다음 리소스를 참조하세요.

Codelabs