안드로이드 2.0 부터 가장 크게 변경된 점 중의 하나는 주소록(Contact) 관련된 기능입니다. 기존에는 하나의 안드로이드 폰은 하나의 구글 계정만을 지원 했지만, 이 후 버전 부터는 여러개의 계정을 동시에 사용할 수 있게되었습니다. 한 폰에서 사용중인 계정이 여러가지일 경우, 사용자가 새로운 주소록 정보를 추가하고자 할 때, 어떤 계정과 연관된 주소록 정보인지 지정하게 변했더군요. 새로운 Contacts API 는 사용자가 특정 사람에 대한 정보를 하나 이상의 계정을 통해 중복해서 입력하더라도(예를 들어 동일 인물인데 google 계정 주소록 싱크를 통해 추가되고 뒤 이어 facebook 계정 싱크를 통해 두번 추가된다면..), 해당 정보를 요리조리 비교해서 하나의 주소록 정보로 통합해주는 신기한 일을 수행 한다고 합니다. 하지만 그만큼 개발자가 Contacts API 를 사용하는 것이 복잡해졌는데요, 구글 개발자 사이트 참고 기술 문서에 새로운 Contacts API 를 사용하는 방법에 관한 문서가 있어서 번역해 봅니다. 내용이 꽤 길고;;; 제가 정확하게 내용을 이해하고 번역한 문서가 아니라... 아무래도 잘못된 부분이 있겠지만, 안드로이드펍 개발자 분들에게 도움이 될까 싶어 올려봅니다. 내용을 살펴보시다가 잘못된 부분을 알려주시 저에게도 큰 도움이 되겠습니다~^^

Using the Contacts API



  안드로이드 2.0(API Level 5) 부터, 안드로이드 플랫폼은 하나 이상의 사용자 계정을 통해 수집된 주소록 정보를 통합적으로 관리할 수 있도록 보다 향상된 Contacts API 를 제공한다. 여러 사용자 계정을 통해 추가되는 중복되는 정보를 관리하기 위하여, Contacts Content Provider 는 유사한 주소록 정보를 모아 하나의 '주소 집합(Contact)'을 만들고, 그 결과를 사용자에게 보여준다. 이 문서는 주소록을 관리하기위해 새로운 Contacts API 를 어떻게 사용하는지 설명한다.


 새로운 Contacts API 는 android.provider.ContactsContract  및 이와 관련된 클래스들에 에 정의되어 있다. 이전 Contacts  API 는 비록 더이상 사용되지 않는 것으로 표시되지만(deprecated), 여전히 작동한다. 만일 이전 API 를 사용하는 어플리케이션이 어떻게 새로운 주소록 시스템을 지원할 수 있는지 궁금하다면, '기존 어플리케이션에 대한 고려' 항목을 참고해라.


 만일, 새로운 Contacts API 를 어떻게 사용해야 하며, 특히 하나의 동일한 어플리케이션에서 어떤식으로 이전 API 와 새로운 API 둘 모두를 지원할 수 있는지 궁금하다면, 'Business Card' 샘플 어플리케이션을 살펴봐라. 


