Storage Scope란?
- 개별 앱 공간이 샌드박스 형식으로 격리되어 다른 앱의 파일에 직접 접근할 수 없게 하는 방식을 말합니다.
targetSdkVersiond을 29로 올리면 아래와 같이 Deprecated 됩니다.
@Deprecated
public static File getExternalStorageDirectory()
공용 저장소에 접근할 때는 MediaStore 또는 Intent(ACTION_OPEN_DOCUMENT)를 사용해야 된다고 합니다.
공용 공간은 MediaStore을 통해서만 읽고 쓸 수 있습니다. 혹은 SAF를 사용해 파일을 가져올 수 있습니다.
저장소
Android Q이전
외부 저장소
: 외부 저장소 권한이 있으면 누구나 접근이 가능
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
Android Q이후
샌드박스 저장공간
: 개별 앱만 접근 가능
- 읽고 쓰기 위한 별도의 권한 필요 없음
- Context.getExternalFilesDir(...)
- 앱 삭제 시 함께 삭제됨
- 앨범 아트, 썸네일 등 다른 앱과 공유할 필요가 없는 미디어 파일은 샌드박스 공간을 활용
공용 저장공간
- MediaStore.Audio
- MediaStore.Video
- MediaStore.Images
- 별도 권한 없이 파일 생성 가능
- 외부 저장소 권한으로 다른 앱이 생성한 파일 접근 가능
다운로드 저장공간
- MediaStore.Downloads
- 별도 권한 없이 파일 생성 가능
- 시스템 파일 선택기를 통해서만 다른 앱이 생성한 파일 접근 가능
다른 앱이 생성한 미디어 파일에 대한 접근 권한법
-MediaStore 사용할 경우(10 버전에서 새로 생김)
READ_MEDIA_IMAGES
READ_MEDIA_VIDEO
READ_MEDIA_AUDIO
-MediaStore 혹은 MediaStore 외의 방법을 사용할 경우
READ_EXTERNAL_STORAGE
-더 이상 추가 엑세스 제공 X- (권한요청시 false로 줬음)
WRITE_EXTERNAL_STORAGE
WRITE_MEDIA_STORAGE
앱이 생성한 미디어 파일에 접근하는 경우는 권한이 필요한데
READ_MEDIA_IMAGES, READ_MEDIA_VIDEO ,READ_MEDIA_AUDIO를 사용하시면 됩니다.
이것들은 안드로이드 10에서 새로 추가된 권한이고 각각 사진, 동영상, 음성을 읽는데 필요합니다.
기존의 READ_EXTERNAL_STORAGE 로도 가능한 일이었지만 구글은 앱에서 미디어 파일로 작업할 때는 광범위한 권한요청이아닌 필요한 권한만 요청하라고 권고하고있습니다.
하지만 READ_EXTERNAL_SOTRAGE 를 현재 사용하고 계셔도 안드로이드 11버전에서 잘 동작하기 때문에 계속 사용하셔도 무관하다고 합니다.
안드로이드 11에서 MediaStore API 관련 method 들이 추가된게 있습니다.
MediaStore API method 추가(언제 어떻게 써야할지 모르겠음)
: 스스로 만든 미디어에 대해서는 필요없고, 다른 앱에서 만든 미디어에 요청해야 한다고 하는데..
- createWriteRequest() : 앱에 지정된 미디어 파일 그룹에 관한 쓰기 액세스 권한을 부여하도록 사용자에게 요청
- createDeleteRequest() : 지정된 미디어 파일을 휴지통에 미리 넣지 않고 영구적으로 삭제하도록 사용자에게 요청
- createTrashRequest() : 지정된 미디어 파일을 기기의 휴지통에 넣도록 사용자에게 요청. 휴지통에 있는 항목은 정의한 기간이 지나면 영구적으로 삭제
- createFavoriteRequest() : 지정된 미디어 파일을 기기의 '즐겨찾기' 미디어로 표시 하도록 사용자에게 요청.
최상위 레벨 디렉토리명 더 이상 쓸 수 없다
: ROOT에 다운로드, SDCard 디렉토리 직접 지정 하는 경우(마이그레이션 이슈 발생)
-> OS변경을 감지해서 11로 업데이트 되었다면 기존 패스에서 새로운 패스로 미디어를 복사하는 작업이 필요하다(필요한 경우인듯)
-> 위에 번거로운 복사 작업을 방지하기 위해, 새로운 플레그가 추가되었다(preserveLegacyExternalStorage).
그렇게 하면 구 버전 API에서도 업그레이드 한 경우 마이그레이션을 바이 패스 할수있다.(?)
안드로이드 11에서 설치 or 재 설치한 경우에는 새로운 패스를 이용할수 있게 하고있다.
현재 Scoped Storage 외부에 저장 한 데이터를 마이그레이션하는 데 권장되는 방법은 무엇입니까?
- preserveLegacyExternalStorage 플래그를 사용하면 Android 11을 대상으로하는 경우에도 앱이 업그레이드시 레거시 저장소 액세스를 유지할 수 있습니다.
MediaStore 을 통해 파일 불러오기
1. contentResolver.query()를 통해 파일의 uri를 받아올 수 있습니다.
val uri: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_TAKEN
)
val sortOrder = "$INDEX_DATE_ADDED DESC"
contentResolver.query(uri, projection, null, null, sortOrder)
2. cursor 를 통해 id를 불러와 withAppendedPath함수를 사용해 content:// 경로를 가져올 수 있습니다.
cursor.run {
val idColumn = getColumnIndex(MediaStore.Images.Media._ID)
val id = cursor.getLong(idColumn)
val contentUri = Uri.withAppendedPath(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id.toString()
)
Glide.with(this@StoreActivity)
.load(contentUri)
.into(imageView)
가져온 경로 > content://media/external/images/media/{id}
여기서 안드로이드 Q 미만에서는 '외부 저장소 중 앱 전용 공간'에 저장된 파일도 불러 왔으나 안드로이드 Q에서는 '앱 전용 공간'에 있는 파일은 못 가져오고 '공용 저장소'에 저장한 파일만 가져왔습니다.
이 부분을 통해 안드로이드 Q에서는 파일 접근에 대한 권한을 요구할 때 정말 필요한 권한인지를 다시한번 더 생각해 볼 필요를 느낄 수 있습니다.
MediaStore를 통해 파일 저장하기
1.ContentResolver에 데이터를 저장하기 위해서는 ContentValues클래스를 사용합니다.
val values = ContentValues().apply{
val fileName = "Days-${SystemClock.currentThreadTimeMillis()}.jpg"
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpg")
//추가 경로를 설정
//put(MediaStore.Audio.Media.RELATIVE_PATH, "DCIM/Days")
//iS_PENDING 속성을 1로 해주는 것은 파일을 write 할 때 까지 다른 곳에서 사용 못하게 하는 것입니다.
//파일을 모두 write 할때 이 속성을 0으로 update 해주어야 합니다.
//현 포스팅은 카메라 예제로 외부에서 수정할 수 있어야 하므로 0으로 설정하거나 따로 처리 하지 않습니다.
//1로 되어 있으면 카메라로 찍은 이미지가 저장되지 않습니다.
//0으로 되어 있으면 카메라로 찍은 이미지가 저장 됩니다.
put(MediaStore.Images.Media.IS_PENDING, 0)
}
2. contentResolver를 사용해 MediaStore에서 사용할 수 있는 uri를 받아와 카메라로 넘겨줍니다.
여기서는 WRITE_EXTERNAL_STORAGE권한도 필요 없으며 ContentProvider로 감싸 주지 않아도 동작합니다.
> 이제는 파일 읽기 쓰기 권한을 받을 필요가 없는것 같습니다. 웬만한 기능들이 권한 없이 동작됩니다.
//권한이 없어도 진행 가능 합니다.
//경로 -> content://media/external/images/media/84
val contentUri: Uri? =
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri)
startActivityForResult(intent, PICK_FROM_CAMERA)
3.onActivityResult에서 bitmap으로 이미지 보여주기
if (resultCode != Activity.RESULT_OK) {
//만약 카메라를 사용해 사진을 찍지 않고 뒤로 가게 되면 생성한 uri를 제거해 주어야 합니다.
//그렇게 하지 않으면 검은 화면의 빈 파일이 갤러리에 존재하게 됩니다.
contentUriForAndroidQ?.let {
contentUriForAndroidQ = null
contentResolver.delete(it, null, null)
return
}
}
if (requestCode == PICK_FROM_ALBUM) {
//...
} else if (requestCode == PICK_FROM_CAMERA) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if(contentUriForAndroidQ != null) {
//uri로 부터 이미지를 받아와 화면에 보여주면 됩니다.
val source = ImageDecoder.createSource(contentResolver, contentUriForAndroidQ)
setBitmap(ImageDecoder.decodeBitmap(source))
}
}
}
MediaStore을 통한 삭제
1.파일 삭제
가져온 uri를 contentResolver.delete()를 통해 파일을 지울 수 있습니다.
하지만 안드로이드 Q에서는 파일에 대한 수정 권한이 없기 때문에 삭제할 수 없습니다.
//매우 위험한 코드이다. 갤러리에 있는 모든 사진을 삭제해 버린다.
//하지만 안드로이드 Q 에서는 RecoverableSecurityException 에러가 발생합니다.
contentResolver.delete(contentUri, null, null)
2.권한을 가지고 삭제하기
안드로이드 Q 이상 버전에서는 아래와 같이 에러로 넘어온 intent를 사용해 사진을 삭제할 권한을 받아옵니다.
try {
contentResolver.delete(contentUri, null, null)
} catch (e: RecoverableSecurityException) {
// 권한이 없기 때문에 예외가 발생됩니다.
// RemoteAction은 Exception과 함께 전달됩니다.
// RemoteAction에서 IntentSender 객체를 가져올 수 있습니다.
// startIntentSenderForResult()를 호출하여 팝업을 띄웁니다.
val intentSender = e.userAction.actionIntent.intentSender
intentSender?.let {
startIntentSenderForResult(
intentSender,
DELETE_PERMISSION_REQUEST,
null,
0,
0,
0,
null
)
}
}
참조 :
https://developer.android.com/training/data-storage/shared/media?hl=ko
카메라 예제와 함께 보는 Scoped Storage (안드로이드 Q 대응)
https://black-jin0427.tistory.com/242
안드로이드 11 대응(Target SDK 30) - Scoped storage
https://developside.tistory.com/102
Scoped Storage(범위지정 저장소) 정리 (Legacy Storage와 차이점 정리)
https://youngest-programming.tistory.com/386
https://medium.com/androiddevelopers/android-11-storage-faq-78cefea52b7c
'안드로이드' 카테고리의 다른 글
SAF(Storage Access Framework) (0) | 2021.05.25 |
---|---|
[Android] 이미지 로딩 라이브러리 - Coil (0) | 2021.05.17 |
ViewBinding (0) | 2021.05.13 |
Kotlin apply, with, let, also, run 사용? (0) | 2021.05.11 |
Android 멀티 모듈 (0) | 2021.05.11 |