본문 바로가기
Android/Android 기초

[Android] 안드로이드 저장소(2) - Scoped Storage

by 태크민 2024. 1. 7.

안드로이드 버전10 이상부터는 Scoped Storage를 사용한다. 이전 버전이였던 Legacy Storage와 어떤차이가 있는걸까? 지난 포스트에서도 말했듯이 안드로이드의 저장소는 크게 내부저장소와 외부저장소로 나뉜다. 두 버전에서 내부저장소는 동일하고 외부저장소의 구조가 살짝 바뀌었다. 

 

외부저장소 구조의 변화

기존에는 공용공간안에 모든 파일이 저장되었다면, 변경된 후에는 개별공간이 샌드박스 형태로 보호되어있고 공용공간 또한 타입별로 분리되었다. 개별 앱 공간은 앱 삭제시 함께 제거되고, 공용공간은 앱이 삭제되어도 기기에 남아있다.

 

외부저장소 접근 방법

1. 개별 앱 접근방법

개별 앱 공간은 따로 권한요청이 필요 없고 Context.getExternalFilesDir()를 통해 자신의 앱 공간에만 접근할 수 있다. 기존에는 EXTERNAL_STORAGE권한으로 다른 앱의 개별공간을 접근했지만 변경된 후 부터는 접근할 수 없다.

 

2. 공용공간 접근방법 (Media부분 - 사진 및 동영상, 음악)

안드로이드 버전 10부터는 MediaStore api 사용을 권장하고 있다. MediaStore은 사용자가 가지고있는 파일들을 다른 앱에서도 사용될 수 있도록 설계된 api이다. Media디렉토리 안에서는 자신의 앱에 해당하는 곳은 권한없이 사용할 수 있다. 이전 버전에서는 WRITE_EXTERNAL_STORAGE만 있으면 다른 앱의 공용파일에도 접근할 수 있었지만, 보안상의 이슈로 10부터는 이를 막아놓은 것이다.

 

아래 코드는 MediaStore API 샘플이다. 만약, 다른 앱의 미디어스토어를 사용하려면 접근권한이 필요하다.

val 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 = MediaStore.Images.Media.DATE_TAKEN + " DESC"
 
context.contentResolver.query(
uri, //찾고자 하는 데이터의 Uri
projection, //DB의 column과 같다
null, //Selection, DB의 where과 같다
null, //Selection args, selection에서 물음표에 들어갈 값
sortOder //쿼리 결과데이터의 정렬 기준
)?.use{ cursor ->
	while(cursor.moveToNext()){
    	//projection의 ID column을 사용해서 미디어 아이템의 URI획득
    }
}

 

3. 공용공간 접근방법 (Downloads부분 - 기타 파일들)

미디어 파일 외의 파일들에 접근하는 방법이다. 이곳은 접근권한 없이 Storage Access Framework와 시스팀 파일 선택기를 통해 사용자가 명시적으로 파일을 선택할 수 있다. 

 

Stroage Access Framework는 선택 ui를 화면에 띄워서 앱에서 파일에 접근할 수 있도록 한다. Intent.ACTION_OPEN_DOCUMENT액션을 사용해 새로운 액티비티를 띄우는 방식이다.

val READ_REQUEST_CODE : Int = 42
 
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply{	
	addCategory(Intent.CATEGORY_OPENABLE)	//열 수 있는 파일들만 보고싶을 때 사용
    type = "image/*"	//타입과 일치하는 파일들만 보여줍니다.
}
 
startActivityForResult(intent, READ_REQUEST_CODE)

 

 

선택ui에서 이미지 하나를 선택하면 해당 이미지의 Uri가 앱으로 전달됩니다.

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
    super.onActivityResult(requestCode, resultCode, resultData)
 
    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        resultData?.data?.also { uri ->
            showImage(uri)
        }
    }
}

 

 

저장소 사용방법 요악

파일위치 권한 접근 앱 삭제시 제거
내부저장소 개별 앱 공간 필요 없음 Context.getFilesDir() Y
외부저장소 개별 앱 공간 필요 없음 Context.getExternalFilesDir() Y
공용공간 Media(사진 및 동영상, 음악) READ_EXTERNAL_STORAGE (다른 앱 접근할때만) MediaStore(or SAF) N
Downloads(기타) 필요없음 Storage Access Framework(system's file picker) N