사진 및 동영상에 대한 일부 액세스 권한 부여

Android 14에서는 선택된 사진 액세스를 도입합니다. 이를 통해 사용자는 지정된 유형의 모든 미디어에 대한 액세스 권한을 부여하는 대신 라이브러리의 특정 이미지 및 동영상에 관한 액세스 권한을 앱에 부여할 수 있습니다.

이 변경사항은 앱이 Android 14 (API 수준 34) 이상을 타겟팅하는 경우에만 사용 설정됩니다. 아직 사진 선택 도구를 사용하지 않는다면 앱에서 구현하여 저장소 권한을 요청하지 않고도 사용자 개인 정보 보호도 강화하는 이미지와 동영상을 선택하는 일관된 환경을 제공하는 것이 좋습니다.

저장소 권한을 사용하여 자체 갤러리 선택 도구를 유지하고 구현을 완전히 제어해야 하는 경우 새 READ_MEDIA_VISUAL_USER_SELECTED 권한을 사용하도록 구현을 조정합니다. 앱이 새 권한을 사용하지 않으면 시스템은 앱을 호환성 모드로 실행합니다.

타겟 SDK READ_MEDIA_VISUAL_USER_SELECTED 선언됨 선택한 사진 액세스 사용 설정됨 UX 동작
SDK 33 아니요 아니요 해당 사항 없음
앱에서 제어
SDK 34 아니요 시스템에 의해 제어(호환성 동작)
앱에서 제어

나만의 갤러리 선택 도구를 만들려면 광범위한 개발 및 유지보수가 필요하며 앱에서 명시적인 사용자 동의를 얻기 위해 저장소 권한을 요청해야 합니다. 사용자는 이러한 요청을 거부할 수 있습니다. 또는 앱이 Android 14를 실행하는 기기에서 실행 중이고 앱이 Android 14 (API 수준 34) 이상을 타겟팅하는 경우 선택한 미디어에 관한 액세스를 제한할 수 있습니다. 다음 이미지는 새 옵션을 사용하여 권한을 요청하고 미디어를 선택하는 예를 보여줍니다.

.
그림 1. 새 대화상자를 통해 사용자는 전체 액세스 권한을 부여하거나 모든 액세스를 거부하는 일반적인 옵션 외에 앱에 제공할 특정 사진과 동영상을 선택할 수 있습니다.

이 섹션에서는 MediaStore를 사용하여 자체 갤러리 선택 도구를 만드는 데 권장되는 방법을 보여줍니다. 이미 앱의 갤러리 선택 도구를 유지하고 있고 완전한 제어를 유지해야 한다면 다음 예를 사용하여 구현을 조정할 수 있습니다. 선택한 사진 액세스를 처리하도록 구현을 업데이트하지 않으면 시스템이 앱을 호환성 모드로 실행합니다.

권한 요청

먼저 OS 버전에 따라 Android 매니페스트에서 올바른 저장소 권한을 요청합니다.

<!-- Devices running Android 12L (API level 32) or lower  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- To handle the reselection within the app on devices running Android 14
     or higher if your app targets Android 14 (API level 34) or higher.  -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

그런 다음 OS 버전에 따라 올바른 런타임 권한을 요청합니다.

// Register ActivityResult handler
val requestPermissions = registerForActivityResult(RequestMultiplePermissions()) { results ->
    // Handle permission requests results
    // See the permission example in the Android platform samples: https://github.com/android/platform-samples
}

// Permission request logic
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
    requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE))
}

일부 앱은 권한이 필요하지 않습니다.

Android 10 (API 수준 29)부터 앱은 공유 저장소에 파일을 추가하기 위해 저장소 권한이 필요하지 않습니다. 즉, 저장소 권한을 요청하지 않고도 앱이 이미지를 갤러리에 추가하거나, 동영상을 녹화하여 공유 저장소에 저장하거나, PDF 인보이스를 다운로드할 수 있습니다. 앱이 공유 저장소에만 파일을 추가하고 이미지나 동영상을 쿼리하지 않는 경우 저장소 권한 요청을 중지하고 AndroidManifest.xml에서 maxSdkVersion를 API 28로 설정해야 합니다.

