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

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에 API 28의 maxSdkVersion를 설정해야 합니다.

<!-- 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 15 이상 및 Google Play 시스템 업데이트 지원이 포함된 Android 14의 앱은 QUERY_ARG_LATEST_SELECTION_ONLY를 사용 설정하여 부분 액세스에서 사용자가 선택한 마지막 이미지와 동영상을 쿼리할 수 있습니다.

if (getExtensionVersion(Build.VERSION_CODES.U) >= 12) {
    val queryArgs = bundleOf(
        QUERY_ARG_SQL_SORT_ORDER to "${Images.Media.DATE_ADDED} DESC"
        QUERY_ARG_LATEST_SELECTION_ONLY to true
    )

    contentResolver.query(collectionUri, projection, queryArgs, null)
}

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

앱이 이전 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 권한을 사용하여 이 동작을 직접 처리하도록 사진 선택 도구를 구현하거나 앱의 갤러리 선택 도구를 조정하는 것이 좋습니다.