본문 바로가기

android

[안드로이드] READ_EXTERNAL_STORAGE

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 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

출처 : https://coding-mia.tistory.com/175

  • 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는 사용할 수 없습니다.

 

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

https://heeeju4lov.tistory.com/50

'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