Android/App개발2010. 4. 22. 15:05
Android 2.0에서부터 SyncAdapter를 통해 Contact정보를 다른 소스로부터 동기화를 지원한다.
Example을 하나 제공하는데 이 샘플의 테스트 방법이 developer.android.com에 정확하게 설명이 되어 있지 않다.

아래는 구글담당자의 테스트방법에 대한 메일내용이다.

Hi, 

I have uploaded the zipped source files for this sample at: 
http://code.google.com/p/apps-for-android/downloads/list 

Sorry about the server related python source files not being visible 
on developer.android.com. We will fix it soon. 
This sample source will be available with the next sdk package, we are 
working on making zipped samples downloadable from 
developer.android.com itself in future. 

Regarding password issue, please use account user1/test on 
samplesyncadapter server. 

I would recommend that to add new accounts, download the server code, 
modify it as you want and host it on a separate app engine instance. 
If you look at dashboard.py and app.yaml in server code it should be 
pretty clear how to do this..giving a short overview below: 
1) Add a new Sample SyncAdapter account @: 
http://samplesyncadapter.appspot.com/add_user 
For example I have added user1: 
http://samplesyncadapter.appspot.com/edit_user?user=1 
2) Enter username/password @ 
http://samplesyncadapter.appspot.com/add_credentials 
The password for user1 is test. 
3) Modify the android code to use your server instance. 

Sorry for the confusion so far, please let me know if you have more 
questions. 

Thanks, 
Megha 

On Feb 18, 10:28 am, HCH <hayeshau...@gmail.com> wrote: 


위와 같이 수행하여 계정을 추가한 후 추가한 계정의 Friends를 등록하면 Sync시 추가한 Friends가 Contacts에 동기되어 표시되는것을 확인할 수 있다. 이 때 Friends를 추가할 때 다른 계정의 Handle명을 입력해야 함을 주의해야 한다.


Posted by 삼스
Android/정리2010. 4. 21. 11:52

효율적인 모바일앱의 작성은 항상 쉽지 않다. 특히, 안드로이드처럼 Dalvik garbage collector 의해 자동으로 메모리가 관리되는 경우 충분히 주의하지 않으면 성능이슈가 발생할 수밖에 없다

성능에 민감한 코드들(layout생성, view시스템의 그리기, 게임의 로직부분)은 필연적으로 비용을 초래한다. 너무 많은 메모리 할당후에는 당신의 앱은 메모리를 해재하도록 잠시 멈출수 있다. 대부분의 경우 사용자가 느끼지 못할정도로 gc는 이루어지는 반면에 리스트를 scroll하거나 게임에서 적을 물리치거나 하는 경우 심각한 성능과 응답성의 저하를 느낄수 있다. 100~200ms정도 gc시간이 걸리는 것은 일반적인 상황은 아니다. 부드러운 애니매이션의 경우 각 프레임이 16~33ms정도의 속도가 필요하다. 초당 10프레임정도로만 떨어져도 사용자는 문제를 느끼게 된다.

대개의 경우 gc는 tons of small, short-lived object, 일부 garbage collector때문에 발생한다. 그리고 그런 object의 collection을 최적화할 수 있으며 그렇게 해서 애플리케이션의 좀더 덜 방해받게 할 수 있다. 안드로이드의 garbage collector는 불행히도 이러한 성능최적화등을 할 수 없다. 

잦은 gc를 피해가도록 해주기 위해, 안드로이드 SDK는 allocation tracker라는 툴을 포함시켰다. DDMS의 일부기능으로 되어 있으며 디버깅목적으로 이미 사용하고 있다. 이 툴을 사용하려면 SDK에 포함되어 있는 DDMS를 먼저 실행한다. 이클립스버전의 DDMS에는 아직 지원되지 않는다.

DDMS가 실행되면 해당 애플리케이션 프로세스를 선택하고 Allocation Tracker탭을 클릭하라. 새로운 뷰에서 Start Tracking을 클릭하면 분석하고자 하는 애플리케이션을 선택한다. 준비가 되었다면 Get Allocations를 선택하가. 할당된 object들이 첫번째 테이블에 리스트될것이다. 보여지는 한 line을 선택하면 두번째 테이블에 애플리케이션에 할당된 stack이 보여진다. 어떤 타입의 object들이 할당되었는지는 물론이고 포함된 스래드, 클래스, 파일의 라인까지 표시된다. 아래 스크린샷은 Shelves앱의 listView를 스크롤하는 동안의 할당상태를 보여준다.

