Android/정리2010. 4. 14. 16:52

Android2.0(API level5)부터 멀티어카운트와 다른 데이터소스로부터 다루고 통합하는 개선된 Contacts API 제공한다. 여러소스들로부터 오버랩되는 데이터를 다루기 위해 contacts content provider 유사한 contacts들을 모은다 그리고 사용자에게 하나의 엔티티로 제공한다. 문서는 contacts 다루기 위한 새로운 방법에 대해 기술한다.

새로운 Contacts API android.provider.ContactsContract 이와 관련된 클래스들로 정의된다. 예전 API 여전히 지원되나 사용이 권장되지는 않는다. 이전 API 작성된 애플리케이션을 가지고 있다면 Considerations for legacy apps문서를 참조하라.

새로운 API 예전 API 모두 사용하는 예제를 참조하고 싶다면 Business Card sample application 참조하라.

Data structure of Contacts

새로운 API에서 data 3개의 primary table 나뉜다. Contacts, raw contacts그리고 data 구성되며 이는 여러 개의 contacts소스로부터 정보를 다루고 저장하기 좋은 구조로 만들어져 있다.

Data raw contact 연관된 모든 종류의 데이터가 저장된 일반적인 테이블이다. row 해당종류의 데이터가 저장된다(ex: name, photo, email address, phone number, group membership). row 데이터의 종류를 파악하기 위한 MIME type으로 tag된다. Column 그것이 가지고 있는 타입이 row 저장되어있는 데이터의 종류에 따라  결정된다. ) 특정 row data종류가 Phone.CONTENT_ITEM_TYPE이라면, 첫번째 column 전화번호를 가지게 되고, data종류가 Email.CONTENT_ITEM_TYPE이라면 column에는 email주소가 저장된다.

ContactsContract.CommonDataKinds클랙스는 contacts data 해당하는 MIME type들에 대응되는 서브클래스들을 제공한다. 필요하다면 data row 해당하는 새로운 MIME type 정의하여 사용이 가능하다. 많은 정보를 원하면  android.provider.ContactsContract.Data 보라.

RawContacts table 하나의 row Data 집합과 인물에 대한 설명과 하나의 contacts source 연결된 정보를 표현한다. 예를 들면, 하나의 row Google이나 Exchange 계정 또는 facebook 친구와 연관된 정보를 정의한다. 많은 정보는 ContactsContract.RawContacts 살펴보라.

Contacts테이블내의 하나의 row 동일한 인물(또는 엔티티) 설명하는 하나이상의 합쳐진 RawContacts 표현한다.

위에서 언급한대로 Contact content provider 자동으로 Raw Contacts 합쳐서 하나의 Contact 엔트리에 넣는다. 취합로직이 Contacts row내의 엔트리들을 다루기 때문에 엔트리들은 읽을수 있지만 수정되어선 안된다. 아래 Aggregation of contacts 섹션을 보면 어떻게 취합이 이루어지는지에 대한 많은 정보를 얻을 있다.

것은 통합되어 제공되므로 사용자에게 contacts 보여질 하부의 다양한 소스로부터 contact들의 뷰가 취합되어진다. 이는 Contacts level에서 이루어진다.(When displaying contacts to users, applications should typically operate on the Contacts level, since it provides a unified, aggregated view of contacts from various underlying sources)

Example: Inserting a Phone Number

전화번호를 새로운 API 사용해서 추가하려면, 전화번호를 추가할 Raw Contact ID 필요할것이다. Data row 생성할 필요가 생긴다 :

 

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(contacts 취합)

사용자가 여러 개의 소스로부터 contacts 동기화할 , 일부 contact들은 동일인물이나 엔티티에 해당하는 경우가 있다. 예를 들어 “Bob Parr” 사용자의 동료이거나 친구일수 있다. 따라서 사용자는 그의 회사 이메일계정과 개인계정정보 두가지를 모두 contact 정보로 갖기를 원할것이다. 사용자에게 단순화된 뷰를 제공하려면 시스템은 겹쳐지는 contact들을 배치하고, 하나로 합치고 취합한다.

