앱이 자기 영역과 미디어 스토어만 보도록 제한하는 저장소 모델.
- 예전 — 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에 쓰려면 권한이 필요하다" → 내가 만든 항목은 권한 없이 쓰고 수정할 수 있다.