성능에 영향을 주는 부분을 모두 제거할 필요가 없다고 하더라도 코드에서 중요한 이슈를 확인하는데 도움이 될것이다.  예를 들면 내가 보아온 많은 애플리케이션에서 draw시마다 매번 Paint 객체를 새로 생성한다. 이 Paint객체를 하나의 인스탄스로 관리하는 것 만으로 성능이슈를 간단히 개선할 수 있다. 나는 Android source code를 살펴볼것을 강력하게 권고한다. 이유는 성능을 끌어올리기 위해 어떤 기술을 사용하였는지 볼수 있기 때문이다. 그리고 객체의 재사용에 대한 안드로이드가 제공하는 API들을 발견하게 될것이다.


Posted by 삼스
Android/App개발2010. 4. 19. 16:39
볼륨키로 조절시 어떤때는 ringer볼륨, 어떤때는 music볼륨등 다르게 조절되는것을 볼수있다.
이 때 이값은 어떻게 결정될까?

AudioService의 getActiveStreamType(int suggestedStreamType)함수에 의해 요구한 볼륨조절값을 참조하여 현재 시스템의 상태에 따른 볼륨조절 믹서 종류를 구분하여 준다.

    private int getActiveStreamType(int suggestedStreamType) {
        boolean isOffhook = false;
        try {
            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
            if (phone != null) isOffhook = phone.isOffhook();
        } catch (RemoteException e) {
            Log.w(TAG, "Couldn't connect to phone service", e);
        }

        if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) {
            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
            return AudioSystem.STREAM_BLUETOOTH_SCO;
        } else if (isOffhook) {
            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
            return AudioSystem.STREAM_VOICE_CALL;
        } else if (AudioSystem.isMusicActive()) {
            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
            return AudioSystem.STREAM_MUSIC;
        } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
            // Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING...");
            return AudioSystem.STREAM_RING;
        } else {
            // Log.v(TAG, "getActiveStreamType: Returning suggested type " + suggestedStreamType);
            return suggestedStreamType;
        }
    }

함수의 구현내용을 살펴보면...
BT사용중이면 BT볼륨  -> Call중이면 VoiceCall볼륨 -> 음악재생중이면 Music 볼륨 -> 디폴트는 Ringer볼륨 의 순서로 결정된다.




Posted by 삼스
Android/App개발2010. 4. 19. 11:51
emulator에서는 phone과 notificatin ringtone이 하나도 뜨지 않아서 테스트를 할수가 없다.
이 때 Sdcard와 db파일을 일부조정하면 테스트가 가능하다.

1. emulator실행
2. DDMS에서 file explorer에서 scard폴더에 추가하고자 하는 ringtone파일들(여기서는 ogg파일들을 사용함)을 집어넣는다.
3. /data/data/com.android.providers.media/databases/external-xxxx.db파일을 꺼내온다.
4. 꺼낸 external-xxxx.db파일을 sqlitespy프로그램으로 아래 그림과 같이 audio_meta테이블에서 is_ringtone과 is_notification값을 1로 변경한다. is_alarm을 1로 하면 알람톤으로도 사용이 가능할것이다.
5. 변경한 external-xxxx.db파일을 emulator에 다시 집어 넣는다.
6. emulator를 다시 시작한다.


위의 순서로 하면 아래와 같이 링톤이 뜨게 된다.




Posted by 삼스
Android2010. 4. 17. 23:59
안드로이드펍에서 진행하는 앱개발자를 위한 컨퍼런스입니다.

T-story개발자 등록자는 할인되네요.

관심있는 분들은 유료지만 들을만 해 보이네요~!

링크 -> http://www.onoffmix.com/e/socialnmobile/1453

Posted by 삼스
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 삼스
Android/정리2010. 4. 13. 13:31

Window Backgrounds & UI Speed

일부 안드로이드 어플리케이션들은 UI toolkit 뛰어넘는 성능을 요구되며 그렇게 하기 위한 가지 방법이 있다. 문서에서 당신은 그리기와 액티비티가 화면에 보여지는 시간을 빠르게 하는 방법을 발견하게 될것이다. 두가지 기술은 모두 윈도우의 백그라운드 그리기(window’s background drawable) 특성에 기인한다.

