Content Observer

Programs/Android 2013. 2. 27. 10:45

출처 : http://cafe.daum.net/superdroid/aAfL/110?docid=1MWA2aAfL11020111110130336

Content Observer

Provider는 공용 저장소를 제공하였다.

여기서 좀 찜찜한 기분이 들지 않는가?

공용 저장소라고 하면 나도 데이터를 쓰고/읽고가 가능하고

다른 패키지에서도 데이터를 쓰고/읽고가 가능할 것이다.

 

만일 내가 특정값을 Provider를 통해 읽어와 사용하고 있는 중에,

다른 패키지에서 그 값을 변경했을 경우 문제가 되지 않겠는가?

 

이러한 문제를 해결하기 위해 Content Observer라는 것이 존재한다.

이 녀석의 역할은 특정 URI에 해당하는 데이터가 변경되었는지

감시하는 역할을 한다.

 

테스트 패키지로 이해해 보자.

!!! 테스트 할 패키지는 바로 전 강좌의 소스를 그대로 사용하겠다.

 

먼저 Provider 패키지 부분을 약간 수정해 보자.

아래는 StudentsProvider.java 소스이고

delete() 함수 부분만을 수정할 것이다.

getContext().getContentResolver().notifyChange(uri,null);

이라는 부분이 존재한다.

notifyChange()라는 함수명만 보아도 대충 알만하지 않는가?

변경된 사실을 누군가에게 알린다는게 아닐까?

 

그렇다!  해당 함수는 ContentResolver들에게 DB의 내용이 변경되었다는 것을

알리는 역할을 한다.

위의 소스는 delete() 함수 즉 특정 레코드를 삭제하고 있다.

그러므로 DB의 내용이 레코드 삭제를 통해 변경 되었으므로,

ContentResolver 들에게 알리는 것이다.

 

getContext().getContentResolver().notifyChange(uri,null);

 

notifyChange()함수에서 첫번째 인자 uri 는 무엇을 말할까?

변경된 부분의 Uri 즉 경로를 말한다.

예를 들어 Uri가

content://com.provider.students/students/4

와 같다면

students 테이블의 4번째 id 레코드가 삭제되었다는 것을 의미 하는 것이다.

 

content://com.provider.students/students

와 같다면

students 테이블의 여러개의 레코드가 삭제되었다는 것을 의미 하는 것이다.

 

위의 소스와 같이 Provider 구현자는 DB가 변경되는 부분에

notifyChange() 함수를 넣우 둬야 한다.

DB가 변경된 사실을 꼭 알릴 필요가 없다면 마음대로 하라. ^^

내가 하고 싶은 말은 구현자의 몫이라는 것이다.

 

 

 

  !!! 그냥 참조만 하자.

 

 

  우리가 사용하는 ContentObserver는 System Service이다.

  위의 소스에서 ContentService 가 바로 ContentObserver를 관리하는 서비스 인 것이다.

  아래는 Framework의 ContentService 소스이다.

  ContentProvider에서 notifyChange() 함수를 호출하면 바로

  ContentService의 notifyChange() 함수가 호출되는 것이다.

 

이제는 위의 변경되었다는 사실을 받는 부분을 구현해야 한다.

이전 패키지에 Resolver 부분을 그대로 수정하자.

아래는 StudentsResolverTestActivity.java 파일이다.

1번과 같이 ContentObserver 객체 변수를 선언하고

2번과 같이 ContentObserver 를 구현한다.

ContentObserver class는 abstruct class이다.

그러므로 추가적으로 3번과 같이 0nChange ()함수를 구현해 주어야 한다.

해당 함수는 ContentProvider가 변경 사실을 알려 줄때

호출되는 Callback 함수 이다.

해당 함수가 정말 호출되었는지 확인하기 위해서 Log를 남겨 두자.

이 ContentObserver가 동작하기 위해서는

5번과 같이 ContentResolver에 등록해 주어야 한다.

등록하는 함수는 registerContentObserver()이다.

또한 해지 하는 함수는 6번과 같이 unregisterContentObserver()이다.

 

그런데 만일 해지를 하지 않으면 큰일 날까?

Framework 소스 확인해 본 결과 특별히 문제가 되진 않았다.

그런데 왜 책이나 인터넷에는 꼭 해지를 해야 한다고 할까? ^^a

 

