범위가 지정된 디렉터리 액세스 사용

일반적으로 사진 앱과 같은 앱은 외부 저장소의 특정 디렉토리(예: Pictures 디렉토리)에만 액세스해야 합니다. 외부 저장소에 액세스하는 기존 방식은 이런 유형의 앱에 대한 대상 디렉토리 액세스를 쉽게 제공하지 못합니다. 예:

  • manifest에서 READ_EXTERNAL_STORAGE 또는 WRITE_EXTERNAL_STORAGE를 요청하면 외부 저장소의 모든 공개 디렉터리에 액세스할 수 있습니다. 이는 앱에 필요한 것보다 과도한 액세스일 수 있습니다.
  • 일반적으로, 저장소 액세스 프레임워크를 사용하면 시스템 UI를 통해 사용자가 디렉터리를 선택할 수 있습니다. 앱이 항상 동일한 외부 디렉터리에 액세스한다면 이 작업이 필요하지 않습니다.

Android 7.0에서는 일반 외부 저장소 디렉토리에 액세스하기 위한 단순화된 API를 제공합니다.

외부 저장소 디렉터리에 액세스

StorageManager 클래스를 사용하여 적절한 StorageVolume 인스턴스를 가져옵니다. 그런 다음 이 인스턴스의 StorageVolume.createAccessIntent() 메서드를 호출하여 인텐트를 만듭니다. 이 인텐트를 사용하여 외부 저장소 디렉토리에 액세스합니다. 이동식 미디어 볼륨을 비롯한 모든 사용 가능한 볼륨의 목록을 가져오려면 StorageManager.getStorageVolumes()를 사용합니다.

특정 파일에 관한 정보가 있으면 StorageManager.getStorageVolume(File)을 사용하여 파일이 포함된 StorageVolume을 가져옵니다. 이 StorageVolume에서 createAccessIntent()를 호출하여 파일의 외부 저장소 디렉터리에 액세스합니다.

외부 SD 카드와 같은 보조 볼륨에서 특정 디렉터리 대신 전체 볼륨에 대한 액세스를 요청하려면 createAccessIntent()를 호출할 때 null을 전달합니다. 기본 볼륨에 null을 전달하거나 잘못된 디렉터리 이름을 전달하는 경우 createAccessIntent()는 null을 반환합니다.

다음 코드 스니펫은 기본 공유 저장소에서 Pictures 디렉토리를 여는 방법의 예시입니다.

Kotlin

    val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val volume: StorageVolume = sm.primaryStorageVolume
    volume.createAccessIntent(Environment.DIRECTORY_PICTURES).also { intent ->
        startActivityForResult(intent, request_code)
    }
    

자바

    StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
    StorageVolume volume = sm.getPrimaryStorageVolume();
    Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
    startActivityForResult(intent, request_code);
    

시스템은 외부 디렉토리에 대한 액세스 권한을 부여하려고 시도하고, 필요한 경우 단순화된 UI를 사용하여 사용자의 액세스를 확인합니다.

그림 1. Pictures 디렉터리에 대한 액세스를 요청하는 애플리케이션.

사용자가 액세스 권한을 부여하면 시스템에서는 결과 코드 RESULT_OK가 포함된 onActivityResult() 재정의 및 URI가 포함된 인텐트 데이터를 호출합니다. 제공된 URI를 사용하여 디렉터리 정보에 액세스할 수 있는데, 이는 저장소 액세스 프레임워크에서 반환한 URI를 사용하는 것과 유사합니다.

사용자가 액세스 권한을 부여하지 않으면 시스템에서는 결과 코드 RESULT_CANCELED가 포함된 onActivityResult() 재정의 및 null 인텐트 데이터를 호출합니다.

특정 외부 디렉터리에 대한 액세스 권한을 얻으면 그 디렉터리의 하위 디렉터리에 대한 액세스 권한도 얻게 됩니다.

이동식 미디어의 디렉터리에 액세스

범위가 지정된 디렉터리 액세스를 사용하여 이동식 미디어의 디렉터리에 액세스하려면, 먼저 다음과 같이 MEDIA_MOUNTED 알림을 수신하는 BroadcastReceiver를 추가합니다.

    <receiver
        android:name=".MediaMountedReceiver"
        android:enabled="true"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_MOUNTED" />
            <data android:scheme="file" />
        </intent-filter>
    </receiver>
    

사용자가 SD 카드 등의 이동식 미디어를 마운트하면 시스템에서는 MEDIA_MOUNTED 알림을 전송합니다. 이 알림은 이동식 미디어 디렉터리에 액세스하는 데 사용할 수 있는 인텐트 데이터의 StorageVolume 개체를 제공합니다. 다음은 이동식 미디어의 Pictures 디렉토리에 액세스하는 예입니다.

Kotlin

    // BroadcastReceiver has already cached the MEDIA_MOUNTED
    // notification Intent in mediaMountedIntent
    val volume =
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME) as StorageVolume
    volume.createAccessIntent(Environment.DIRECTORY_PICTURES).also { intent ->
        startActivityForResult(intent, request_code)
    }
    

자바

    // BroadcastReceiver has already cached the MEDIA_MOUNTED
    // notification Intent in mediaMountedIntent
    StorageVolume volume = (StorageVolume)
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
    Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
    startActivityForResult(intent, request_code);
    

권장사항

사용자에게 액세스 권한을 반복적으로 요청할 필요가 없도록 하려면 가능한 경우 외부 디렉터리 액세스 URI를 유지하는 것이 좋습니다. 사용자가 액세스 권한을 부여하면 getContentResolver()를 호출하고, 반환된 ContentResolver와 함께 디렉터리 액세스 URI로 takePersistableUriPermission()을 호출합니다. 시스템은 URI를 유지하며, 이후 액세스 요청에서는 RESULT_OK를 반환하고 사용자에게 확인 UI를 표시하지 않습니다.

사용자가 외부 디렉터리 액세스를 거부하면 즉시 액세스를 다시 요청하지 마세요. 액세스를 반복적으로 요청하는 것은 사용자 환경에 도움이 되지 않습니다. 사용자가 요청을 거부하는데 앱이 다시 액세스를 요청하면, UI에 다시 묻지 않음 체크박스가 표시됩니다.

그림 1. 이동식 미디어에 대해 두 번째 액세스를 요청하는 애플리케이션.

사용자가 다시 묻지 않음을 선택하여 요청을 거부하면 앱에서 지정된 디렉터리에 대한 이후의 모든 요청이 자동으로 거부되고, 사용자에게는 어떤 요청 UI도 표시되지 않습니다.