Domain/Android

[Android] MediaStore ( Android 11 / Android 버전 대응 / Storage 정책 )

by Donghwan 2021. 4. 23.

Android MediaStore는 Android Q에서 Scoped Storage를 적용하게 되면서 필수적으로 적용해야 할 부분입니다. Android R 정책에 API Level 30을 타겟으로 하는 앱은 Scoped Storage 정책만 사용해야 한다는 내용이 있습니다.

Android Q 이전엔 내부 저장소는 시스템 및 개별 앱이 사용하는 공간으로 샌드박스 형식으로 잘 보호되는 영역이며, 각 앱은 개별 저장공간을 할당 받았습니다. 반면 외부 저장소는 하나의 공용 저장소로 동작했습니다. 외부 저장소를 읽고 쓸 수 있는 권한이 있으면 모든 파일에 접근이 가능했습니다. 외부 저장소에 접근 권한만 있다면 Private한 파일 조차 읽고 쓸 수 있었습니다. 때문에 개별 앱들이 간접적인 방법으로 다른 앱의 정보를 확인할 수 있는 위험성을 갖고 있었습니다.

하지만 Android Q의 Scoped Storage가 도입되면서 사용자의 정보 보호를 위해 외부 저장소의 개념이 변화됩니다. 기존 외부 저장소의 Public한 공간이 완전히 사라지며 이미지, 동영상, 오디오, 다운로드에 대해서만 접근이 가능하도록 변경 됩니다.

각 공용 공간(이미지, 동영상, 오디오, 다운로드)는 MediaStore를 통해서만 읽고 쓸 수 있습니다. 또한 다운로드를 제외한 공간은 해당 타입에 대한 파일만 저장이 가능합니다. 추가 권한 없이도 공용 공간에 파일을 생성 할 수 있으며, 앱 삭제시 해당 앱이 소유한 모든 파일이 삭제됩니다(앱 삭제 이후에 유지가 되어야하는 파일은 따로 처리해야 합니다).

MediaStroe은 추가 권한 없이 사용가능하며, 다른 앱이 저장한 콘텐츠를 보기 위해서는 READ_EXTERNAL_STORAGE 권한이 필요합니다. 그 외의 파일들은 System File Picker(시스템 기본 파일 선택기)를 통해 접근합니다. 사용자가 다른 앱에서 보지 않아도 되는 파일이나, 앱이 제거 된 이후, 유지될 필요가 없는 파일은 MediaStore를 사용하면 안됩니다.

Sample Code

/* Android Q (API Level 29) 이하는 WRITE_EXTERNAL_STORAGE Permission이 필수적으로 필요 */
val values = ContentValues().apply {
            this.put(MediaStore.Images.Media.DISPLAY_NAME, "image_1024.JPG")
            this.put(MediaStore.Images.Media.MIME_TYPE, "image/*") //TYPE
            this.put(MediaStore.Images.Media.DATE_TAKEN, "") //DATA_TAKEN -> 날짜 정보 어떻게 설정할지
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                this.put(MediaStore.Images.Media.IS_PENDING, 1)
            }
        }
        contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.let { uri ->
            contentResolver.openFileDescriptor(uri, "w", null).use { parcelFileDescriptor ->
                parcelFileDescriptor?.run {
                    // write something to OutputStream
                    FileOutputStream(this.fileDescriptor).use { outputStream ->
                        val imageInputStream = resources.openRawResource(R.raw.logo)
                        while (true) {
                            val data = imageInputStream.read()
                            if (data == -1) {
                                break
                            }
                            outputStream.write(data)
                        }
                        imageInputStream.close()
                        outputStream.close()
                    }
                }
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                values.clear()
                values.put(MediaStore.Images.Media.IS_PENDING, 0)
                contentResolver.update(uri, values, null, null)
            }
        }

 


참고자료

  • Android Developer
728x90
반응형

댓글