Window background 다소 오해하게 하는 용어이다. 당신이 setContentView() Activity에서 호출하여 UI 설정하면 안드로이드는 Activity 윈도우에 당신의 뷰를 추가한다. 하지만 윈도우에는 당신이 추가한 뷰만 있는 것이 아니다. 여기서 가장 중요한 하나는 뷰구조의 가장 상위에는 DecorView 있다는 것이다.

A typical Android view hierarchy

DecorView 실제로 윈도우의 background drawable 가지고 있는 뷰이다. 당신의 액티비티에서 getWindow().setBackgroundDrawable() 호출함으로써 DecorView background drawable 변경하게 된다. 이는 현재 구현된 안드로이드에 아주 국한된 내용이며 향후 버전이나 디바이스에서 변경될수 있다.

당신이 표준안드로이드 테마를 사용한다면 디폴트 background drawable 당신의 액티비티에 셋팅된다. T-Mobile G1에서 현재 ColorDrawable 사용되고 있다. 대부분의 애플리케이션에서 이미지는 동작하고 남겨진다. 이는 반대로 당신의 어플리케이션에 그리기 성능에 영향을 줄수 있다. 항상 전체화면에 불투명한 그림을 그리는 예제가 있다.

An opaque user interface doesn't need a window background 

그림은 ImageView 의해 커버되는 윈도우의 배경이 없는 스크린샷이다. 애플리케이션은 최대한 빠르게 다시그리기를 하도록 구현되었다. 44 fps 속도로 그려지고 있다. 이와 같이 속도를 개선하기 위한 쉬운방법은 background drawable 제거하는것이다. UI 불투명하기 때문에 배경을 그리는 것은 낭비적이다. 따라서 배경의 제거는 성능향상에 효과적이다.

Remove the background for faster drawing

새버전의 애플리케이션은 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애플리케이션이 좋은 예제가 된다.

Textured backgrounds are good candidates for window's background

애플리케이션시 나무느낌배경을 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.

 

Posted by 삼스
Android/App개발2010. 4. 13. 10:44

Command Line Shell For SQLite

The SQLite library includes a simple command-line utility named sqlite3 (or sqlite3.exe on windows) that allows the user to manually enter and execute SQL commands against an SQLite database. This document provides a brief introduction on how to use the sqlite3 program.

Getting Started

To start the sqlite3 program, just type "sqlite3" followed by the name the file that holds the SQLite database. If the file does not exist, a new one is created automatically. The sqlite3 program will then prompt you to enter SQL. Type in SQL statements (terminated by a semicolon), press "Enter" and the SQL will be executed.

For example, to create a new SQLite database named "ex1" with a single table named "tbl1", you might do this:

$ sqlite3 ex1
SQLite version 3.6.11
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table tbl1(one varchar(10), two smallint);
sqlite> insert into tbl1 values('hello!',10);
sqlite> insert into tbl1 values('goodbye', 20);
sqlite> select * from tbl1;
hello!|10
goodbye|20
sqlite>

You can terminate the sqlite3 program by typing your systems End-Of-File character (usually a Control-D). Use the interrupt character (usually a Control-C) to stop a long-running SQL statement.

Make sure you type a semicolon at the end of each SQL command! The sqlite3 program looks for a semicolon to know when your SQL command is complete. If you omit the semicolon, sqlite3 will give you a continuation prompt and wait for you to enter more text to be added to the current SQL command. This feature allows you to enter SQL commands that span multiple lines. For example:

sqlite> CREATE TABLE tbl2 (
   ...>   f1 varchar(30) primary key,
   ...>   f2 text,
   ...>   f3 real
   ...> );
sqlite>

Aside: Querying the SQLITE_MASTER table

The database schema in an SQLite database is stored in a special table named "sqlite_master". You can execute "SELECT" statements against the special sqlite_master table just like any other table in an SQLite database. For example:

$ sqlite3 ex1
SQLite vresion 3.6.11
Enter ".help" for instructions
sqlite> select * from sqlite_master;
    type = table
    name = tbl1
tbl_name = tbl1
rootpage = 3
     sql = create table tbl1(one varchar(10), two smallint)
sqlite>