Data structure of Contacts

 새로운 Contacts API 에서는, 주소록 정보는 세 개의 주요한 테이블 - 'Contacts', 'Raw Contacts', 'Data' 에 나누어 저장되며, 이는 이전 API 에서 사용된 데이타 구조와는 차이가 있다. 새로운 데이타 구조는 시스템이 특정한 주소록 정보(Contact)에 대하여, 다양한 경로로 추가되는 주소 정보(RawContact)를 쉽게 저장하고 관리할 수 있게 해준다. 



  •  Data 테이블은 다양한 형태(Generic)의 정보를 저장할 수 있는 테이블로, Raw Contact 에서 참조하는 모든 데이타를 저장한다. 하나의 행(Row)은 이름, 사진, 이메일 주소, 폰 번호, 혹은 그룹정보와 같은 특정 종류의 데이터를 저장한다. 각각의 행은 자신이 어떤 종류의 데이타를 저장하고 있는지 나타내는 MIME 타입 값을 갖고 있다. 예를들어, 특정 행의 데이타 타입이 Phone.CONTENT_ITEM_TYPE 이라면, 그 행의 첫번째 열(Column)에는 전화번호가 저장되며, Email.CONTENT_ITEM_TYPE 라면 이메일 주소가 저장된다.ContactsContract.CommonDataKinds 클래스는 주소록 정보를 표현하는데 일반적으로 사용되는 MIME 타입들을 서브클래스로 제공해 준다. 또한 개발자가 필요한경우, 추가적인 MIME 타입을 정의할 수 있다. Data 테이블에 대한 보다 상세한 정보와 예제가 필요하면 ContactsContract.Data 항목을 참고하라.

  • Raw Contacts 태이블은 딱 하나의 '주소 정보 소스'(contacts soruce - 사용자 계정) 와 연관된 Data 집합과 해당 인물에 대한 추가적인 정보를 저장한다. '주소 정보 소스' 는 구글 사용자 계정, Exchage 사용자 계정 혹은 페이스북 사용자 계정일 수 있다. (주> 즉, 한명의 친구에 대하여, 해당 친구의 주소 정보를 한 번은 자신의 구글 계정 동기화를 통해 다른 한 번은 동시에 페이스북 계정 정보를 싱크해 저장했다면, 해당 친구의 주소 정보는 두 곳의 '주소 정보 소스' 를 통해 수집 된 것이며, 해당 친구의 주소 정보에 대하여, Raw Contacts 테이블에는 두개의 열이 생성된다.)보다 상세한 정보는 ContactsContract.RawContacts 를 참고 하라.
  • Contacts 테이블은 동일한 인물 (동일한 대상)을 나타내는 하나 혹은 그 이상의 RawContacts 의 집합 정보를 저장한다. 나타낸다. 위에서 언급했듯이, Contacts 컨텐츠프로바이더는 가능한 경우, 자동적으로 동일한 대상을 나타내는 Raw Contacts 를 하나의 Contacts 로 모은다. (예를 들어 이름이나 이메일 주소가 동일한 Raw Contact가 존재할 경우) Contact 에 저장된 정보는 정해진 규칙에 따라, 자동적으로 관리되기 때문에, Contacts 의 정보를 읽는 것은 가능하지만, 임의로 수정할 수는 없다. Raw Contacts 가 서로 비교된 후 하나의 Contact 정보로 결합되는 과정과 이 과정을 컨트롤 할 수 있는  보다 상세한 내용은 '주소록 결합' 항목을 참조해라.

 Contacts 테이블은 여러 '주소 정보 소스' 를 통해 수집된 주소록 정보를 통합하여 제공해 주기 때문에, 사용자에게 주소 정보를 표시하고자 하는 경우, 해당 어플리케이션은 Contacts 테이블의 내용을 참조하여 화면을 구성하는 것이 좋다.

Example: Inserting a Phone Number

 새로운 API 를 이용하여 폰 번호를 추가하기 위해서는, 우선 폰 번호를 저장하기 위해 새로운 Data 행을 생성해야 하고, 추가로 새로운 폰 번호 정보를 덧붙일 Raw Contact 열을 나타내는 ID 값이 필요하다.


import android.provider.ContactsContract.CommonDataKinds.Phone;
...
ContentValues values = new ContentValues();
values
.put(Phone.RAW_CONTACT_ID, rawContactId);
values
.put(Phone.NUMBER, phoneNumber);
values
.put(Phone.TYPE, Phone.TYPE_MOBILE);
Uri uri = getContentResolver().insert(Phone.CONTENT_URI, values);

