내 앱이 아닌 다른 애플리케이션에서 미디어(이미지, 동영상 또는 파일)을 추가/삭제 여부를 감지하기 위해서는

ContentObserver를 다음과 확장하여 사용할 수 있다.

class PickleContentObserver(val activity:FragmentActivity) : ContentObserver(Handler()), LifecycleObserver{
    ...
    val contentChangedEvent = SingleLiveEvent<Void>() // Activity 또는 Fragment에서 구독

    init {
        activity.lifecycle.addObserver(this)
        activity.contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, this)
        activity.contentResolver.registerContentObserver(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true, this)
    }

    override fun onChange(selfChange: Boolean, uri: Uri?) {
        super.onChange(selfChange, uri)
        contentChangedEvent.postValue(null)
        logger.i("onChange : selfChange = $selfChange uri = $uri")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy(){
        activity.contentResolver.unregisterContentObserver(this)
    }
}

다른 애플리케이션으로 부터 새로운 미디어가 추가/삭제된 경우는 기존 미디어 리스트를 무효화하고 새로운 Cursor로부터 최신 데이터를 얻을 수 있다. 

하지만 미디어가 삭제되기 전에 특정 미디어의 id 또는 Uri를 따로 관리하고 있고, 특정 미디어 삭제 이후 기존 선택된 미디어 목록에 대한 유효성 검사가 필요하다. 다시 말하자면 체크박스로 선택된 사진이 다른 앱에서 삭제되면 사진도 삭제 해야되지만 체크박스로 선택되었던 상태도 삭제해야한다.

미디어의 id로 Uri의 정보를 얻는 방법은 간단하다.

var uri = ContentUris.withAppendedId(상위콘텐츠Uri, id) 
// 예) 사진 : MediaStore.Images.Media.EXTERNAL_CONTENT_URI

해당 미디어의 Uri가 이미 있다면 위의 과정은 건너뛴다.

이제 Uri가 실제로 존재하는 파일인지 확인하려면 다음 코드를 사용할 수 있다. 구글 크로미움 오픈소스 참조

class ContentResolverUtil(val context: Context) {
    ...
    private val contentResolver: ContentResolver = context.contentResolver
    fun isExist(uri: Uri) :Boolean{
        var pfd:ParcelFileDescriptor? = null
        try {
            pfd = contentResolver.openFileDescriptor(uri, "r")
            return pfd!=null
        } catch (e:FileNotFoundException){
            logger.w(e.toString())
        }finally {
            try {
                pfd?.close()
            }catch (e :IOException){
                logger.w(e.toString())
            }
        }
        return false
    }
    ...
}

ContentResolver로부터 query() 메서드에 조건절을 추가하여 호출하는 것도 방법이 될 수 있다.

val cursor = contentResolver.query(
    uri, // 미디어 Uri 
    arrayOf(MediaStore.MediaColumns._ID),
    null,
    null,
    null)
if(cursor.count == 0){
    logger.i("삭제된 미디어")
}

하지만 약 18만개의 사진, 동영상 미디어를 가지고 Galaxsy S9으로 테스트 해본 결과  ParcelFileDescriptor의 인스턴스 존재 유무를 체크하는것이 비용이 더 적었다.

query()로 미디어 스토어 테이블에 해당 Uri가 존재하는지 체크할 때는 6~8ms 소요

ParcelFileDescriptor의 null여부를 체크할 때는 3~5ms 소요 

 

카테고리: Kotlin

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.