But you cannot execute DROP TABLE, UPDATE, INSERT or DELETE against the sqlite_master table. The sqlite_master table is updated automatically as you create or drop tables and indices from the database. You can not make manual changes to the sqlite_master table.

The schema for TEMPORARY tables is not stored in the "sqlite_master" table since TEMPORARY tables are not visible to applications other than the application that created the table. The schema for TEMPORARY tables is stored in another special table named "sqlite_temp_master". The "sqlite_temp_master" table is temporary itself.

Special commands to sqlite3

Most of the time, sqlite3 just reads lines of input and passes them on to the SQLite library for execution. But if an input line begins with a dot ("."), then that line is intercepted and interpreted by the sqlite3 program itself. These "dot commands" are typically used to change the output format of queries, or to execute certain prepackaged query statements.

For a listing of the available dot commands, you can enter ".help" at any time. For example:

sqlite> .help
.backup ?DB? FILE      Backup DB (default "main") to FILE
.bail ON|OFF           Stop after hitting an error.  Default OFF
.databases             List names and files of attached databases
.dump ?TABLE? ...      Dump the database in an SQL text format
.echo ON|OFF           Turn command echo on or off
.exit                  Exit this program
.explain ON|OFF        Turn output mode suitable for EXPLAIN on or off.
.genfkey ?OPTIONS?     Options are:
                         --no-drop: Do not drop old fkey triggers.
                         --ignore-errors: Ignore tables with fkey errors
                         --exec: Execute generated SQL immediately
                       See file tool/genfkey.README in the source 
                       distribution for further information.
.header(s) ON|OFF      Turn display of headers on or off
.help                  Show this message
.import FILE TABLE     Import data from FILE into TABLE
.indices TABLE         Show names of all indices on TABLE
.iotrace FILE          Enable I/O diagnostic logging to FILE
.load FILE ?ENTRY?     Load an extension library
.mode MODE ?TABLE?     Set output mode where MODE is one of:
                         csv      Comma-separated values
                         column   Left-aligned columns.  (See .width)
                         html     HTML <table> code
                         insert   SQL insert statements for TABLE
                         line     One value per line
                         list     Values delimited by .separator string
                         tabs     Tab-separated values
                         tcl      TCL list elements
.nullvalue STRING      Print STRING in place of NULL values
.output FILENAME       Send output to FILENAME
.output stdout         Send output to the screen
.prompt MAIN CONTINUE  Replace the standard prompts
.quit                  Exit this program
.read FILENAME         Execute SQL in FILENAME
.restore ?DB? FILE     Restore content of DB (default "main") from FILE
.schema ?TABLE?        Show the CREATE statements
.separator STRING      Change separator used by output mode and .import
.show                  Show the current values for various settings
.tables ?PATTERN?      List names of tables matching a LIKE pattern
.timeout MS            Try opening locked tables for MS milliseconds
.timer ON|OFF          Turn the CPU timer measurement on or off
.width NUM NUM ...     Set column widths for "column" mode
sqlite>

Changing Output Formats

The sqlite3 program is able to show the results of a query in eight different formats: "csv", "column", "html", "insert", "line", "list", "tabs", and "tcl". You can use the ".mode" dot command to switch between these output formats.

The default output mode is "list". In list mode, each record of a query result is written on one line of output and each column within that record is separated by a specific separator string. The default separator is a pipe symbol ("|"). List mode is especially useful when you are going to send the output of a query to another program (such as AWK) for additional processing.

sqlite> .mode list
sqlite> select * from tbl1;
hello|10
goodbye|20
sqlite>

You can use the ".separator" dot command to change the separator for list mode. For example, to change the separator to a comma and a space, you could do this:

sqlite> .separator ", "
sqlite> select * from tbl1;
hello, 10
goodbye, 20
sqlite>

In "line" mode, each column in a row of the database is shown on a line by itself. Each line consists of the column name, an equal sign and the column data. Successive records are separated by a blank line. Here is an example of line mode output:

sqlite> .mode line
sqlite> select * from tbl1;
one = hello
two = 10

one = goodbye
two = 20
sqlite>

In column mode, each record is shown on a separate line with the data aligned in columns. For example:

sqlite> .mode column
sqlite> select * from tbl1;
one         two       
----------  ----------
hello       10        
goodbye     20        
sqlite>