Aggregation of contacts - 주소록 결합

 사용자가 여러 '주소 정보 소스' 로 부터 주소 정보를 싱크 할 때 수집되는 주소 정보중 몇몇은 세부 내용만 약간 다른, 동일한 사람이나 동일한 대상을 가리킬 수 있다. 예를들어, "Bob Parr" 는 사용자의 직장 동료인 동시에 개인적인 친구일 수 있다. 이 경우, 사용자는 자신의 회사 이메일 계정 과 자신의 개인적안 이메일 계정 양쪽 모두를 통해 동일한 "Bob Parr" 에 대한 아주 약간 다른 주소 정보를 중복해서 다운받을 수 있다. 이러한 경우, 사용자가 보다 편리하게 주소록을 사용할 수 있도록, 안드로이드 시스템은 서로 겹치는 주소 정보(Raw Contact)를 하나로 결합하여, 하나의 주소 집합(Contact)을 만든다. 


 시스템은 기본적으로 자동으로 이러한 일을 수행한다. 하지만, 필요에 따라 개발자는 시스템이 어떤식으로 주소 정보를 결합해야 하는지 컨트롤 하거나, 아예 주소록 결합 자체를 중지 시킬 수 있다. 

Automatic aggregation - 자동 결합

  Raw Contact 가 추가되거나 수정될 때, 시스템은 서로 일치하는 Raw Contact 정보를 찾는다. 만일 시스템이 일치하는 Raw Contact 를 찾지 못할 경우, 오직 하나의 Raw Contact 만을 갖는 '주소 집합(Contact)' 가 생성된다. 만일 일치되는 Raw Contact 가 딱 한쌍일 경우에는 해당 Raw Contact 와 결합하여 두 개의 Raw Contact 를 참조로 갖는 새로운 '주소 집합(Contact)' 를 생성한다. 그리고, 만일 시스템이 유사한 Raw Contact 를 여러개 발견할 경우, 가장 유사한 Raw Contact 와 결합된다. 


 두 개의 Raw Contacts 는 다음의 조건 중 하나 이상을 만족하는 경우 서로 연관관계가 있다고 여겨진다.

  • 이름이 동일하다.
  • 이름에 순서만 다른 동일한 단어가 사용되었다. (예를 들어, "Bob Parr" 와 "Parr, Bob")
  • 이름에 공통적인 짧은 이름이 있다. (예를 들어, "Bob Parr" 와 "Robert Parr")
  • 둘 중 하나의 Raw Contact 는 성 혹은 이름 정보만 있는데 해당 정보가 일치한다. 이 규칙은 오직 두 개의 Raw Contacts 가 추가로 동일한 Data - 전화번호, 이메일 주소, 혹은 별명등 를 공유하고 있을 때만 적용된다. (예를 들어  Helen ["elastigirl"] = Helen Parr ["elastigirl"])
  • Raw Contacts 중 하나는 이름 정보가 없지만, 전화 번호, 이메일 주소 혹은 별명이 동일한다. (예를 들어, Bob Parr[incredible@android.com] = incredible@android.com)

 안드로이드 시스템에서 이름을 비교하는 경우 대소문자나 소리 구분자를 구분하지않는다. (Bob=BOB=bob / Helene=Helene) 또한, 두 전화번호를 비교할 때 "*", "#", "(" , ")" 또는 공백 문자등 특수 문자는 무시된다. 그리고 두개의 전화번호가 국가 코드가 있고 없고를 제외하고 동일하다면,  두 번호가 동일하다가 간주한다. (단, 일본 국가 코드를 갖는 번호를 제외하고)


 '주소 집합(Contact)' 은 영구적이지 않다. Raw Contact 가 변화하는 경우, 새로운 집합이 생성 될 수도 있고 기존 집합이 해체될 수도 있다.

Explicit aggregation - 명시적 결합

 시스템에의해 자동으로 생성된 '주소 집합' 은 어플리케이션 혹은 Sync Adapter 의 요구 사항을 만족시키지 못할 수도 있다. 이런 경우, 개발자가 명식적으로 '주소 결합' 과정을 제어 할 수 있도록 두 가지 방식의 API 가 제공된다. Aggregation Mode 는 개발자가 자동 주소 집합 과정을 컨트롤 훌 수 있게 해준다. Aggregation Exception 은 개발자가 '주소 집합' 을 완전히 다시 작성 할 수 있도록 해준다.