시스템은 디폴트로 자동으로 contact들을 취합한다. 하지만 필요한 경우 당신의 애플리케이션은 시스템이 취합하는 방법에 관여하거나 모두함께 취합되지 않도록 할수 있다.

Automatic aggregation

Raw contact 추가되거나 수정되면, 시스템은 취합되어질 매치되는(겹쳐지는)raw contact들을 찾는다. 하나도 매치되는 raw contact들을 찾지 못할 수도 있다. 때는 새로운 raw contact 생성될것이다. 하나가 검색되었다면 두개의 raw contact 갖는 하나의 Contact 새로 생성된다. 그리고 여러 개의 raw contact 검색되었다면 가장 매치가 잘되는 raw contact 선택하게 된다.

2개의 raw contact 아래의 요구 적어도 하나이상 일치하면 매치된것으로 간주된다.

  • 이름이 같은경우
  • 이름이 동일한 단어로 순서만 다른 경우(ex, “Bob Parr” “Parr, Bob”)
  • 공통된 short name 갖는 경우(ex, “Bob Parr” “Robert Parr”)
  • First name last name 하나만 갖고 있으며 다른 raw contact 동일한 경우, 룰은 느슨한편이다. 그래서 두개이 raw contact 폰번호, 이메일주소 또는 애칭을 공유하는 경우에만 적용된다.(ex, Hellen [“elastigirl]”=Helen Parr[“elastigirl”])
  • 둘중 하나가 name정보가 모두 없는 상태에서 전화번호, 이메일주소, 또는 애칭을 공유하고 있는 경우(ex, Bob Parr [incredible@android.com] = incredible@android.com)

 

이름을 비교할 시스템은 대소문자와 음성구분마크(diacritical mark)(Hélène=Helene) 구분하지 않는다. 전화번호를 구분할 “*”, “#”, “(“, “), “ “ 특수문자는 비교하지 않는다. 또한 두개의 번호가 국가코드만 다르고 나머지는 동일하다면 두가지를 동일한 번호로 인식한다(일본 국가코드만 제외됨)

자동 취합은 영속적이지 않으며 하나의 구성하는 raw contact 변경이 새로운 취합을 생성하기도하고 존재하는 취합을 중단시킬 있다.(Automatic aggregation is not permanent; any change of a constituent raw contact may create a new aggregate or break up an existing one.)

Explicit aggregation(명시적 취합)

어떤 경우에는 시스템의 자동 취합기능이 당신의 애플리케이션이나 sync adapter 요구사항에 부합되지 않는경우가 있을 있다. 그래서 명시적으로 취합을 제어할 있는 두가지 종류의 API set 있다. Aggregation mode(취합모드) 자동취합행위자체를 제어할수 있으며 aggregation exception 자동화된 취합자체를 오버라이드할수 있다.

Aggregation modes(취합모드)

당신을 raw contact 각각에 대해 aggregation mode 설정할 있다. 이렇게 하기 위해서 모드상수값을 RawContact row column으로 추가한다. 모드상수는 아래와 같이 정의되어 있다:

·         AGGREGATION_MODE_DEFAULT 일반모드, 자동취합기능 동작.

·         AGGREGATION_MODE_DISABLED 자동취합기능 꺼짐. raw contact 취합되지 않는다.

·         AGGREGATION_MODE_SUSPENDED 자동취합기능이 비활성화된다. 취합모드가 suspene 변경될 이미 취합중인 상태였다면, 취합중인 내용은 계속 유지된다.

 

Aggregation exceptions(취합 예외)

두개의 raw contact 무조건 합치거나 떨어뜨려 놓으려면, ContactsContract.AggregationException(contact2.db agg_exceptions테이블명으로 있슴)테이블에 row 하나 추가할 있다. 여기에 추가된 예외는 모든 자동 취합룰에 대해 오버라이드된다.

 

Loookup URI