개인적인 생각으로는

더 이상 noti를 받기 싫은 경우에 unregisterContentObserver()를 사용해서

해지 하는 것이 주 용도라고 생각한다.

하지만 등록과정이 있으므로 해당 Activity가 종료하기 전에

해제 하도록 하여 Framework의 부하를 조금이라도 줄여 주는 것이 맞다.

 

수정한 Provider와 Resolver 패키지를 모두 설치하고

Resolver를 아래와 같이 실행해 보자.

위의 1번 Insert를 눌러 ContentPorvider에 데이터를 추가하고

2번의 Delete를 눌러 데이터를 삭제해 보자.

Delete를 누르면 Provider에서 notifyChange() 가 호출될 것이다.

3번에서 정상적으로 호출된 로그가 출력되었다,

해당 notifyChange()가 Provider에서 호출된후,

Resolver 측에서 그 결과를 전달받게 된다.

4번에서 정상적으로 0nChange() 함수가 호출된 것을 볼 수 있다.

정말 간단하지 않은가? ^^

 

자 좀더 자세히 알아보기 위해

registerContentObserver() 함수에 대해서 살펴 보자.

 

해당 함수의 원형은 아래와 같다.

 

public final void registerContentObserver

(

Uri                       uri,  <<== 첫번째 인자.

boolean                notifyForDescendents,

ContentObserver    observer

)

 

위에서 세번째 인자 observer는 무엇을 의미하는지 알 것이다.

0nChange() 가 구현된 ContentObserver 객체를 넣어주면 된다.

그렇다면 첫번째 인자 URI는 무엇을 의미하는 것일까?

아래의 그림을 보고 이해해 보자.

우선 Resolver 측에서

1번에서 registerContentObserver()함수를 이용해서 등록하였다.

2번을 보면 등록할때 첫번째 인자로 URI를 넘긴다.

3번과 같이 URI는 "content :// com.provider.students / students"이다.

         즉 "content :// com.provider.students / students" 경로의 데이타가

         변경되면 알려 달라는 의미이다.

자 이제 Provider 측이다.

4번을 보면 특정 데이타가 변경되어 notifyChange() 함수를 호출하여 알린다.

5번은 변경된 데이터의 경로 Uri가 첫번째 인자로 전달된다.

6번의 Uri은 "content :// com.provider.students / students" 이다.

        즉 "content :// com.provider.students / students" 경로의 데이타가 변경되었다는 의미이다.

7번에서 ContentService 측에서

        Resolver에서 등록한 Uri와 Provider에서 알린 Uri가 같은지 체크한다.

8번에서 같으면 ContentService는 해당 Resolver에 0nChange() 함수를 호출해 주는 것이다.

 

이해가 되는가?

 

 

자 그렇다면 두번째 인자가 의미하는 것은 무엇일까?

 

public final void registerContentObserver

(

Uri                       uri,

boolean                notifyForDescendents, <<== 두번째 인자.

ContentObserver    observer

)

 

위에서 보면 Provider에서 보낸 Uri와 Resolver에서 등록한 Uri가

완전 동일해야만 noti를 받을 수 있었다.

하지만 예를들어

Resolver에서 등록한 Uri : "content :// com.provider.students / students"

Provider에서 보낸     Uri : "content :// com.provider.students / students / 4"

이라면 서로 Uri가 달라 받을 수 없을 것이다.

하지만 자세히 보라. 상위 경로는 모두 동일하고 하위 경로 "/ 4" 만 다른다.

이렇게 하위 경로만 다른 경우에도 받고 싶을 때가 많을 것이다.

(하위 경로를 일일이 다 알 수 있는 것다 아니지 않은가?)

 

그때 바로 두번째 인자인 notifyForDescendents 를 "true"로 설정하면 된다.

 

아래의 그림으로 이해해 보자.

Provier측에 7번에서 하위 경로가 "/125" 로 추가 되었다.

하지만

Resolver측에서 2번과 같이 notifyForDescendents 를 "true"로 설정하였다.

그로 인해 8번과 같이 일치하지 않아도

9번이 실행된 것이다.

 

자 테스트를 해 보자.

아래의 Resolver 소스를 약간 변경해 보자.

자 "true"로 변경하였다.

 

 

 