Aggregation modes - 결합 모드


 개발자는 각 개별 Raw Contact 에 대하여, 결합 모드 값를 설정할 수 있다. 모드 값을 설정하기 위해서는 특정 값을 Raw Contact 의 AGGREGATION_MODE 컬럼에 값을 추가 한다. 사용 가능한 상수(Constant) 값은 다음과 같다.

  • AGGREGATION_MODE_DEFAULT — '자동 주소 결합'이 허용된다. 
  • AGGREGATION_MODE_DISABLED — '자동 주소 결합' 이 허용되지 않는다. 이 Raw Contact 는 '주소 집합' 을 이룰 수 없다. 
  • AGGREGATION_MODE_SUSPENDED — '자동 주소 결합' 기능이 비활성화 된다. 만일 현재 Raw Contact 가 이미 특정 '주소 집합'에 소속되어 있었다면, 이 모드를 설정해도 그대로 해당 집합에 남아 있는다. 또한 이 Raw Contact 의 값이 변경되어 집합 내의 다른 Raw Contact 와 일치점이 없어지더라도, 계속 같은 '주소 집합' 으로 남아 있게 된다. 

Aggregation exceptions - 결합 예외


 두 개의 Raw Contacts 를 아무 조건 없이 하나로 묶거나, 두 개로 분리한다. 개발자는 ContactsContact.AggregationException 테이블에 각 Raw Contact 를 나타내는 ID 값과 결합 형태를 나타내는 정보를  추가할 수 있다. 해당 테이블에 추가된 예외 정보는 모든 자동 집합 규칙 중에 항상 최우선으로 적용된다. 


Loookup URI


 새로운 Contacts API 는 특정 '주소 집합(Contact)'을 참조하기 위하여 'Lookup Key' 라는 새로운 개념을 제공한다. 만일 어플리케이션이 특정 '주소 집합' 에 대한 참조를 유지해야 될 경우, 개발자는 전통적인 ID 값 대신, 'Lookup Key' 를 사용해야 한다. 'Lookup Key' 값은 ContactsContract.Contactstable 의 칼럼으로 '주소 집합'을 통해 얻을 수 있다. 개발자는 얻어진 'Lookup Key' 를 이용해서 다음과 같은 방식으로 '주소 집합' 에 접근하기 위한 URI 를 생성 할 수 있다.


Uri lookupUri= Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey)


 그리고, 전통적으로 Content URI 를 사용하는 방식 대로 해당 URI 를 사용 할 수 있다. 


Cursor c = getContentResolver().query(lookupUri, new String[]{Contacts.DISPLAY_NAME}, ...);
try {
    c
.moveToFirst();
   
String displayName = c.getString(0);
} finally {
    c
.close();
}


 이런 복잡한 과정이 필요한 이유는 '주소 집합(Contact)'의 ID 값은 손쉽게 변경 될 수 있기 때문이다. 만일 개발자가 '주소 집합'의 ID 값을 유지하고 있었는데, 사용자가 손 수 해당 '주소 집합' 을 또 다른 '주소 집합' 과 결합 했다고 생각해 보자. 이 경우, 이전에는 서로 다른 '주소 집합' 이 하나의 '주소 집합' 이 되었고, 개발자가 저장하고 있던 ID 값은 아무것도 가르키지 않게 된다. 


 Lookup Key 는 이런 경우에도 계속 해당 '주소 집합(Contact)' 에 접근 할 수 있도록 해준다. Lookup Key 는 Raw Contact 에 대한 서버측 식별자와 연관된 문자열 이다. 때문에, Lookup Key 는 해당 Raw Contact 가 다른 것과 통합되었건 그렇지 않건 '주소 집합(Contact)' 를 찾는데 사용할 수 있다.


 만일, 어플리케이션의 성능을 고려한다면, 개발자는 특정 '주소 집합(Contact)' 을 나타내는 Lookup Key 와 ID 값 모두를 저장해야 한다. 아래와 같이, 두 개의 값을 모두 사용하여 특정 '주소 집합(Contact)' 을 나타내는 URI 를 생성 할 수 있다. 


