공유 저장소의 미디어 파일에 액세스 | Android 개발자 | Android Developers
공유 저장소의 미디어 파일에 액세스 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 많은 앱에서 더욱 풍부한 사용자 환경을 제공하기 위해 사용자가 외부
developer.android.com
더 좋은 사용자 경험을 제공하기 위해 많은 앱들이 사용자로 하여금 미디어 파일을 생성하고, 이 파일들에 접근하게 할 수 있는 기능을 제공합니다. 이 파일들은 external storage 볼륨에서 저장되고 사용되어집니다. 안드로이드 프레임워크는 Media Store 라고 불리는 이러한 미디어 파일들에 최적화된 index 를 제공함으로써 손쉽게 미디어 파일들을 접근하고, 업데이트 할 수 있게 합니다. 그리고 앱이 지워지고 난 이 후에도 이러한 파일들은 사용자 디바이스에 남아있게 됩니다.
만약 당신의 앱이 해당 앱 안에서만 관리되는 미디어 파일들을 사용할 목적이라면 external storage 안의 app specific directories(https://developer.android.com/training/data-storage/app-specific#media) 에 미디어 파일들을 관리하는 것이 좋습니다.
Photo picker
Media store 를 이용하는 대안적인 방법으로 Androd Photo Picker tool 이 있습니다. Androd Photo Picker 는 사용자로 하여금 사진 파일들 선택하게 함에 있어서 더 안전하고 build-in 된 방식을 제공합니다. Androd Photo Picker 를 사용하면 당신의 앱이 전체 미디어 라이브러리에 접근하기 위한 권한 획득 과정이 필요하지 않습니다. Androd Photo Picker 는 지원되는 디바이스에서만 활용가능합니다. 자세한 내용은 photo picker 가이드(https://developer.android.com/training/data-storage/shared/photopicker)를 참고하세요.
Media Store
추상화된 Media Store 계층과 상호작용하기 위해서는 ContentResolver(https://developer.android.com/reference/android/content/ContentResolver) 객체를 사용해야합니다. ContentResolver 객체는 app 의 context 를 통해서 접근할 수 있습니다.
val projection = arrayOf(media-database-columns-to-retrieve)
val selection = sql-where-clause-with-placeholder-variables
val selectionArgs = values-of-placeholder-variables
val sortOrder = sql-order-by-clause
applicationContext.contentResolver.query(
MediaStore.media-type.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder
)?.use { cursor ->
while (cursor.moveToNext()) {
// Use an ID column from the projection to get
// a URI representing the media item itself.
}
}
시스템은 자동으로 external storage 볼륨을 스캔하고, 미디어 파일들을 다음에서 정의된 collection 들에 추가합니다.
- Images : DCIM/ 폴더과 Pictures/ 폴더에 저장된 사진과 스크린샷을 의미합니다. 시스템은 이러한 파일들을 MediaStore.Images 테이블에 추가합니다.
- Videos : DCIM/ 폴더과 Pictures/ 폴더, /Movies 폴더에 저장된 파이들을 의미합니다. 시스템은 이러한 파일들을 자동으로 MediaStore.Video 테이블에 추가합니다.
- Audio files : Alarms/, Audiobooks/, Music/, Notifications/, /Podcasts/, Rintones/ 디렉토리에 저장된 파일들을 의미합니다. 추가적으로 시스템은 Music/ 나 Video/ 디렉토리에 있는 오디오 플레이리스트들을 인식하고, Recordings/ 디렉토리에 있는 음성 녹음도 인식합니다. (Recordings/ 디렉토리는 Android 11(api level 30) 에서는 사용할 수 없습니다) 시스템은 이러한 파일들을 MediaStore.Audio 테이블에 추가합니다.
- Downloaded files : Download/ 디렉토리에 있는 파일들을 의미합니다. Android 10 (API Level 29) 이상에서부터 이러한 파일들은 MediaStore.Downloads 테이블에 추가합니다. 이 테이블은 Android 9 이하의 단말에서는 사용 불가능합니다.
Media Store 에서는 또한 MediaStore.Files 이라 불리는 컬렉션도 포함합니다. 이 테이블에 저장된 파일들은 앱이 Scoped Storage 를 사용하는지 여부에 따라서 달라집니다. (Scoped Storage 는 Android 10 에서부터 적용됨)
- 만약 앱이 Scoped Storage 를 사용한다면, 이 컬랙션에서는 앱에서 추가한 사진, 영상, 오디오 파일들만 포함되게 됩니다. 대부분의 경우 다른 앱에서 생성한 미디어 파일들에 접근하기 위해서 MediaStore.Files 을 사용할 필요가 없습니다. 그러나 만약 그럴 필요가 있는 사용처가 있다면 READ_EXTERNAL_STORAGE 권한을 선언하고, 이 권한을 사용자로부터 부여받아야 합니다. 그러나 다른 앱이 생성한 미디어 파일들에 접근할때는 MediaStore API 를 사용하는 것이 권장되는 방법입니다.
- Scoped Storage 를 사용하지 않거나, 사용 불가능한 경우, 이 컬랙션은 모든 종류의 미디어 파일들을 포함하게 됩니다.
Request necessary permissions
미디어 파일들에 대한 동작을 수행하기 전에, 앱이 이러한 파일들을 접근하기 위한 적절한 권한을 선언하고 부여받았는지 확인해야합니다. 명심해야할 것은 사용하지 않는 범위 이상의 권한을 선언하지 않도록 하는 것입니다.
Storage permissions
만약 다른앱이 만든 미디어 파일에 접근하는 경우가 없다면 어떠한 권한 설정도 필요하지 않습니다.
Android 10 이상의 디바이스에서는 MediaStore.Downloads 컬랙션을 포함해서 앱이 생성한 미디어 파일들에 접근하는 경우 저장소와관련된 권한을 설정할 필요가 없습니다. 만약 카메라 앱을 개발한다면 이 앱은 Media Store 에 자신이 생성한 사진 파일들만 관리하기 때문에 저장소 권한이 필요 없습니다.
다른 앱의 미디어 파일을 접근하는 경우
다른 앱이 생성한 미디어 파일에 접근하기 위해서는 다음과 같은 저장소 관련 권한들을 선언해야합니다. 접근하려는 파일들은 다음 미디어 파일 컬렉션에 포함되어야 합니다.
- MediaStore.Images
- MediaStore.Video
- MediaStore.Audio
어떤 파일이 MediaStore.Images, MediaStore.Video, MediaStore.Audio 쿼리를 통해 보여진다면, 이 파일은 MediaStore.Files 쿼리를 통해서도 보여집니다.
다음 코드는 적절한 저장소 권한을 설정하는 방법에 대해서 다룹니다.
<!-- Required only if your app needs to access images or photos
that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Required only if your app needs to access videos
that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Required only if your app needs to access audio files
that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- 위쪽은 Target SDK 33 이상인 경우에만 적용됨 -->
<!-- 아래쪽은 Target SDK 32 이하에서 다른 앱의 미디어 파일을 접근할때 적용 -->
<!-- If your app doesn't need to access media files that other apps created,
set the "maxSdkVersion" attribute to "28" instead. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
만약 READ_MEDIA_IMAGES, READ_MEDIA_VIDEO 권한을 동시에 선언하고, 이들 권한 부여 요청을 동시에 진행한다면 안드로이드 시스템은 하나의 권한 요청 다이얼로그에서 두가지 권한을 동시에 요청합니다.
레거시 디바이스에서 동작하는 앱을 위해 필요한 추가 권한들
Android 9 이하에서 동작하거나, 임시적으로 Scoped Storage 기능을 껐다면 임의의 미디어 파일에 접근하기 위해서는 READ_EXTERNAL_STORAGE 권한이 필수적으로 필요합니다. 만약 미디어 파일에 수정을 하고 싶다면 WRITE_EXTERNAL_STORAGE 까지 필요하게 됩니다.
다른 앱에서 다운로드 한 파일에 접근하기 위해서는 저장소 접근 프레임워크를 사용해야합니다.
만약 앱에서 다른 앱이 생성한 MediaStore.Downloads 컬렉션에 접근하려면, Storage Access Framework 를 사용해야합니다. https://developer.android.com/training/data-storage/shared/documents-files
공유 저장소의 문서 및 기타 파일 액세스 | Android 개발자 | Android Developers
공유 저장소의 문서 및 기타 파일 액세스 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 4.4(API 수준 19) 이상을 실행하는 기기에서 앱은 저장소 액세스
developer.android.com
Media location permission
만약 앱이 Android 10 (API level 29) 이상을 타겟팅하고 있고, unredacted EXIF metadata 를 사진으로부터 얻고 싶다면 ACCESS_MEDIA_LOCATION 권한을 선언하고, 런타임에 사용자에게 명시적으로 부여받아야 합니다.
Check for updates to the media store
미디어 파일들에 좀 더 안정적으로 접근하기 위해서 - 특별히 앱이 미디어 스토어로 부터 uri 나 data 를 캐싱해둔다고 한다면 - 마지막으로 미디어 스토어와 동기화를 한 이후에 미디어 스토어 버전이 변경되었는지 체크하는 것이 좋습니다. 이를 위해 getVersion() 메소드를 호출할 수 있습니다. 반환되는 값은 unique 한 문자열이며, 미디어 스토어에서 대량의 변경이 일어난 경우에 이 반환값도 변경이 일어납니다. 만약 마지막 동기화 이후에 이 값이 변경되었다면, 앱의 미디어 캐시를 다시 동기화애햐합니다.
이 체크는 앱 startup time 에 완료하는게 좋습니다. 매번 미디어 파일을 쿼리할때마다 이 체크를 할 필요는 없습니다. 단순히 미디어 스토어에 파일 하나를 저장한다고 해서 이 버전이 변경되지는 않습니다.
Query a media collection
5분 이상의 미디어 파일과 같은 특정 조건을 만족시키는 미디어 파일을 찾기 위해서는 아래와 같이 SQL 과 같은 형태의 selection statement 를 작성해서 사용할 수 있습니다.
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.
// Container for information about each video.
data class Video(val uri: Uri,
val name: String,
val duration: Int,
val size: Int
)
val videoList = mutableListOf<Video>()
val collection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(
MediaStore.VOLUME_EXTERNAL
)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE
)
// Show only videos that are at least 5 minutes in duration.
val selection = "${MediaStore.Video.Media.DURATION} >= ?"
val selectionArgs = arrayOf(
TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString()
)
// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"
val query = ContentResolver.query(
collection,
projection,
selection,
selectionArgs,
sortOrder
)
query?.use { cursor ->
// Cache column indices.
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
val durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
while (cursor.moveToNext()) {
// Get values of columns for a given video.
val id = cursor.getLong(idColumn)
val name = cursor.getString(nameColumn)
val duration = cursor.getInt(durationColumn)
val size = cursor.getInt(sizeColumn)
val contentUri: Uri = ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
id
)
// Stores column values and the contentUri in a local object
// that represents the media file.
videoList += Video(contentUri, name, duration, size)
}
}
위와 같은 쿼리를 수행할때 다음과 같은 사항들을 명심해야합니다.
- query() 메소드는 Worker Thread 에서 수행되어야 합니다.
- column index 들은 캐시해두어서 쿼리 결과의 row 를 처리할때 매번 getColumnIndexOrThrow() 메소드를 호출하지 않아도 되도록 합니다.
- 위 코드처럼 content URL 에 ID 를 끝에 추가합니다.
- Android 10 이상의 디바이스에서는 MediaStore API 에 정의되어있는 column 이름들을 사용해아합니다. 만약 앱에서 의존하고 있는 라이브러리가 이 API 에 정의되어 있지 않은 column name 을 사용하도록 요구하는 경우 (예를 들어 MimeType 과 같은 이름) CursorWrapper 를 사용해서 앱 프로세스 안에서 적절한 이름으로 동적으로 변환해줘야 합니다.
'천복만복 프로그래밍 > 천복만복 안드로이드' 카테고리의 다른 글
Edge to Edge (2) | 2024.10.02 |
---|---|
Non SDK Interface restriction (0) | 2023.05.26 |
현재 다크모드 상태인지 체크하는 법 (0) | 2022.09.24 |
Access app-specific files 가이드 문서 정리 (1) | 2022.09.17 |
Android Custom View, Custom View Group (0) | 2022.09.06 |