새로운 Contacts API contact 검색하기 위해 lookup key 개념을 소개한다. 당신의 애플리케이션이 contact 참조를 관리할 필요가 있다면 전통적인 row id대신에 lookup key 사용해야 한다. 당신은 contact로부터 lookup key 가져올 있다. 이는 ContactsContract.Contacts 테이블에 하나의 칼럼(contact2.db 경우 “lookup”필드명으로 있슴)으로 있다. Lookup key 가져오면 아래의 방법으로 UIR 구성할 있다.

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

그리고 아래와 같이 전통적인 방법처럼 URI 사용할 있다.

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

이렇게 복잡해진 이유는 contact row ID 근본적으로 volatile하기 때문이다. 당신의 애플리케이션이 아주 ID 가진 contact 저장했다고 해보자, 그러면 사용자는 수동으로 해당 contact 다른 contact join한다. 이제 개로 사용되는 하나의 contact 있다. 그리고 ID 저장된 contact 아무데도 없다. <- 이게 먼소린지???

The reason for this complication is that regular contact row IDs are inherently volatile. Let's say your app stored a long ID of a contact. Then the user goes and manually joins the contact with some other contact. Now there is a single contact where there used to be two, and the stored long contact ID points nowhere.

Lookup key 이런 케이스의 해결책을 제시한다. 키값은 raw contacts들의 server-side id값들을 이어붙인 문자열이다. 당신의 애플리케이션에서는 임의 contact 찾기 위해 문자열을 사용할 있다. 이는 해당 raw contact 취합여부에 상관없이 사용이 가능하다.

만일 당신의 애플리케이션은 성능을 중시한다면 lookup long ID 모두 저장할 있으며 ID값을 벗어난 lookup URI값을 아래와 같이 만들어낼 있다.If performance is a concern for your application, you might want to store both the lookup and the long ID of a contact and construct a lookup URI out of both IDs, as shown here:

Uri lookupUri = getLookupUri(contactId, lookupKey)

URI 두개의 ID 모두 표현되어 있다면, 시스템은 long ID 먼저 사용한다. 그렇게 하면 아주 빠른 쿼리가 가능하다. Contact 검색되지 않거나 검색된 정보가 잘못된 lookup key 가지고 있으면, content provider lookup key 파싱하고 생성된 raw contact들을 추적(?)한다. 만일 애플리케이션이 contacts bulk-process 한다면, ID 모두 다루어야 하고 반대로 사용자 액션당 하나의 contact 대한 작업을 한다면, long ID 저장할 필요가 없을 것이다.

안드로이드는 스스로 contact 참조할 (shortcut, Quuick Contact, contact 또는 수정중인 경우) 생기면 lookup URI 사용한다.

contact 단순히 보고 있는중에 contact ID 변경 되는것인가? 그것은 백그라운드로 동기화가 진행중이거나 화면에 보여지는 중에 다른 항목과 취합될수 있기 때문이다.

 

정리하면 contact 참조할 필요가 생기면 무조건 lookup URI 사용할것을 추천하는 바이다.

 

 

Considerations for legacy applications(이전 애플리케이션에 대한 고려)

이전 API 사용하는 애플리케이션이 있다면 새로운 API 업그레이드를 고려해야 한다. 가지 방법이 있다:

·         내버려 두고 Contacts 호환성모드에 기대는 .

·         업그레이드를 진행하는 , 그리고 Android2.0 이전 플랫폼에 대한 지원을 중단하는 .

·         이전 버전을 계속 유지한 상태에서 버전을 작성하는

·         배포된 platform 맞는 올바른 API 사용한 어플을 만드는

·          

Using compatibility mode

호환성 모드는 가장 쉬운 방법이다. 그리고 public API만을 사용하는 안드로이드 2.0에서도 동작해야 한다. Non-public API 사용하는 예제는 내부 쿼리들에서 명시적인 테이블명을 사용하고 컬럼을 사용한다그것은 Contacts class public상수로 정의되어 있지 않다.