Uri lookupUri = getLookupUri(contactId, lookupKey)

 시스템에게 Lookup Key 와 ID 가 모두 제공 되면, 시스템은 우선 ID 를 이용하여 '주소 집합(Contact)'를 찾는다. 이 쿼리는 매우 빠르게 이루어진다. 만일 쿼리 결과, 주소를 찾지 못하거나, 발견된 주소가 잘못된 Lookup Key 를 갖고 있으면, Lookup Key 를 이용하여, 연관된 Raw Contact 를 찾은 후, 해당 정보를 기반으로 '주소 집합(Contact)' 을 찾게 된다. 따라서, 만일 개발하고자 하는 어플리케이션이 Contacts Contetns Provider 와 빈번하고 덩치 큰 규모의 작업을 수행한다면,  반드시 Lookup Key 와 ID 모두를 유지해야 한다. 반면에, 사용자 액션에 의해 한번의 하나의 '주소 집합(Contact)' 를 참조하는 정도의 어플리케이션을 구현할 경우에는 ID 값을 저장할 필요한 없을 것이다. 


 안드로이드 내부적으로는 '주소 집합(Contact)' 를 참조할 필요가 있는 경우, Lookup Key 를 사용한다. 예를 들어 특정 주소록에 접근하기 위한 단축키나, Quick Contact 기능, 또는 주소록을 수정하거나, 주소록 정보를 확인 하고자 하는경우에 모두 Lookup Key 를 사용한다. 어째서 주소록 정보를 확인 할 때에도, Lookup Key 를 사용해야 될까? Contact ID 는 사용자가 '주소 집합(Contact)' 를 확인 하고 있는 동안에도 변경 될 수 있다. 예를 들어, '주소록 결합' 작업이 백그라운드에서 일어나고 있고, 특정 '주소 집합(Contact)' 이 동적으로 다른 것과 집합을 이룰 수도 있기 때문이다. 


 즉, 요역하자면, 만일 특정 '주소 집합(Contact)' 에 대한 참조가 필요할 경우, Lookup URI 를 사용할 것을 권장 한다.


Considerations for legacy applications

 만일 이전 버전의 Contacts API 를 사용하는 어플리케이션이 있다면, 새로운 API 를 사용하도록 업그레이드 하는 것을 고려해볼 필요가 있다. 개발자는 다음 네가지 방안 중 하나를 선택할 수 있다. 
  •  어플리케이션을 수정없이 유지하고, Contacts API 호환 모드를 믿는다. 
  •  어플리케이션을 업그레드 하고, 안드로이드 2.0 이전 플랫폼은 지원하지 않는다.
  •  이전 어플리케이션과는 별도로, 새로운 API 를 사용하는 어플리케이션을 제작한다.
  •  하나의 어플리케이션이 플랫폼에 따라 올바른 종류의 API 를 사용하도록 구현한다.

 각각의 옵션에 대하여 한번 살펴보자.

Using compatibility mode - 호환성 모드를 사용

 호환성 모드를 사용하는 것은 가장 쉬운 방법이다. 개발자는 기존 어플리케이션을 수정하지 않고 그냥 두면 된다. 기존 어플리케이션이 Contact 와 관련되어 공개된 API 만을 사용하고 있다면, 안드로이드 2.0 플랫폼 상에서도 정상적으로 작동 할 것이다. 공개된 API 를 사용하지 않는다는 것은 쿼리 상에 명시적으로 특정 Contacts 테이블 이름을 지정했거나, Contacts 클래스에서 제공하는 Public 상수(Constant)를 통하지 않고 직접 특정 컬럼을 사용한 코드등을 뜻한다.

 하지만, 어플리케이션이 동작 하더라도, 개발자는 새로운 Contacts API를 사용할 것을 고려해 보아야 한다. 가장 큰 이유는 이전 API 를 사용한 어플리케이션은, 오직 하나의 계정 - 첫번째 등록된 구글 계정 을 통해 추가된 주소록 정보에만 접근할 수 있기 때문이다. 만일 사용자가 새로운 계정을 추가하거나, 처음 등록된 구글 계정 외에 다른 계정을 사용한다면, 이전 API 를 사용하는 어플리케이셔은 새롭게 추가된 주소록 정보를 접근할 수 없다. 