By default, each column is at least 10 characters wide. Data that is too wide to fit in a column is truncated. You can adjust the column widths using the ".width" command. Like this:

sqlite> .width 12 6
sqlite> select * from tbl1;
one           two   
------------  ------
hello         10    
goodbye       20    
sqlite>

The ".width" command in the example above sets the width of the first column to 12 and the width of the second column to 6. All other column widths were unaltered. You can gives as many arguments to ".width" as necessary to specify the widths of as many columns as are in your query results.

If you specify a column a width of 0, then the column width is automatically adjusted to be the maximum of three numbers: 10, the width of the header, and the width of the first row of data. This makes the column width self-adjusting. The default width setting for every column is this auto-adjusting 0 value.

The column labels that appear on the first two lines of output can be turned on and off using the ".header" dot command. In the examples above, the column labels are on. To turn them off you could do this:

sqlite> .header off
sqlite> select * from tbl1;
hello         10    
goodbye       20    
sqlite>

Another useful output mode is "insert". In insert mode, the output is formatted to look like SQL INSERT statements. You can use insert mode to generate text that can later be used to input data into a different database.

When specifying insert mode, you have to give an extra argument which is the name of the table to be inserted into. For example:

sqlite> .mode insert new_table
sqlite> select * from tbl1;
INSERT INTO 'new_table' VALUES('hello',10);
INSERT INTO 'new_table' VALUES('goodbye',20);
sqlite>

The last output mode is "html". In this mode, sqlite3 writes the results of the query as an XHTML table. The beginning <TABLE> and the ending </TABLE> are not written, but all of the intervening <TR>s, <TH>s, and <TD>s are. The html output mode is envisioned as being useful for CGI.

Writing results to a file

By default, sqlite3 sends query results to standard output. You can change this using the ".output" command. Just put the name of an output file as an argument to the .output command and all subsequent query results will be written to that file. Use ".output stdout" to begin writing to standard output again. For example:

sqlite> .mode list
sqlite> .separator |
sqlite> .output test_file_1.txt
sqlite> select * from tbl1;
sqlite> .exit
$ cat test_file_1.txt
hello|10
goodbye|20
$

Querying the database schema

The sqlite3 program provides several convenience commands that are useful for looking at the schema of the database. There is nothing that these commands do that cannot be done by some other means. These commands are provided purely as a shortcut.

For example, to see a list of the tables in the database, you can enter ".tables".

sqlite> .tables
tbl1
tbl2
sqlite>

The ".tables" command is similar to setting list mode then executing the following query:

SELECT name FROM sqlite_master 
WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%'
UNION ALL 
SELECT name FROM sqlite_temp_master 
WHERE type IN ('table','view') 
ORDER BY 1

In fact, if you look at the source code to the sqlite3 program (found in the source tree in the file src/shell.c) you'll find exactly the above query.

The ".indices" command works in a similar way to list all of the indices for a particular table. The ".indices" command takes a single argument which is the name of the table for which the indices are desired. Last, but not least, is the ".schema" command. With no arguments, the ".schema" command shows the original CREATE TABLE and CREATE INDEX statements that were used to build the current database. If you give the name of a table to ".schema", it shows the original CREATE statement used to make that table and all if its indices. We have:

sqlite> .schema
create table tbl1(one varchar(10), two smallint)
CREATE TABLE tbl2 (
  f1 varchar(30) primary key,
  f2 text,
  f3 real
)
sqlite> .schema tbl2
CREATE TABLE tbl2 (
  f1 varchar(30) primary key,
  f2 text,
  f3 real
)
sqlite>

The ".schema" command accomplishes the same thing as setting list mode, then entering the following query:

SELECT sql FROM 
   (SELECT * FROM sqlite_master UNION ALL
    SELECT * FROM sqlite_temp_master)
WHERE type!='meta'
ORDER BY tbl_name, type DESC, name

Or, if you give an argument to ".schema" because you only want the schema for a single table, the query looks like this:

SELECT sql FROM
   (SELECT * FROM sqlite_master UNION ALL
    SELECT * FROM sqlite_temp_master)
WHERE type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'
ORDER BY substr(type,2,1), name

You can supply an argument to the .schema command. If you do, the query looks like this:

SELECT sql FROM
   (SELECT * FROM sqlite_master UNION ALL
    SELECT * FROM sqlite_temp_master)
WHERE tbl_name LIKE '%s'
  AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'
ORDER BY substr(type,2,1), name

The "%s" in the query is replace by your argument. This allows you to view the schema for some subset of the database.

sqlite> .schema %abc%

Along these same lines, the ".table" command also accepts a pattern as its first argument. If you give an argument to the .table command, a "%" is both appended and prepended and a LIKE clause is added to the query. This allows you to list only those tables that match a particular pattern.

The ".databases" command shows a list of all databases open in the current connection. There will always be at least 2. The first one is "main", the original database opened. The second is "temp", the database used for temporary tables. There may be additional databases listed for databases attached using the ATTACH statement. The first output column is the name the database is attached with, and the second column is the filename of the external file.

sqlite> .databases

Converting An Entire Database To An ASCII Text File

Use the ".dump" command to convert the entire contents of a database into a single ASCII text file. This file can be converted back into a database by piping it back into sqlite3.

A good way to make an archival copy of a database is this:

$ echo '.dump' | sqlite3 ex1 | gzip -c >ex1.dump.gz

This generates a file named ex1.dump.gz that contains everything you need to reconstruct the database at a later time, or on another machine. To reconstruct the database, just type:

$ zcat ex1.dump.gz | sqlite3 ex2

The text format is pure SQL so you can also use the .dump command to export an SQLite database into other popular SQL database engines. Like this:

$ createdb ex2
$ sqlite3 ex1 .dump | psql ex2

Other Dot Commands

The ".explain" dot command can be used to set the output mode to "column" and to set the column widths to values that are reasonable for looking at the output of an EXPLAIN command. The EXPLAIN command is an SQLite-specific SQL extension that is useful for debugging. If any regular SQL is prefaced by EXPLAIN, then the SQL command is parsed and analyzed but is not executed. Instead, the sequence of virtual machine instructions that would have been used to execute the SQL command are returned like a query result. For example:

sqlite> .explain
sqlite> explain delete from tbl1 where two<20;
addr  opcode        p1     p2     p3          
----  ------------  -----  -----  -------------------------------------   
0     ListOpen      0      0                  
1     Open          0      1      tbl1        
2     Next          0      9                  
3     Field         0      1                  
4     Integer       20     0                  
5     Ge            0      2                  
6     Key           0      0                  
7     ListWrite     0      0                  
8     Goto          0      2                  
9     Noop          0      0                  
10    ListRewind    0      0                  
11    ListRead      0      14                 
12    Delete        0      0                  
13    Goto          0      11                 
14    ListClose     0      0

The ".timeout" command sets the amount of time that the sqlite3 program will wait for locks to clear on files it is trying to access before returning an error. The default value of the timeout is zero so that an error is returned immediately if any needed database table or index is locked.

And finally, we mention the ".exit" command which causes the sqlite3 program to exit.

Using sqlite3 in a shell script

One way to use sqlite3 in a shell script is to use "echo" or "cat" to generate a sequence of commands in a file, then invoke sqlite3 while redirecting input from the generated command file. This works fine and is appropriate in many circumstances. But as an added convenience, sqlite3 allows a single SQL command to be entered on the command line as a second argument after the database name. When the sqlite3 program is launched with two arguments, the second argument is passed to the SQLite library for processing, the query results are printed on standard output in list mode, and the program exits. This mechanism is designed to make sqlite3 easy to use in conjunction with programs like "awk". For example:

$ sqlite3 ex1 'select * from tbl1' |
>  awk '{printf "<tr><td>%s<td>%s\n",$1,$2 }'
<tr><td>hello<td>10
<tr><td>goodbye<td>20
$

Ending shell commands

SQLite commands are normally terminated by a semicolon. In the shell you can also use the word "GO" (case-insensitive) or a slash character "/" on a line by itself to end a command. These are used by SQL Server and Oracle, respectively. These won't work insqlite3_exec(), because the shell translates these into a semicolon before passing them to that function.

Compiling the sqlite3 program from sources

