READ_EXTERNAL_STORAGE
- 애플리케이션이 외부 저장소의 파일을 읽을 수 있도록 한다.
- 외부 저장소
- SD카드
- 내부 저장소의 공유된 영역('내 파일' 앱{다운로드, 음악, 사진 같은 폴더를 포함한다.}
- 단순히 앨범에서 이미지를 가져올 때는 READ_EXTERNAL_STORAGE 권한을 얻지 않아도 된다.
- 앱이 Intent.ACTION_PICK 또는 Intent.ACTION_GET_CONTENT와 같은 암시적 인텐트를 사용하여 갤러리 앱을 호출하면 사용자가 직접 이미지를 선택한다.
- 이 방식은 Storage Access Framework(SAF)를 활용한 것으로, 시스템이 사용자와의 상호작용을 통해 파일에 대한 접근을 제어합니다.
- 따라서 앱은 파일 경로를 직접 알 수 없으며, 시스템이 제공하는 URI를 통해서만 파일에 접근할 수 있습니다.
- 이러한 방식은 앱이 파일 시스템에 직접 접근하지 않기 때문에 권한이 필요하지 않습니다.
- 외부 저장소
- MediaStore API 사용할 때 권한이 필요합니다.
- MediaStore API는 앱이 파일의 메타데이터를 직접 쿼리하거나 파일 내용을 읽기 위해 ContentResolver를 사용해야합니다.
- 이러한 접근은 앱이 파일 시스템에 직접 접근하는 방식이므로 사용자의 동의 없이 다른 앱의 데이터를 읽을 수 있는 보안 위험이 존재합니다.
- 따라서 안드로이드는 이러한 접근을 제한하기 위해 권한을 요구합니다.
- API 33 부터 이 권한은 아무 영향도 미치지 않습니다.
- APi 33부터 사용되는 권한입니다.
- READ_MEDIA_IMAGS
- READ_MEDIA_VIDEO
- READ_MEDIA_AUDIO
- APi 33부터 사용되는 권한입니다.
- 이 권한은 API 19부터 시행된다.
- API 19이하는 모든 앱이 외부 저장소에서 읽을 수 있는 권한을 가지고 있다.
WRITE_EXTERNAL_STORAGE
- 안드로이드 앱이 외부 저장소(ex: SD 카드)에 데이터를 쓸 수 있도록 하는 권한이다.
- 앱이 Build.VERSION_CODERS.R (Android11, API 30) 이상을 대상으로 하는 경우 이 권한은 적용되지 않는다.
- 앱이 API 레벨 19 이상을 실행하는 장치에 있는 경우, Context.getExternalFilesDir(String) 및 Context.getExternalCacheDir()이 반환하는 앱 전용 디렉토리에서 파일을 읽고 쓸 수 있는 이 권한을 선언할 필요가 없습니다.
- 앱이 파일 관리자로서 외부 저장 파일에 대한 광범위한 접근이 필요한 경우, 시스템이 앱을 허용 목록에 올려야 성공적으로 MANAGE_EXTERNAL_STORAGE 권한을 요청할 수 있습니다.
Scoped Storage
- Scoped Storage는 안드로이드 10 버전(API 29)에서 시작되었다.
- 내부 저장소의 개별 앱 공간은 이전과 동일하다.
- 외부 저장소의 공용공간이 사진 및 동영상, 음악, 다운로드 구조로 분리되고 이 안에서 앱 패키지 별로 구분되어 저장되도록 변경되었다.
- 공용 공간에 패키지 그림이 있는데 사진 및 동영상 안에 앱의 데이터가 들어가는 것이지 디렉토리가 생기는 것이 아니다.
- 즉, 외부 저장소의 공용공간에서 앱패키지 별로 개인 공간으로 나뉜다.
1. 개별 앱 공간 접근 방법
- 앱 자신의 개별 공간에 접근하는 경우입니다.
- 권한 요청이 필요하지 않습니다.
- getExternalFilesDir()을 통해 접근이 가능합니다.
- 앱 삭제시 함께 제거 됩니다.
- 안드로이드 9이하에서 사용하던 레거시 저장소와 비교
- 차이점 : 개별 앱 저장소에 접근할 때는 차이가 거의 없다.
- 공통점
- 레거시 저장소 : 다른 앱의 개별 공간에 접근할 때 EXTERNAL_STORAGE 권한이 필요했습니다.
- 즉, 자신의 개별 공간에 접근하는 경우는 권한 요청이 필요 없었습니다.
- scoped storage도 개별 공간에 접근할 때는 권한 요청이 필요 없습니다.
- 앱 삭제시, 함께 개별 앱 저장소도 제거됩니다.
- getExternalFilesDir() 혹은 getExternalCacheDir()로 접근
- 앱 삭제시에도 파일을 보존하는 방법
- MediaStore를 사용하여 파일 생성하고 메타데이터를 추가해야한다.
- Manifest의 android:allowBackup=true 설정 시 앱 심사 정보를 구글 클라우드에 저장해서 앱 재설치 했을때 클라우드에서 데이터를 복원할 수 있습니다.
2. 미디어에서 접근 방법
- 미디어에서 자신의 앱이 아닌 다른 앱 파일에 접근하거나 읽어올 때는 READ_EXTERNAL_STORAGE 권한(API 33 이상: READ_MEDIA)이 필요합니다.
- 접근 할 때 MediaStore API를 사용합니다.
- 앱이 삭제되어도 미디어에 저장된 정보는 삭제 되지 않습니다.
- MediaStore API는 사용자가 가지고 있는 파일들을 다른 앱에서도 사용할 수 있도록 설계된 API입니다.
- MediaStore API 외에 File API, Storage Access Framework도 사용 가능하지만 구글에서는 MediaStore API를 권고하고 있습니다.
- 레거시 스토리지와 비교
- 레거시(안드로이드 9)
- 레거시 스토리지에서 READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE를 사용해야 외부 공용 저장소에서 읽기, 쓰기가 가능했습니다.
- 외부 공용 저장소에서 내 앱의 개별 저장소라도 READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE 권한이 필요합니다.
- 이 권한이 있으면 누가 생성했는지, 어떤 경로에 저장되어있는지 상관 없이 모든 파일에 접근 및 조작이 가능했습니다.
- Scoped Storage
- 미디어 콜렉션 안에서 자신의 앱에 해당하는 곳은 접근 권한 요청 없이 가능합니다.
- 기존의 불필요한 사용자 권한 요청을 줄일 수 있다는 장점이 있습니다.
- 하지만 다른 앱이 생성한 미디어 파일에 접근하는 경우는 권한이 필요하고 레거시와 차이점이 있습니다.
- MediaStore
- READ_EXTERNAL_STORAGE 사용하세요.
- 안드로이드 11(API30) 부터 WRITE_EXTERNAL_STORAGE, WRITE_MEDIA_STORAGE는 사용할 수 없습니다.
- 레거시(안드로이드 9)
2-1. MediaStore API를 사용하여 외부 저장소로 내보내는 방법
녹음 파일을 외부 저장소(내장 메모리 -> Music)으로 내보내는 방법
1. MediaStore에 저장할 파일의 정보(ContentValues)를 생성
val values = ContentValues()
values.put(MediaStore.Audio.Media.DISPLAY_NAME, {파일이름})
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp4")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music")
values.put(MediaStore.Audio.Media.IS_PENDING, 1);
}
2. ContentValues를 MediaStore에 insert
val contentResolver = context.contentResolver
val collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
val insertUri = contentResolver.insert(collection, values)
위에서 생성한 ContentValues를 MediaStore에 insert하고 uri를 받습니다.
3. insert하고 얻은 uri에 파일 저장
val fileDescriptor = context.contentResolver.openFileDescriptor(insertUri!!, "w")
val outputStream = FileOutputStream(fileDescriptor?.fileDescriptor)
val inputStream = context.contentResolver.openInputStream(fileUri)
val bytes = ByteArray(8192)
while (true) {
val read = inputStream?.read(bytes)
if (read == -1) {
break
}
outputStream.write(bytes, 0, read!!)
}
outputStream.close()
inputStream?.close()
fileDescriptor?.close()
이제 insert하고 받은 uri로 FileDescripter를 open합니다.
그리고, input/output stream에 write 하면 됩니다.
전송하는 파일이 음원파일이고 사이즈가 클수도 있기 때문에 buffer를 이용하도록 합니다.
4. MediaStore에 IS_PENDING 값 update
values.clear()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
values.put(MediaStore.Audio.Media.IS_PENDING, 0)
}
context.contentResolver.update(insertUri, values, null, null)
IS_PENDING 값을 0으로 설정하여 팡ㄹ이 준비 되었음을 표시합니다.
update()함수로 메타 데이터를 갱신합니다.
똑같은 코드로 values에 IS_PENDING을 2번한 이유는
1번만 설정했을 때는 업데이트가 안되는데 2번 설정하면 업데이트가 된다고 합니다.
3. 다운로드 공간 접근 방법
- 권한이 필요없습니다.
- 저장소 엑세스 프레임워크(SAF: Storage Access Framework)와 시스템 파일 선택기를 통해 사용자가 명시적으로 파일을 선택한 경우 접근이 가능하며 앱 삭제시 제거되지 않습니다.
- 구글은 SAF 사용을 권고 하고 있습니다.
- 앱과 문서 provider는 서로 직접 상호작용 하지않고 systemUI를 통해 상호작용한다.
- systemUI로 문서를 제공하기 위해서 ACTION_OPEN_DOCUMENT, ACTION_CREATE_DOCUMENT 인텐트를 실행 시킨다.
출처
https://coding-mia.tistory.com/175
[Kotlin] Scoped Storage(범위지정 저장소) 정리 (Legacy Storage와 차이점 정리)
참고글 : https://beeyoo0o0ncha.tistory.com/30 안드로이드 내부저장소 외부저장소 이해한것 정리 안드로이드에선 앱에 저장소가 2가지가있다. 내부 저장소(Internal Storage) , 외부 저장소(External Storage) 내부
coding-mia.tistory.com
'android' 카테고리의 다른 글
안드로이드 FLAG 정리 (0) | 2025.05.20 |
---|---|
18. Android) R8 (0) | 2024.01.22 |
17.Android 기기에 device owner를 설정하는 방법 (1) | 2024.01.05 |
15. 안드로이드 동시성 코루틴 공부 (0) | 2023.12.14 |
14. 안드로이드 Handler 공부 (1) | 2023.12.08 |