<!-- No permission is needed to add files to shared storage on Android 10 (API level 29) or higher  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

미디어 다시 선택 처리

Android 14에서 선택한 사진 액세스 기능을 사용하면 앱에서 새로운 READ_MEDIA_VISUAL_USER_SELECTED 권한을 채택하여 미디어 다시 선택을 제어하고 사용자가 앱에 다른 이미지 및 동영상 모음에 관한 액세스 권한을 부여할 수 있도록 앱의 인터페이스를 업데이트해야 합니다. 다음 이미지는 권한을 요청하고 미디어를 다시 선택하는 예를 보여줍니다.

.
그림 2. 또한 새 대화상자를 통해 사용자는 앱에 제공할 사진과 동영상을 다시 선택할 수 있습니다.

선택 대화상자를 열면 요청된 권한에 따라 사진, 동영상 또는 둘 다 표시됩니다. 예를 들어 READ_MEDIA_IMAGES 권한 없이 READ_MEDIA_VIDEO 권한을 요청하는 경우 사용자가 파일을 선택할 수 있도록 UI에 동영상만 표시됩니다.

// Allow the user to select only videos
requestPermissions.launch(arrayOf(READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))

기기의 사진 라이브러리에 대한 액세스 권한이 앱에 전체, 부분 또는 거부되었는지 확인하고 그에 따라 인터페이스를 업데이트할 수 있습니다. 앱이 시작할 때가 아니라 저장소 액세스가 필요할 때 이러한 권한을 요청하세요. 권한 부여는 onStartonResume 앱 수명 주기 콜백 간에 변경될 수 있습니다. 사용자가 앱을 종료하지 않고도 설정에서 액세스 권한을 변경할 수 있기 때문입니다.

if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
    (
        ContextCompat.checkSelfPermission(context, READ_MEDIA_IMAGES) == PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(context, READ_MEDIA_VIDEO) == PERMISSION_GRANTED
    )
) {
    // Full access on Android 13 (API level 33) or higher
} else if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
    ContextCompat.checkSelfPermission(context, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED
) {
    // Partial access on Android 14 (API level 34) or higher
}  else if (ContextCompat.checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
    // Full access up to Android 12 (API level 32)
} else {
    // Access denied
}

기기 라이브러리 쿼리

올바른 저장소 권한에 액세스할 수 있는지 확인한 후에는 MediaStore와 상호작용하여 기기 라이브러리를 쿼리할 수 있습니다. 부여된 액세스 권한이 일부인지 전체인지에 관계없이 동일한 접근 방식이 작동합니다.

data class Media(
    val uri: Uri,
    val name: String,
    val size: Long,
    val mimeType: String,
)

// Run the querying logic in a coroutine outside of the main thread to keep the app responsive.
// Keep in mind that this code snippet is querying only images of the shared storage.
suspend fun getImages(contentResolver: ContentResolver): List<Media> = withContext(Dispatchers.IO) {
    val projection = arrayOf(
        Images.Media._ID,
        Images.Media.DISPLAY_NAME,
        Images.Media.SIZE,
        Images.Media.MIME_TYPE,
    )

    val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Query all the device storage volumes instead of the primary only
        Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
    } else {
        Images.Media.EXTERNAL_CONTENT_URI
    }

    val images = mutableListOf<Media>()

    contentResolver.query(
        collectionUri,
        projection,
        null,
        null,
        "${Images.Media.DATE_ADDED} DESC"
    )?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(Images.Media._ID)
        val displayNameColumn = cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME)
        val sizeColumn = cursor.getColumnIndexOrThrow(Images.Media.SIZE)
        val mimeTypeColumn = cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE)

        while (cursor.moveToNext()) {
            val uri = ContentUris.withAppendedId(collectionUri, cursor.getLong(idColumn))
            val name = cursor.getString(displayNameColumn)
            val size = cursor.getLong(sizeColumn)
            val mimeType = cursor.getString(mimeTypeColumn)

            val image = Media(uri, name, size, mimeType)
            images.add(image)
        }
    }

    return@withContext images
}