Upgrading to the new API and dropping support for older platforms - 새로운 API 를 사용하도록 어플리케이션을 업그레이드하고, 이전 버전에 대한 지원을 중단

 만일 개발자가 이전 버전에 대한 지원을 중단하고, 오직 안드로이드 2.0 이상에서만 작동하도록 어플리케이션을 수정하고자 한다면, 다음과 같은 방식으로 새로운 API 를 적용 할 수 있다. 
  •  Contacts 를 사용하는 모든 구문에서 새로운 Contacts API 를 호출하도록 이전 코드를 수정하라. 작업이 성공적으로 완료되었다면, 어플리케이션 빌드 중에, deprecation 경고(더이상 지원하지 않는다고 명시된 API를 호출할때 발생함)를 볼 수 없을 것이다. 새로운 어플리케이션은 하나 이상의 계정을 지원하며, 또한 새롭게 추가된 안드로이드 2.0의 기능들도 활용할 수 있다.
  •  어플리케이션 메니페스트 중에, 'android:minSdkVersion' 속성값을 '5' 이상으로 설정하라. android:minSdkVersion 속성에 대한 보다 상세한 정보는 다<uses-sdk> 에 대한 항목을, minSdkVersion 에 사용되는 값에 관한 보다 상세한 정보는 API Levels 문서를 참조하라.

Maintaining two applications -  두 가지 어플리케이션을 유지

 개발자는 아래와 같은 방식으로 기존 어플리케이션 외에, 안드로이드 2.0 이상 버전을 지원하는 어플리케이션을 추가로 작성할 수도 있다. 
  • 기존 어플리케이션을 똑같이 복제해라.
  • 이전 어플리케이션을 수정해라.
    • 만일 SDK 버전 정보가 5 이상이면 (안드로이드 2.0), 어플리케이션 사용자에게 안드로이드 2.0이상 버전을 위한 새로운 어플리케이션이 존재하고, 해당 어플리케이션을 설치하라는 팝업을 보여준다. 원한다면, 새롭게 마켓에 등록된 어플리케이션으로 이동할 수 있는 Link 를 제공 할 수도 있다. (참조: Using Intents to Launch Market) 
  • 새로운 어플리케이션을 수정해라.
    •  Contacts 를 사용하는 모든 구문에서 새로운 Contacts API 를 호출하도록 이전 코드를 수정하라. 작업이 성공적으로 완료되었다면, 어플리케이션 빌드 중에, deprecation 경고(더이상 지원하지 않는다고 명시된 API를 호출할때 발생함)를 볼 수 없을 것이다. 새로운 어플리케이션은 하나 이상의 계정을 지원하며, 또한 새롭게 추가된 안드로이드 2.0의 기능들도 활용할 수 있다.
    •  어플리케이션 메니페스트 중에, 'android:minSdkVersion' 속성값을 '5' 이상으로 설정하라. android:minSdkVersion 속성에 대한 보다 상세한 정보는 다 <uses-sdk> 에 대한 항목을, minSdkVersion 에 사용되는 값에 관한 보다 상세한 정보는 API Levels 문서를 참조하라.
  • 두 가지 어플리케이션을 모두 안드로이드 마켓에 등록하라. 예전 API 를 사용하는 수정된 어플리케이션은 기존 어플리케이션의 업그레이드 버전으로 등록하고, 새로운 API 어플은 새로운 어플ㄹ리케이션으로 등록하라. 또한, 어플리케이션 설명에 두 가지 유사한 어플리케이션의 차이점에 대해 잘 설명하라.
이러한 방식에는 몇 가지 문제점이 있다. 
  • 어플리케이션의 data 는 동일한 패키지내에서만 접근이 가능하기 때문에, 새로운 버전의 어플리케이션은 이전 어플리케이션의 data 를 읽을 수 없다.  따라서, Database 나 Shared Preference 등의 정보들이 새롭게 생성되어야 한다.
  • 새로운 API 를 사용하는 어플리케이션으로 업그레이드 하는 과정이 너무 불편한다. 어떤 사용자들은 그냥 예전 어플리케이션을 사용하거나, 아예 어플리케이션 자체를 삭제해 버릴 수도 있다.

