HOME > android > basic

안드로이드 MediaStore에서 미디어 파일 삭제

By JS | 01 Dec 2019

MediaStore에서 Image/Video/Audio 등 미디어 파일 삭제하는 방법을 소개합니다. Query, Update 등과 유사하게, ContentResolver.delete() API로 MediaStore의 아이템을 삭제할 수 있습니다.

Android 10(TargetSDK 29)에서 Scoped Storage 적용으로, 어떤 파일을 삭제하려면 사용자로부터 허락을 받아야 할 수도 있습니다. 예외로, 앱이 추가한 미디어 파일은 허락없이 삭제가 가능합니다.

반면에, TargetSDK 29 미만에서 앱이 미디어 파일을 삭제하려면 WRITE_EXTERNAL_STORAGE 권한을 갖고 있어야 합니다.

Android 10과 그 미만의 SDK에서 어떻게 아이템을 삭제하는지 알아보겠습니다.

MediaStore의 아이템을 쿼리하는 방법은 MediaStore에서 미디어 파일 정보 읽기를, MediaStore에 아이템을 추가하는 방법은 MediaStore에 미디어 파일 저장하는 방법 참고해주세요.

미디어 파일 삭제 (Android 10)

Android 10에서 Scoped Storage의 적용으로 3rd party app은 WRITE_EXTERNAL_STORAGE 권한을 받을 필요가 없어졌습니다.

모든 앱은 WRITE_EXTERNAL_STORAGE 권한이 있어도 파일을 쓸 수 없기 때문입니다. 파일을 쓰려면, 필요할 때마다 사용자에게 권한을 받아야 합니다.

ContentResolver.delete()는 MediaStore의 아이템을 삭제합니다. 앱이 등록한 미디어 파일은 사용자의 허락없이 삭제할 수 있고, 다른 앱에서 등록한 미디어 파일을 삭제하려면 사용자의 허락을 받아야 합니다.

앱이 직접 등록한 미디어 파일

앱이 직접 등록한 미디어 파일은 사용자 허락없이 삭제할 수 있습니다.

다음 코드는 특정 이름의 미디어 파일을 찾고, 그 Uri를 MediaStore에서 삭제하는 코드입니다.

// fileName 과 동일한 아이템을 찾은 후 Uri를 저장
val projection = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DISPLAY_NAME
)
var displayName: String? = null
var contentUri: Uri? = null
contentResolver.query(
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    projection,
    null,
    null,
    null
)?.use { cursor ->
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
    val displayNameColumn =
        cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
    while (cursor.moveToNext()) {
        val id = cursor.getLong(idColumn)
        displayName = cursor.getString(displayNameColumn)
        if (displayName != fileName) {
            continue
        }
        contentUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            id.toString()
        )
    }
}

// 찾은 Uri를 MediaStore에서 삭제
contentUri?.let{
    contentResolver.delete(contentUri!!, null, null)
    Log.d(TAG, "Removed $displayName from MediaStore: $contentUri")
}

다른 앱이 등록한 미디어 파일

만약 다른 앱이 저장한 미디어 파일이나, 사용자가 PC에서 디바이스에 저장한 미디어파일을 삭제하고 싶을 수도 있습니다. 이런 경우, 앱은 삭제할 수 있는 권한이 없습니다.

만약 위와 같이 삭제하려고 시도하면 RecoverableSecurityException 예외가 발생하며 실패합니다. 이 예외가 발생할 때 Media Provider는 RemoteAction 객체를 생성하여 Exception에 저장합니다. RemoteAction에는 IntentSender 객체가 있으며, 이를 이용하여 사용자에게 이 파일을 삭제해도 되는지 묻는 팝업을 띄울 수 있습니다.

사용자가 확인 버튼을 누르면, 앱은 삭제 권한을 부여받게 되고, 다시 삭제를 시도하면 삭제가 성공합니다.

다음은 방금 말한 내용에 대한 예제입니다.

private const val DELETE_PERMISSION_REQUEST = 0x1033

try {
    contentUri?.let{
        contentResolver.delete(contentUri!!, null, null)
        Log.d(TAG, "Removed $displayName from MediaStore: $contentUri")
    }
} 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
        )
    }
}

// 팝업이 종료되면, 결과가 onActivityResult로 콜백됩니다.
// 이 때 다시 삭제를 시도할 수 있습니다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode == Activity.RESULT_OK && requestCode == DELETE_PERMISSION_REQUEST) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            removeMediaFile("my_image.jpg")
        }
    }
}

IntentSender를 실행하면 다음과 같은 팝업이 보이며, 확인 버튼을 누르면 앱은 삭제 권한을 부여받습니다. popup for removing a file from MediaStore

권한을 받으면 onActivityResult()로 콜백이 오며 다시 삭제를 시도할 수 있습니다.

에뮬레이터에서 위의 코드를 테스트했을 때 MediaStore에서 아이템이 삭제되는 것을 확인하였습니다. 하지만 adb shell로 그 파일이 저장된 실제 경로에 들어가면 파일은 삭제되지 않고 남아있습니다. 이 부분이 버그인지, 정책에 의한 동작인지는 확실히 모르겠습니다. 자신이 등록한 미디어 파일은 실제 파일까지 삭제되는 것으로 보아 실제 파일도 삭제되는 것이 올바른 동작인 것 같습니다.

미디어 파일 삭제 (Android 10 미만)

Android 10 미만에서 MediaStore의 파일을 삭제하려면 WRITE_EXTERNAL_STORAGE 권한을 받아야 합니다. 위와는 다르게 권한을 미리 받았기 때문에 다음과 같은 코드로 바로 삭제할 수 있습니다.

contentResolver.delete(contentUri!!, null, null)

이 글에서 사용한 코드는 GitHub - MediaStore에서 확인할 수 있습니다.

참고