The source code to the sqlite3 command line interface is in a single file named "shell.c" which you can download from the SQLite website. Compile this file (together with the sqlite3 library source code to generate the executable. For example:

gcc -o sqlite3 shell.c sqlite3.c -ldl -lpthread

Posted by 삼스
Android/정리2010. 4. 10. 08:09

Avoiding Memory Leaks

Android app들은 적어도 T-Mobile G1에서 16MB heap size제약이 있다. 이것은 phone을 위해서는 많은 양이면서 개발자에게는 아주 작은양이다. 대부분의 메모리를 사용할 생각이 아니더라도 다른 app들을 죽이지 않을 정도로 가능한 아주 작은 양의 메모리를 사용해야 한다. 더 많은 app들이 메모리에 유지되고 그들의 app들간에 전환이 일어날것이다. 내작업의 일부로써 메모리누수이슈가 속출할것이며 대부분의 시간을 이런 실수(하나의 Context에 오랬동안 레퍼런스를 유지하는것)때문에 보내게 될것이다.

Android에서 Context는 resource를 load하고 access하는것을 제외한 많은 작업에 이용된다. 이것은 모든 widget
들이 생성자에서 Context 파라메터를 인자로 받는 이유이기도 하다. 일반적인 Android app에서 Activity와
Application이라는 2개의 Context를 가진다. 이것은 보통 class와 method에 전달하는 첫번째 파라메터이다.

@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
 
 
TextView label= new TextView(this);
  label
.setText("Leaks are bad");
 
  setContentView
(label);
}

위코드는 view가 전체액티비티에 하나의 레퍼런스를 가지고 있고 액티비티의 모든게 그안에 유지된다는것을 의미한다(모든 뷰구조와 그 리소스들), 그러므로 만일 Context가 누수(누수는 레퍼런스를 하나 유지하여 GC를 방지하는것을 의미한다)가 발생하면 당신은 많은 양의 메모리를 잃게된다. 전체 액티비티를 읽는것은 당신이 주의하지 않으면 아주 쉽게 일어날수 있다.

디폴트로 화면이 전환되면 현재 액티비티는 제거되고 그 상태가 저장된 상태로 새로운 액티비티가 다시 생성된다. 이런 방식으로 안드로이드는 리소스들을 다시 로드하여 UI를 다시 구성한다. 당신이 로테이션시마다 다시 로드되지 않기를 원하는 아주 큰 비트맵을 사용하는 어플리케이션을 작성한다고 상상해보라.  아주 쉬운 방법은 static field를 사용하는 것이다.

private static Drawable sBackground;
 
@Override
protected void onCreate(Bundle state) {
 
super.onCreate(state);
 
 
TextView label= new TextView(this);
  label
.setText("Leaks are bad");
 
 
if (sBackground== null) {
    sBackground
= getDrawable(R.drawable.large_bitmap);
 
}
  label
.setBackgroundDrawable(sBackground);
 
  setContentView
(label);
}

이 코드는 아주 빠르지만 또한 아주 잘못된 코드이다. 처음 액티비티가 생성되어 처음 로테이션될때 leak이 발생된다. Drawable이 뷰에 첨부될때 view는 drawable에 callback으로 설정된다. 위 코드상에서 drawable은 TextView의 참조를 가지고 있다. TextView는 액티비티의 참조를 가지고 있다.

이 예제는 Context의 누수의 아주 단순한 케이스중 하나이며 Home screen소스상에서(unbindDrawables()메소드를 찾아보라) 저장된 drawable의 callback에서 액티비티가 제거될때 null로 초기화하는 것을 볼수 있다.  충분히 흥미진진하고 누수된 context의 연결고리를 생성할수 있는 여러 군데가 있으며 그것은 좋지 않은 방법이다. 그것들은 당신을 오히려 더 빠르게 메모리부족을 야기시킨다.

2가지의 아주 쉬운 context와 연관된 메모리누수를 피하는 방법이 있다. 가장 명백한 방법의 하나는 그 Context자신의 영역의 외부로 빠져나가는것을 완전히 피하는것이다. 위 코드는 static을 사용하는 경우를 보여주었다. 하지만 내부 class나 그것의 내부참조를 외부의 클래스에 참조하도록 하는것은 똑같이 아주 위험하다.
또한가지 방법은 Application context를 사용하는것이다. 이는 액티비티 생명주기와 관계없이 당신의 application이 살아있는동안 항상 유지된다. 만일 아주 오랬동안 유지되어야 하는 객체를 사용해야 한다면 applicaiton객체를 기억하라. Application context는 Context.getApplicationContext()나 Activity.getApplication()을 통해 아주 쉽게 얻을수 있다.