이 코드 스니펫은 MediaStore와 상호작용하는 방법을 보여주기 위해 단순화되었습니다. 프로덕션 지원 앱에서 Paging 라이브러리 등을 통해 페이지로 나누기를 사용하면 우수한 성능을 보장하는 데 도움이 됩니다.

기기가 업그레이드될 때 사진 및 동영상 액세스 권한이 유지됨

앱이 이전 Android 버전에서 Android 14로 업그레이드되는 기기에 있는 경우 시스템은 사용자의 사진과 동영상에 대한 전체 액세스 권한을 유지하며 앱에 일부 권한을 자동으로 부여합니다. 정확한 동작은 기기가 Android 14로 업그레이드되기 전에 앱에 부여되는 권한 집합에 따라 다릅니다.

Android 13의 권한

다음 상황을 고려해보세요.

  1. 앱이 Android 13을 실행하는 기기에 설치되어 있습니다.
  2. 사용자가 앱에 READ_MEDIA_IMAGES 권한과 READ_MEDIA_VIDEO 권한을 부여했습니다.
  3. 그런 다음 기기가 앱이 여전히 설치되어 있는 동안 Android 14로 업그레이드됩니다.
  4. 앱이 Android 14 (API 수준 34) 이상을 타겟팅하기 시작합니다.

이 경우에도 앱은 사용자의 사진 및 동영상에 대한 전체 액세스 권한을 보유합니다. 시스템도 앱에 부여된 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 권한을 자동으로 유지합니다.

Android 12 이하 권한

다음 상황을 고려해보세요.

  1. 앱이 Android 13을 실행하는 기기에 설치되어 있습니다.
  2. 사용자가 앱에 READ_EXTERNAL_STORAGE 권한 또는 WRITE_EXTERNAL_STORAGE 권한을 부여했습니다.
  3. 그런 다음 기기가 앱이 여전히 설치되어 있는 동안 Android 14로 업그레이드됩니다.
  4. 앱이 Android 14 (API 수준 34) 이상을 타겟팅하기 시작합니다.

이 경우에도 앱은 사용자의 사진 및 동영상에 대한 전체 액세스 권한을 보유합니다. 시스템도 READ_MEDIA_IMAGES 권한과 READ_MEDIA_VIDEO 권한을 앱에 자동으로 부여합니다.

권장사항

이 섹션에는 READ_MEDIA_VISUAL_USER_SELECTED 권한 사용을 위한 여러 권장사항이 포함되어 있습니다. 자세한 내용은 권한 권장사항을 참고하세요.

권한 상태를 영구적으로 저장하지 않음

SharedPreferences 또는 DataStore를 포함하여 권한 상태를 영구적인 방식으로 저장하지 마세요. 저장된 상태가 실제 상태와 동기화되지 않을 수 있습니다. 권한 상태는 권한 재설정, 앱 최대 절전 모드, 앱 설정에서 사용자가 시작한 변경 후에 또는 앱이 백그라운드로 전환될 때 변경될 수 있습니다. 대신 ContextCompat.checkSelfPermission()를 사용하여 저장소 권한을 확인하세요.

사진 및 동영상에 대한 전체 액세스 권한이 있다고 가정하지 않음

Android 14에 도입된 변경사항에 따라 앱은 기기의 사진 라이브러리에 대한 일부 액세스 권한만 보유할 수도 있습니다. 앱이 ContentResolver를 사용하여 쿼리될 때 MediaStore 데이터를 캐시하는 경우 캐시가 최신 상태가 아닐 수 있습니다.

  • 저장된 캐시를 사용하는 대신 항상 ContentResolver를 사용하여 MediaStore를 쿼리합니다.
  • 앱이 포그라운드에 있는 동안 결과를 메모리에 유지합니다.
  • 사용자가 권한 설정을 통해 전체 액세스에서 부분 액세스로 전환할 수 있으므로 앱이 onResume 앱 수명 주기를 거치면 결과를 새로고침합니다.

URI 액세스를 임시로 취급

