반응형

 

UriMatcher는 ContentProvider를 접한 학습자가 추가로 맞이하게 되는 녀석이다.

본 포스팅을 방문했는데 ContentProvider는 뭐지? 라는 분들은 아래 링크 먼저 다녀오시길 바란다.

 

[Android] ContentProvider 누구보다 쉽게!

 

안드로이드 개발 문서에 보면 UriMatcher에 대해 다음과 같이 정의하고 있다.

 

 

Utility class to aid in matching URIs in content providers.

 

ContentProvider의 Uri 매칭을 처리하는 유틸리티 클래스로 정의되어 있으나, 앞서 설명한 것 처럼 Uri 매칭을 처리하기 때문에 구글에서도 아래와 같이 static 필드로 가이드하고 있다.

private static final int PEOPLE = 1;
private static final int PEOPLE_ID = 2;
private static final int PEOPLE_PHONES = 3;
private static final int PEOPLE_PHONES_ID = 4;
private static final int PEOPLE_CONTACTMETHODS = 7;
private static final int PEOPLE_CONTACTMETHODS_ID = 8;

private static final int DELETED_PEOPLE = 20;

private static final int PHONES = 9;
private static final int PHONES_ID = 10;
private static final int PHONES_FILTER = 14;

private static final int CONTACTMETHODS = 18;
private static final int CONTACTMETHODS_ID = 19;

private static final int CALLS = 11;
private static final int CALLS_ID = 12;
private static final int CALLS_FILTER = 15;

private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static
{
    sURIMatcher.addURI("contacts", "people", PEOPLE);
    sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
    sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
    sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
    sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
    sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
    sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);
    sURIMatcher.addURI("contacts", "phones", PHONES);
    sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);
    sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);
    sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);
    sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);
    sURIMatcher.addURI("call_log", "calls", CALLS);
    sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);
    sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
}

 

때문에 대다수의 블로그, 서적, 개발자들은 ContentProvider 클래스 내부에 UriMatcher를 구성하여 처리하고 있으며, 이는 결과적으로 추가되는 외부 연동에 따른 확장성을 고려할 때, 매우 비 효율적인 코드라고 볼 수 있다.

 

따라서 필자는 다음의 아이디어로 UriMatcher를 정리해보았다.

 

 

외부 연동 Config를 Enum으로 관리해보면 어떨까?

UriMatcher 생성시 초기화 과정에서 추가해주는 uri 정보들은 결국 외부 연동을 위해 관리되는 정보이다. 따라서 해당 정보를 별도의 Enum 클래스로 관리해주고, 커스텀 addURI 메서드를 구현하여 처리해주고자 하였다.

/**
 * 외부 연동 정보 클래스
 */
enum class DabalExternal(
        /**
         * 외부 연동 코드
         */
        val dabalExternalCode:Int,
        /**
         * 외부연동 경로
         */
        val dabalExternalPath:String,
        /**
         * 외부연동 메서드 키
         */
        val dabalExternalMethodKey:String) {
    /**
     * 정보 없음
     */
    UNKNOWN(-1, "",""),
    /**
     * 샘플앱
     */
    SAMPLE(0, "dabal_test_call", "isDabal")
}

 

 

UriMatcher에서 처리할 기본 / 커스텀 메서드를 구현한 커스텀 UriMatcher를 제공해주면 어떨까?

UriMatcher는 기본적으로 기준정보라 보이는 외부연동 정보를 addURI를 통해 추가하여 입력된 uri정보가 관리되고 있는 Uri 정보중에 매칭된 정보가 있는지 여부를 판별해주는 용도로 쓰이고 있다.

 

하지만 필자가 구현한 메서드 콜 방식의 ContentProvider에서 사용하는 UriMatcher의 경우, Uri 매칭 이외에 외부 연동 메서드 콜을 위한 키 정보를 제공해주는 기능도 요구된다.

 

따라서 UriMatcher 객체를 ContentProvider에서 직접 생성 / 접근하는 방식이 아닌 TestUriMatcher라는 커스텀 클래스 내에서 관리되는 UriMatcher를 통해 처리될 수 있도록 구현해 보았다.

 

또한, 외부 연동의 빈도수를 고려했을때, static 필드로 힙 메모리에 적재해놓지 않고, 필요한 순간에 메모리를 동적 할당하여 사용하고 해제하는 방식의 UriMatcher를 구현하였다.

 

아래 코드로 보자.

/**
 * UriMatcher 관련 유틸 클래스
 */
class TestUriMatcher {

    /**
     * 고정 값 final 관리
     */
    companion object {
        /**
         * 필터 Url 기본
         */
        private const val AUTHORITY : String = "com.dabal.external"

    }

    private val uriMatcher: UriMatcher = UriMatcher(android.content.UriMatcher.NO_MATCH)

    init {
        addUri(uriMatcher, DabalExternal.SAMPLE)
        // 외부 연동 추가 시 DabalExternal 클래스에 기준 정보 추가 후 아래에 추가해준다.
    }

    /**
     * UriMatcher 추가 함수.
     * DabalExternal 정보를 받아서 자동으로 처리해 준다.
     */
    private fun addUri (uriMatcher: UriMatcher, dabalExternal: DableExternal) {
        uriMatcher.addURI(AUTHORITY, dabalExternal.dabalExternalPath, dabalExternal.dabalExternalCode)
    }

    /**
     * 외부 호출 용 메서드 반환
     */
    fun getExternalCallInfo(uri: Uri?) : String {
        return uri?.let {
            when(uriMatcher.match(it)) {
                DabalExternal.SAMPLE.dableExternalCode -> DabalExternal.SAMPLE.dabalExternalMethodKey
                else -> DabalExternal.UNKNOWN.dabalExternalMethodKey
            }
        } ?: return DabalExternal.UNKNOWN.dabalExternalMethodKey
    }
}

 

기본 Url의 경우, 앞서 블로그에서 기술한 ContentProvider 구현 시 사용한 테스트 Authority Url 정보를 사용하였다.

해당 클래스를 통해 외부에서 들어오는 콜에 대해 DabalExternal 클래스에 정의된 기준정보 별 분기 처리가 가능하다.

 

분기된 코드에 따라 실제 ContentProvider에서는 보다 간략한 코드로 처리할 수 있다.

//ContentProvider 샘플 코드

override fun getType(uri: Uri?): String? {
    val uriMatcher = TestUriMatcher()
    return uri?.let {
    uriMatcher.getExternalCallInfo(uri)
    } ?: return null
}

/**
* 외부에서 메서드 호출 접근 시 처리한다.
*/
override fun call(method: String?, arg: String?, extras: Bundle?): Bundle {
    return when (method) {
        DabalExternal.SAMPLE.dabalExternalMethodKey -> isDabal()
        else -> Bundle()
    }
}

 

어떤가? 보다 쉽게 UriMatcher를 사용할 수 있지 않은가?

위의 방식으로 코드를 분리하면 추가되거나 삭제되는 외부 연동에 대해 쉽게 대응이 가능하다.

 

본 포스팅에서는 ContentProvider에서 사용하는 UriMatcher에 대해 기술하였다.

필자도 ContentProvider와 UriMatcher에대해 학습과정에 많은 허들이 있었던 만큼 본 포스팅을 방문하는 분들은 조금 더 수월하게 개발하기를 바라며 본 포스팅을 마치고자 한다.

반응형

'안드로이드 > 컴포넌트' 카테고리의 다른 글

[Android] RecyclerView에 Header Footer 구현기  (0) 2018.06.19

+ Recent posts