Supporting the old and new APIs in the same application - 
동일한 어플리케이션에서 두 가지 방식의 API 를 모두 지원

 몇 가지 기법을 활용하여, 개발자는 어떤 플랫폼에서도 작동하는 하나의 패키지를 만들 수 있다. 이 방법은 구현상에 어려움이 있지만, 노력을 드릴 가치가 있다. 

우선, 기존의 어플리케이션을 검토한 후, Contacts 를 접근 하는데 사용한 API 들을 뽑아내 ContactAccessorOldApi 같은 하나의 클래스에 모아라. 예를들어 아래와 같은 코드가 있다면.

    protected void pickContact() {
        startActivityForResult
(new Intent(Intent.ACTION_PICK, People.CONTENT_URI), 0);
   
}
다음과 같이 변경 될 수 있다.

    private final ContactAccessorOldApi mContactAccessor = new ContactAccessorOldApi();

   
void pickContact() {
        startActivityForResult
(mContactAccessor.getContactPickerIntent(), 0);
   
}
 이에 대응하는 ContactAccessorOldApi 는 다음과 같다.
    public Intent getContactPickerIntent() {
       
return new Intent(Intent.ACTION_PICK, People.CONTENT_URI);
   
}

 이 작업이 완료되면, deprecation 경고는 오직 새롭게 추가된 API Wrapper 클래스인 ContactAccessorOldApi 내에서만 발생해야 한다. 그 다음엔, 새로운 Abstract 클래스 ContactAccessor 를 작성하라. 이 Abstract Class 는 ContactAccessorOldApi 에서 사용되는 모든 메서드 형식을 포함하고 있어야 한다. 그리고, Contact AccessorOldApi 가 ContactAccessor 를 상속 받도록 해라.

    public abstract class ContactAccessor {
       
public abstract Intent getContactPickerIntent();
       
...
   
}
 새로운 확장 클래스 ContactAccessorNewApi 를 추가 하라. 그리고 해당 클래스가 새로운 Contacts API 를 이용하도록 모든 API 들을 구현해라.

    public class ContactAccessorNewApi extends ContactAccessor {    
       
@Override
       
public Intent getContactPickerIntent() {
           
return new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
       
}
       
...
   
}
 이 시점에서, 개발자는 동일한 API 에 대하여, 이전 Contacts API 를 버전 과 새로운 Contacts API 를 사용하는 버전, 두 가지 버전의 구현 클래스를 갖는다. 이제, 이것을을 잘 활용해 보자. 다음의 코드를 ContactAccessor 클래스에 추가해라.

    private static ContactsAccessor sInstance;

   
public static ContactAccessor getInstance() {
       
if (sInstance == null) {
           
String className;
           
int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
           
if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
                className
= "ContactAccessorOldApi";
           
} else {
                className
= "ContactAccessorNewApi";
           
}
           
try {
               
Class<? extends ContactAccessor> clazz =
                       
Class.forName(ContactAccessor.class.getPackage() + "." + className)
                               
.asSubclass(ContactAccessor.class);
                sInstance
= clazz.newInstance();
           
} catch (Exception e) {
               
throw new IllegalStateException(e);
           
}
       
}
       
return sInstance;
 그리고, ContactsAccessorOldApi 에 대한 참조는 모두 ContacstAccessor 에 대한 참조로 교체해라.

    private final ContactAccessor mContactAccessor = ContactAccessor.getInstance();
  작업이 모두 완료되었다! 이 어플리케이션을 안드로이드 2.0, 1.6, 1.5 버전에서 테스트 해보라.

 우리는 개발자들이 안드로이드 2.0 에서 새롭게 추가된 Contacts 기능을 좋아하기를 희망하고, 새로운 API 를 이용한 멋진 어플리케이션이 개발될 날을 눈빠지게 기다리고 있다.