이런 애플리케이션이 현재도 사용중임에도 불구하고 이렇게 계속 내버려두고 싶지은 않을것이다. 왜냐하면 오직 하나의 계정에만 접근이 가능하기 때문이다. 그것도 처음 지정된 구글계정에만. 만일 사용자가 다른 계정을 열거나 다른 구글계정으로 변경한다면 이런 애플리케이션들은 계정에 접근할 수가 없게 된다.

 

Upgrading to the new API and dropping support for older platforms

애플리케이션이 Android 2.0보다 오래된 플랫폼을 이상 지원하지 않는다면 아래와 같은 방법으로 업그레이드 가능하다.

l  API Contacts 모든 사용처를 변경한다. 모두 마친 후에는 애플리케이션 빌드중에 발생하는 모든 사용되지 않음 경고는 무시한다. 애플리케이션은 다중계정과 Android 2.0 다른 특성들의 장점을 모두 지원하게 될것이다.

l  Manifest파일에 <uses-s> 엘리먼트에 android:minSdkVersion속성을 업데이트한다. API 사용하려면 API level ‘5’이상을 설정해야 한다.

 

Maintaining two applications

Android 2.0이전과 이후 버전을 모두 지원하는 애플리케이션을 만들고자 한다면, 다음과 같은 작업을 해야 한다.

l  기존 어플 복제

l  이전 어플 수정

n  어플 적재시 SDK버전을 확인해야 . 버전정보는 android.os.Build.VERSION.SDK. 값을 확인하면 .

n  버전이 맞지 않으면 마켓으로 새로운 어플을 설치할 있도록 팝업을 띄워주는 것이 좋다.(see Using Intents to Launch Market).

l  어플 수정

n  API 호출부분 모두 수정

n  AndroidManifest.xml수정

u  애플리케이션이름과 패키지이름을 새로 부여하라. 현재 안드로이드 마켓은 동일 이름/패키지로 두개의 어플리케이션 설치를 지원하지 않는다.

u  android:minSdkVersion 수정

l  두개의 어플을 모두 마켓에 발표한다. 하나는 업그레이드버전이고 하나는 새로운 버전이다. 간략하게 차이점에 대한 설명을 달아야 한다.

 

방법은 두가지 단점이 있다.

u  어플은 이전 어플의 데이터를 읽을수 없다. 어플의 데이터는 동일한 패키지에 해당하는 코드에서만 접근 가능하다.

u  업그레이드 과정이 아주 사용자에게 투박하다. 일부 사용자는 두개를 사용하거나 모두 삭제할수도 있다.

 

Supporting the old and new APIs in the same application

이것은 아주 사소한 트릭이지만 노력에 비해 결과는 훌룡하다. 하나의 패키지에 모든 플랫폼에서 동작하게 있다.

기존 어플의 모든 Contacts 사용하는 코드를 모아서 하나의 클래스로 만들어라. 예를 들면 아래와 같이 하면된다.

ContactAccessor라는 abstract class 만들고 이를 확장하여 old API 억세스하는 class new API 억세스하는 class 만든다. 그러고 SdkVersion 따라 적절한 class 생성하여 미리정의된 interface method 호출하여 사용하도록 한다.

기존 코드는 아래와 같다.

    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 getContactPickerIntent()이다.

    public Intent getContactPickerIntent() {
       
return new Intent(Intent.ACTION_PICK, People.CONTENT_URI);
   
}

이제 ContactAccessor 정의하고 이를 확장하여 Old API용과 New API 클래스를 정의한다.

    public abstract class ContactAccessor {
       
public abstract Intent getContactPickerIntent();
       
...
   
}

 

    public class ContactAccessorNewApi extends ContactAccessor {    
       
@Override
       
public Intent getContactPickerIntent() {
           
return new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
       
}
       
...
   
}

ContactAccessor에서 SdkVersion 따라 분기하여 적절한 class 리턴하는 singletone 함수를 만든다.

    private static ContactAccessor 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;
   
}

이제 아래와 같이 ContactAccessor 생성하여 SdkVersion 상관없이 사용할 있는 객체를 완성하였다.

    private final ContactAccessor mContactAccessor = ContactAccessor.getInstance();

 

Posted by 삼스