T-story개발자 등록자는 할인되네요.
관심있는 분들은 유료지만 들을만 해 보이네요~!
링크 -> http://www.onoffmix.com/e/socialnmobile/1453
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가 아래의 요구 중 적어도 하나이상 일치하면 매치된것으로 간주된다.
이름을 비교할 시 시스템은 대소문자와 음성구분마크(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();
Window Backgrounds & UI Speed
일부 안드로이드 어플리케이션들은 UI toolkit을 뛰어넘는 성능을 요구되며 그렇게 하기 위한 몇 가지 방법이 있다. 이 문서에서 당신은 그리기와 액티비티가 화면에 보여지는 시간을 빠르게 하는 방법을 발견하게 될것이다. 이 두가지 기술은 모두 윈도우의 백그라운드 그리기(window’s
background drawable) 특성에 기인한다.
Window background는 다소 오해하게 하는 용어이다. 당신이 setContentView()를 Activity에서 호출하여 UI를 설정하면 안드로이드는 Activity의 윈도우에 당신의 뷰를 추가한다. 하지만 그 윈도우에는 당신이 추가한 뷰만 있는 것이 아니다. 여기서 가장 중요한 것 하나는 뷰구조의 가장 상위에는 DecorView가 있다는 것이다.
DecorView는 실제로 윈도우의 background drawable을 가지고 있는 뷰이다. 당신의 액티비티에서 getWindow().setBackgroundDrawable()을 호출함으로써 DecorView의 background drawable을 변경하게 된다. 이는 현재 구현된 안드로이드에 아주 국한된 내용이며 향후 버전이나 디바이스에서 변경될수 있다.
당신이 표준안드로이드 테마를 사용한다면 디폴트 background
drawable이 당신의 액티비티에 셋팅된다. T-Mobile G1에서 현재 ColorDrawable이 사용되고 있다. 대부분의 애플리케이션에서 이 이미지는 잘 동작하고 남겨진다. 이는 반대로 당신의 어플리케이션에 그리기 성능에 영향을 줄수 있다. 항상 전체화면에 불투명한 그림을 그리는 예제가 있다.
위 그림은 ImageView에 의해 커버되는 윈도우의 배경이 없는 스크린샷이다. 이 애플리케이션은 최대한 빠르게 다시그리기를 하도록 구현되었다. 44 fps의 속도로 그려지고 있다. 이와 같이 속도를 개선하기 위한 쉬운방법은 background
drawable을 제거하는것이다. UI가 불투명하기 때문에 배경을 그리는 것은 낭비적이다. 따라서 배경의 제거는 성능향상에 효과적이다.
새버전의 애플리케이션은 51fps까지 향상되었다. 더 개선된 차이는 T-Mobile G1의 메모리버스의 속도 향상으로 쉽게 설명된다.
커스텀테마를 사용하여 배경을 제거하는 것을 쉽게 할 수 있다. 먼저 res/values/themes.xml를 만들고 아래와 같이 내용을 추가하라.
<resources>
<style name="Theme.NoBackground" parent="android:Theme">
<item name="android:windowBackground">@null</item>
</style>
</resources>
그러고 난 후 android:theme=”@style/Theme.NoBackground”속성을 당신의
<activity/>나 <application/>테그에 추가하면 된다. 이 크릭은 MapView,
WebView등 fullscreen을 사용하는 어플에서 많이 사용된다.
불투명 뷰와 안드로이드 : 이 최적화방식은 안드로이드 UI툴킷이 현재 불투명한 자식뷰들에 의해 숨겨진 뷰의 그리기에 대해 충분히 스마트하지 않기 때문에 필요하다. 이런 최적화가 구현되지 않은 주된 이유는 간단하다 안드로이드 애플리케이션내에서 불투명한 뷰는 아주 조금 사용되기 때문이다.
윈도우 배경을 변경하기 위해 테마를 사용하는 것은 액티비티의 시작을 인지하는 성능을 개선하기 위한 최고의 방법이다. 이 트릭은 커스텀 백그라운드(texture나 logo)를 사용하는 액티비티에만 적용이 가능하다. Shelves애플리케이션이 좋은 예제가 된다.
이 애플리케이션시 나무느낌배경을 XML이나 onCreate에서 설정하면 사용자는 어두운 배경이나 디폴트테마를 가지고 시작될것이다. 나무느낌배경은 콘텐트뷰의 inflation이후에 표시될것이며, 첫번째 layout/drawing이 수행될것이다. 이것은 부작용을 야기하고 애플리케이션이 로드되는데 시간이 걸린다는 느낌을 받게 될것이다.
반면에 애플리케이션이 테마에 나무느낌배경을 정의하면 애플리케이션이 시작되자마자 시스템에 의해 로드된다. 사용자는 디폴트테마를 절대로 볼수 없으며 애플리케이션이 로드중이라는 느낌도 받지 않을것이다. 메모리와 디스크의 사용을 제한하기 위해
res/drawable/background_shelf.xml에 정의되어 연결된다:
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/shelf_panel"
android:tileMode="repeat" />
이 drawable은 테마에서 참조된다.
<resources>
<style name="Theme.Shelves" parent="android:Theme">
<item name="android:windowBackground">@drawable/background_shelf</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>
동일한 트릭이 T-Mobile G1에 탑재된 Google Map애플리케이션에도 사용되었다. 애플리케이션이 적재되면 MapView의 tile이 로딩되는 것을 바로 볼수 있다.
때때로 최고의 트릭은 또한 최고로 단순한것이다. 따라서 당신이 불투명한 UI나 커스텀배경을 사용하는 액티비티를 만들어야 할 때 윈도우배경을 변경해야 함을 기억하라.
Download the source code of the
first example.
Download the source code of
Shelves.