Context연관 메모리누수를 피하는 방법을 정리하겠다. 아래 사항을 기억하라.

  • 오랬동안 유지되어야 하는 레퍼런스는 Activity context에 유지하지 말아라(그것들은 액티비티와 동일한 생명주기를 갖게 된다.) - Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Application context를 사용할것을 시도하라 - Try using the context-application instead of a context-activity
  • 액티비티의 생명주기를 관리하지 않는다면 액티비티내에 non-static inner class를 사용하는것을 피하라. static inner class를 사용해야 하며 액티비티내에서 weak reference를 사용하라.  Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
  • GC는 메모리 누수에 대해 아무런 보장을 하지 않는다. - A garbage collector is not an insurance against memory leaks

Posted by 삼스
Android/App개발2010. 3. 18. 12:07
http://android-developers.blogspot.com/2010/02/live-wallpapers.html

Live wallpapers

Android 2.1에서 소개된 libe wallpaper로 사용자는 이제 더 풍부하고, 애니매이션가능하고 더 인터랙티브한 그들만의 홈스크린을 즐길수 있게 되었다. live wallpaper는 일반 Android application과 아주 유사하고 플랫폼의 모든 자산에 접근이 가능하다: SGL(2D drawing), OpenGL(3D drawing), GPS, accelerometers, network access, .... Nexus One에 들어가 있는 live wallpaper는 이런 API들을 이용하여 더 재미있고 흥미있는 UX의 예를 보여준다. 예를 들어 Grass wallpaper는 폰의 위치를 가지고 해가뜰때와 질때는 구분하여 하늘 이미지를 적절히 표시한다.


자신만의 live wallpaper를 만드는것은 쉽고, SurfaceView와 Canvas를 다루어보았다면 특히 더 쉽다. 작성방법을 배우려면 Android 2.1SDK에서 제공하는 CubeLiveWallpaper예제를 받아야 한다(android-2.1/samples/CubeLiveWallpaper에 있다)

Live wallpaper는 일반적인 Android service와 유사하다. 한가지 다른점은 onCreateEngine()이라는 메서드가 추가된것이다. 이 메서드는 WallpaperService.Engine을 생성하기 위해서 있다. 이 엔진은 wallpaper의 lifecycle을 관리하고 그리는것에 대한 처리를 담당한다. 시스템은 그릴수 있는 surface를 제공한다. wallpaper그리기는 아주 비용이 비싸기 때문에 가능한 CPU자원을 많이 사용하지 않도록 피해서 코드를 최적화해야 한다. 이는 wallpaper의 lifecycle관리가 중요한 이유이기도 한다. wallpaper가 화면에서 보여지지 않을때 wallpaper는 관련된 모든 activity를 stop해야 한다.

엔진은 또한 사용자나 Home app와 상호작용하기 위해 몇가지 메서드를 구현할 수 있다. 사용자가 다른 home screen으로 이동하기 위해 scroll하기를 원한다면 onOffsetChanged()를 사용할 수 있다. touch event를 처리하고자 한다면 onTouchEvent(MotionEvent)를 구현할 수 있다. 마지막으로 app들이 live wallpaper에 명령을 보낼수 있다. 현재는 표준 home app만이 live wallpaper의 onCommand()로 명령을 보낼수 있다 :

  • android.wallpaper.tap : 사용자가 워크스페이스상의 비어있는 곳을 tap할 때 사용됨. Water live wallpaper에서 사용자가 터치한 부분에서 새로운 물줄기가 생성되도록 구현할때 사용되었다.
  • android.home.drop : 사용자가 워크스페이스상에 ICON이나 Widget을 drop할때 사용됨.

Live wallpaper는 Android 2.1의 기능이다. 당신이 만든 live wallpaper를 다운로드하여 잘 동작할것을 보장하기 위해서는 manifest에 아래와 같이 기술해야 한다는 것을 기억하라 :

  • <uses-sdk android:minSdkVersion="7" />, Android 2.1에서 동작하도록 설정.
  • <uses-feature android:name="android.software.live_wallpaper" />, live wallpaper임을 설정

많은 live wallpaper들이 미리 마켓에 많으니 사용해보삼.

Posted by 삼스