사용자가 시스템 권한 대화상자에서 사진 및 동영상 선택을 선택하면 선택한 사진 및 동영상에 대한 앱의 액세스 권한이 결국 만료됩니다. 앱은 권한과 관계없이 Uri에 액세스할 수 없는 경우를 항상 처리해야 합니다.

권한으로 선택 가능한 미디어 유형 필터링

선택 대화상자는 요청된 권한 유형에 민감합니다.

  • READ_MEDIA_IMAGES만 요청하면 선택 가능한 이미지만 표시됩니다.
  • READ_MEDIA_VIDEO만 요청하면 선택할 수 있는 동영상만 표시됩니다.
  • READ_MEDIA_IMAGESREAD_MEDIA_VIDEO를 모두 요청하면 전체 사진 라이브러리가 선택 가능한 상태로 표시됩니다.

앱의 사용 사례에 따라 사용자 환경이 저하되지 않도록 올바른 권한을 요청해야 합니다. 지형지물에서 동영상만 선택되어야 하는 경우 READ_MEDIA_VIDEO만 요청해야 합니다.

단일 작업에서 권한 요청

사용자에게 여러 시스템 런타임 대화상자가 표시되지 않도록 하려면 단일 작업에서 READ_MEDIA_VISUAL_USER_SELECTED, ACCESS_MEDIA_LOCATION, '미디어 읽기' 권한(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO 또는 둘 다)을 요청하세요.

사용자가 선택한 항목을 관리하도록 허용

사용자가 부분 액세스 모드를 선택하면 앱은 기기의 사진 라이브러리가 비어 있다고 가정해서는 안 되며 사용자가 더 많은 파일을 부여할 수 있도록 허용해야 합니다.

사용자는 일부 시각적 미디어 파일에 대한 액세스 권한을 부여하지 않고 권한 설정을 통해 전체 액세스에서 부분 액세스로 전환할 수 있습니다.

호환성 모드

저장소 권한을 사용하여 자체 갤러리 선택 도구를 유지하지만 새 READ_MEDIA_VISUAL_USER_SELECTED 권한을 사용하도록 앱을 조정하지 않은 경우 시스템은 사용자가 미디어를 선택하거나 다시 선택해야 할 때마다 호환성 모드로 앱을 실행합니다.

초기 미디어 선택 중 동작

초기 선택 중에 사용자가 '사진 및 동영상 선택' (그림 1 참고)을 선택하면 앱 세션 중에 READ_MEDIA_IMAGESREAD_MEDIA_VIDEO 권한이 부여되어 사용자가 선택한 사진 및 동영상에 임시 권한이 부여되고 임시 액세스가 제공됩니다. 앱이 백그라운드로 이동하거나 사용자가 앱을 적극적으로 종료하면 시스템은 결국 이러한 권한을 거부합니다. 이 동작은 다른 일회성 권한과 비슷합니다.

미디어를 다시 선택하는 동안 동작

나중에 앱에서 추가 사진과 동영상에 액세스해야 한다면 수동으로 READ_MEDIA_IMAGES 권한 또는 READ_MEDIA_VIDEO 권한을 다시 요청해야 합니다. 시스템은 초기 권한 요청과 동일한 흐름을 따라 사용자에게 사진과 동영상을 선택하라는 메시지를 표시합니다 (그림 2 참고).

앱이 권한 권장사항을 준수하면 이 변경사항으로 인해 앱이 중단되지 않습니다. 특히 권한이 변경된 후 앱에서 URI 액세스가 유지되거나, 시스템 권한 상태를 저장하거나, 표시된 이미지 세트를 새로고침하지 않는다고 가정하지 않는 경우에 이러한 상황이 발생합니다. 그러나 이 동작은 앱의 사용 사례에 따라 이상적이지 않을 수 있습니다. 사용자에게 최상의 환경을 제공하려면 사진 선택 도구를 구현하거나 READ_MEDIA_VISUAL_USER_SELECTED 권한을 사용하여 이 동작을 직접 처리하도록 앱의 갤러리 선택 도구를 조정하는 것이 좋습니다.