자 정상적으로 받을까?

실행해 보자.

 

1번버튼을 눌러 insert로 데이터를 추가해 두자.

데이터를 추가할때 화면에 "content :// com.Provider.Students / students / 3" 번 경로의 데이터가

추가된 것을 알 수 있다.

 

!!! 자 2번에 delete 버튼을 눌러 지우기 전에 한가지 소스를 더 수정해야 한다.

위와 같이 "content :// com.Provider.Students / students / 3" 이 수정 되었으므로

해당 데이터만 지원 주는 코드를 테스트를 위해 변경한 것이다.

다시 실행해 보자.

 

자 이제 2번과 같이 Delete 버튼을 눌렀 이전에 1번에서 추가된

"content :// com.Provider.Students / students / 3" 을 삭제해 보자.

과연 0nChange() 함수가 호출이 될까?

위에 로그를 보면 4번과 같이 정상적으로 호출된 것을 알 수 있다.

 

정리해 보면

 

Provider에는

    특정 레코드가 삭제되었으므로

    "content :// com.Provider.Students / students / 3"  라고 Uri이 notifyChange()를 날렸을 것이고,

Resolver에는

    "content :// com.Provider.Students / students" 라고

    Uri가 registerContentObserver() 함수로 등록되었다.

 

그렇다면 서로 Uri가 달라서 0nChange ()가 호출되지 않아야 하나.

Resolver에서 registerContentObserver() 함수로 등록할때

두번째 인자인 notifyForDescendents 를 "true"로 설정했지 때문에

하위 경로가 추가되어도 호출이 된 것이다.

 

아~ 늘 말하는 거지만 간단한 것을 글로 설명하자니 또 길어 졌다. ^^;;

 

자 마지막으로

ContentObserver 객체를 생성할때

구현해 주어야 하는 0nChange() 함수를 자세히 보자.

1번과 같이 첫번째 인자로 selfChange 라는 녀석이 있다.

무엇을 위해 존재하는 것일까?

테스트를 위해 바로 전에 수정한 내용을 모두 원복하고

2번과 같이 selfChange 값이 어떻게 넣어 오는지 로그를 남겨 보자.

 

자 실행하자.

1번 버튼을 눌러 데이터를 추가하고

2번 버튼을 눌러 지워보자.

3번과 같이 로그를 확인해 보면 0nChange() false" 라고 출력 되었다.

 

이 값은 늘 "false" 일 것이다. ^^;

그렇다면 "true"인 경우가 없는가?

물론 존재한다.

위에서 늘 "false"라고 했던 이유는 우리가 특별한 처리를 해 주지 않은 경우이다.

 

자 그 특별한 경우가 무엇일까?

 

잠시 아래의 내용을 고민해 보자.

만일 Provider에서 notifyChange()를 날렸을때 말고

Resolver에서 강제로 0nChange ()를 호출 되게 할 방법이 없을까?

존재한다.

아래의 Resolver 소스를 수정해 보자.

위의 1번은 insert 버튼을 눌렀을때

생성된 ContentObserver 객체 멤버 함수인 dispatchChange() 함수를 호출하는 것이다.

 

이 함수가 바로 0nChange () 함수를 강제로 호출하게 해준다.

 

dispatchChange()  함수 인자에는 boolean 값이 들어 간다.

이 값을 "true"라고 넣자.

 

자 이제 실행해 보자.

1번에 insert 버튼을 누르면

2번과 같이 dispatchChange()  함수 호출되면서 0nChange () 함수가 강제로 호출되고

         로그가 남았다.

         여기서 주목할 것은 0nChange () 함수의 인자가 "true"로 넣어 온 것이다.

         즉 dispatchChange(boolean) 함수의 인자 값에 따라

         0nChange() 의 인자가 결정되는 것이다.

         우리가 강제로 dispatchChange() 함수를 호출할때는

         우리 스스로가 호출한 것을 구분하기 위해 dispatchChange() 함수의

         인자를 "true"로 넣어 준다.

         그렇다면 0nChange () 함수 내에서 인자 "selfChange" 를 보고

         구분에서 원하는 처리를 하면 된다.

 

 

위에서 작성된 테스트 코드는 아래의 첨부를 참조하자.

 

Posted by outliers
,