안드로이드 시스템·Compose 용어 사전
보안MediaStore · SAF · Storage Access Framework

Scoped Storage

앱이 자기 영역과 미디어 스토어만 보게 제한하는 저장소 모델. Android 10부터 강제.

앱이 자기 영역과 미디어 스토어만 보도록 제한하는 저장소 모델.

  • 예전 — READ/WRITE_EXTERNAL_STORAGE 하나로 SD카드 전체를 읽고 썼다
    • 아무 앱이나 다른 앱의 파일을 훔쳐보고 지웠다
    • 앱을 지워도 쓰레기 파일이 남았다

Android 10 도입 · 11 부터 강제

세 구역

  • 앱 전용 영역 context.filesDir · cacheDir · getExternalFilesDir()

    • 권한 불필요. 앱을 지우면 함께 지워진다
  • 공유 미디어 — MediaStore (사진 · 동영상 · 오디오)

    • 내가 만든 것은 권한 없이. 남의 것은 READ_MEDIA_* 권한
  • 공유 문서 — Storage Access Framework (사용자가 직접 고른다)

    • 권한 불필요 — 사용자의 선택이 곧 허가다

MediaStore에 저장하기

val values = ContentValues().apply {
    put(MediaStore.Images.Media.DISPLAY_NAME, "photo.jpg")
    put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)!!
resolver.openOutputStream(uri)?.use { bitmap.compress(JPEG, 90, it) }

사용자가 고르게 한다

// 사진 — Photo Picker (권한 없음, Android 13+ 및 백포트)
val picker = registerForActivityResult(
    ActivityResultContracts.PickVisualMedia()
) { uri -> uri?.let(::load) }
picker.launch(PickVisualMediaRequest(ImageOnly))

// 임의 파일 — SAF
val opener = registerForActivityResult(
    ActivityResultContracts.OpenDocument()
) { uri -> uri?.let { resolver.takePersistableUriPermission(it, FLAG_GRANT_READ_URI_PERMISSION) } }
opener.launch(arrayOf("application/pdf"))

takePersistableUriPermission 을 안 부르면

  • 앱을 재시작한 뒤 그 URI 로 접근하면 SecurityException 이다
  • (권한이 프로세스 수명 동안만 유지된다)

파일 경로를 쓰지 않는다

/storage/emulated/0/DCIM/... 같은 절대 경로는 더 이상 유효하지 않다

  • URI 를 다루고, InputStream/OutputStream 으로 읽고 쓴다
  • File 객체를 기대하는 라이브러리에는 캐시로 복사해서 넘긴다

MANAGE_EXTERNAL_STORAGE

전체 접근 권한이 있긴 하다 — 파일 관리자 · 백업 앱 전용이다 Play 스토어 심사에서 정당성을 증명해야 하고 대부분 거절된다

면접 함정

  • requestLegacyExternalStorage로 계속 버틴다 → Android 11부터 무시된다.
  • "MediaStore에 쓰려면 권한이 필요하다" → 내가 만든 항목은 권한 없이 쓰고 수정할 수 있다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 권한·보안 — 런타임 권한·저장소·암호화·네트워크