[Android] MediaStore에서 사진/동영상/오디오 파일 읽기
Android에서 MediaStore는 디바이스에 저장된 사진/동영상/오디오 등의 미디어 파일을 관리합니다. MediaStore는 Query API를 제공하며, 이 API를 사용하여 파일들을 탐색할 수 있습니다.
예제를 통해 미디어 파일 정보는 가져오는 방법에 대해서 알아보겠습니다.
1. 미디어 파일 접근 권한
미디어 파일은 OS 버전 별로 요구하는 권한이 조금씩 다릅니다. Android 13에서 READ_EXTERNAL_STORAGE
는 더 이상 사용되지 않고, READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO
3개의 권한으로 분리되었습니다. 기존에는 READ_EXTERNAL_STORAGE
1개의 권한으로 모든 종류의 미디어 파일을 읽을 수 있었지만, Android 13부터 미디어 파일 종류에 따라서 READ 권한이 3개로 분리되었습니다.
- Android 12L 이하 버전 :
READ_EXTERNAL_STORAGE
- Android 13 이상 버전 :
READ_MEDIA_IMAGES
,READ_MEDIA_VIDEO
,READ_MEDIA_AUDIO
앱의 AndroidManifest에는 아래와 같이 OS 별로 권한을 추가할 수 있습니다.
<!-- 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" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
2. 모든 이미지 파일 가져오기
MediaStore를 통해 이미지 파일 정보를 가져오려면 READ_MEDIA_IMAGES
권한을 갖고 있어야 합니다. Runtime 권한이기 때문에, 권한 팝업을 통해 사용자에게 받아야 합니다.
Android 14에서 사용자는 앱에 모든 Read 권한을 부여하지 않고 파일 단위로 접근 권한을 부여할 수 있게 변경되었습니다. 자세한 내용은 Android 14 미디어 파일 일부 접근 권한을 참고해주세요.
권한을 갖고 있다면 아래와 같이 MediaStore의 Query API를 사용하여 이미지 파일을 가져올 수 있습니다.
contentResolver.query()
: 인자로 전달된 projection에 해당하는 이미지 파일 정보를 cursor로 리턴cursor.getColumnIndexOrThrow()
: cursor에서 특정 항목에 대한 데이터를 리턴- URI를 통해 파일에 접근할 수 있음
data class Media(
val uri: Uri,
val name: String,
val size: Long,
val mimeType: String,
)
private fun getImages(contentResolver: ContentResolver): List<Media> {
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.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
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val images = mutableListOf<Media>()
contentResolver.query(
collectionUri,
projection,
null,
null,
"${MediaStore.Images.Media.DATE_ADDED} DESC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val displayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.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 images
}
위 함수를 아래와 같이 호출하여 파일 리스트를 출력할 수 있습니다.
val images = getImages(contentResolver)
for (img : Media in images) {
Log.d(TAG, "Image name: ${img.name}, uri: ${img.uri},"
+ " size: ${img.size}, type ${img.mimeType}")
}
Output:
10-14 12:39:13.365 7906 7906 D MainActivity: Image name: IMG_20231014_122514.jpg, uri: content://media/external/images/media/1000000037, size: 31298, type image/jpeg
10-14 12:39:13.365 7906 7906 D MainActivity: Image name: IMG_20231014_122513.jpg, uri: content://media/external/images/media/1000000036, size: 30806, type image/jpeg
10-14 12:39:13.366 7906 7906 D MainActivity: Image name: IMG_20231014_122512.jpg, uri: content://media/external/images/media/1000000034, size: 30530, type image/jpeg
10-14 12:39:13.366 7906 7906 D MainActivity: Image name: IMG_20231014_122512_1.jpg, uri: content://media/external/images/media/1000000035, size: 31368, type image/jpeg
10-14 12:39:13.366 7906 7906 D MainActivity: Image name: IMG_20231014_122511.jpg, uri: content://media/external/images/media/1000000033, size: 30515, type image/jpeg
3. 모든 비디오 파일 가져오기
MediaStore를 통해 비디오 파일 정보를 가져오려면 READ_MEDIA_VIDEO
권한을 갖고 있어야 합니다.
권한을 갖고 있다면 아래와 같이 MediaStore의 Query API를 사용하여 비디오 파일을 가져올 수 있습니다.
contentResolver.query()
: 인자로 전달된 projection에 해당하는 이미지 파일 정보를 cursor로 리턴cursor.getColumnIndexOrThrow()
: cursor에서 특정 항목에 대한 데이터를 리턴- URI를 통해 파일에 접근할 수 있음
data class Media(
val uri: Uri,
val name: String,
val size: Long,
val mimeType: String,
)
위 함수를 아래와 같이 호출하여 파일 리스트를 출력할 수 있습니다.
val videos = getVideos(contentResolver)
for (video : Media in videos) {
Log.d(TAG, "Video name: ${video.name}, uri: ${video.uri},"
+ " size: ${video.size}, type ${video.mimeType}")
}
private fun getVideos(contentResolver: ContentResolver): List<Media> {
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.SIZE,
MediaStore.Video.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
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val videos = mutableListOf<Media>()
contentResolver.query(
collectionUri,
projection,
null,
null,
"${MediaStore.Video.Media.DATE_ADDED} DESC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val displayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.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 video = Media(uri, name, size, mimeType)
videos.add(video)
}
}
return videos
}
Output:
10-14 12:45:33.935 7906 7906 D MainActivity: Video name: VID_20231014_123042.mp4, uri: content://media/external/video/media/1000000040, size: 511422, type video/mp4
10-14 12:45:33.935 7906 7906 D MainActivity: Video name: VID_20231014_123037.mp4, uri: content://media/external/video/media/1000000039, size: 641758, type video/mp4
10-14 12:45:33.935 7906 7906 D MainActivity: Video name: VID_20231014_123034.mp4, uri: content://media/external/video/media/1000000038, size: 445506, type video/mp4
4. 모든 오디오 파일 가져오기
MediaStore를 통해 오디오 파일 정보를 가져오려면 READ_MEDIA_AUDIO
권한을 갖고 있어야 합니다.
권한을 갖고 있다면 아래와 같이 MediaStore의 Query API를 사용하여 오디오 파일을 가져올 수 있습니다.
contentResolver.query()
: 인자로 전달된 projection에 해당하는 이미지 파일 정보를 cursor로 리턴cursor.getColumnIndexOrThrow()
: cursor에서 특정 항목에 대한 데이터를 리턴- URI를 통해 파일에 접근할 수 있음
data class Media(
val uri: Uri,
val name: String,
val size: Long,
val mimeType: String,
)
private fun getAudios(contentResolver: ContentResolver): List<Media> {
val projection = arrayOf(
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.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
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val audios = mutableListOf<Media>()
contentResolver.query(
collectionUri,
projection,
null,
null,
"${MediaStore.Audio.Media.DATE_ADDED} DESC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
val displayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.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 audio = Media(uri, name, size, mimeType)
audios.add(audio)
}
}
return audios
}
위 함수를 아래와 같이 호출하여 파일 리스트를 출력할 수 있습니다.
val audios = getAudios(contentResolver)
for (audio : Media in audios) {
Log.d(TAG, "Audio name: ${audio.name}, uri: ${audio.uri},"
+ " size: ${audio.size}, type ${audio.mimeType}")
}