2010. 7. 26. 07:23

[번역] 안드로이드 2.0 의 Service API 변경


안드로이드 2.0 Service API 변경

 

http://android-developers.blogspot.com/2010/02/service-api-changes-starting-with.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+blogspot/hsDu+(Android+Developers+Blog)&utm_content=Google+Reader

 

번역: SSKK (http://codemuri.tistory.com/)

 

2010 2 11일 오후 3 30분 경 Dianne Hackborn 에 의해 포스팅 됨

 

안드로이드 2.0 Service API 변경

 

작년에 안드로이드 플랫폼은 Service API 에서의 수많은 문제 영역 외에 서비스를 동작시키는 방법에 대해서도 많은 이슈들이 나타나게 되었다. 그 결과로, 안드로이드 2.0 은 개발자와 사용자 모두를 위해 이 영역에 대한 많은 변경과 개선 사항을 소개하였다.

 

알고 있어야 할 세가지 주요 변경은:

 

l  Service.setForeground() 는 이제 폐기되었고(deprecated) 2.0 에서는 아무것도 하지 않는다.

l  서비스 생명 주기에 있어 아주 쉽게 서비스가 우연히 실행된 채로 남아 있도록 만드는 많은 위험한 케이스가 존재하였다. 2.0 의 새로운 API 는 이것을 좀 더 쉽게 처리할 수 있도록 해준다.

l  안드로이드 2.0 은 또한 최종 사용자에게 그들의 장치에서 실행중인 서비스를 모니터하고 관리하기 위한 새로운 UI 를 소개한다.

 

서비스의 백그라운드

 

2.0 에 대해 상세하게 다루기 전에, 서비스에 관한 빠른 요약을 먼저 살펴보는 것이 유용할 것이다. 안드로이드의 서비스 API 는 애플리케이션이 백그라운드에서 작업을 할 수도 있도록 하는 핵심 매커니즘 중의 하나이다. 안드로이드가 설계된 방식에 따라, 애플리케이션이 일단 사용자에게 더 이상 보여지지 않게 되면, 그 애플리케이션은 보통 소모용으로 간주되거나 만일 메모리가 더 요구되는 경우 시스템에 의해서 종료될 수 있는 후보가 된다.

 

애플리케이션이 이것을 처리하는 주요 방식은 서비스 컴포넌트를 시작하는 것이다. 서비스 컴포넌트는 어떤 가치있는 일을 하고 있으므로 정말 필요하지 않는 한 자신의 프로세스를 종료하지 말 것을 명시적으로 시스템에게 말한다.

 

이것은 매우 강력한 기능이지만 강력함으로 인해 약간의 책임이 뒤따른다. 활발히 실행중인 서비스는 실행 가능한 다른 것들(사용자가 다시 그 프로세스를 방문할 때 초기화 될 필요가 없는 활동하지 않는 백그라운드 프로세스들을 포함하여) 과는 구별된 리소스를 가지고 있다. 그리하여 개발자가 서비스를 디자인 할 때 서비스가 정말 필요할 때만 실행하도록 하고 우연히 서비스가 오랫동안 남아서 실행이 되도록 하는 버그를 피하는 것에 신경 쓰는 것이 중요하다.

 

Service.setForeground() 를 다시 디자인하기

 

안드로이드 1.6 의 마지막 안정화 기간 동안에, 애플리케이션이 그렇게 해서는 안될 때에도 Services.setForeground() 를 사용하는 많은 애플리케이션으로 인한 이슈를 좀더 살펴보기 시작했다. API 는 많이 알리지 않았다 왜냐하면 이것이 대부분의 애플리케이션에서는 사용되어서는 안되며 시스템이 매우 곤란해(hard) 질 수 있기 때문이다: API 는 서비스의 프로세스를 foreground 로 다루어지도록 요청한다, 근본적으로 그 프로세스를 종료시킬 수 없게 되고 그리하여 메모리 빈곤 상태에서 시스템이 회복하는 것을 더 어렵게 만든다.

 

그 당시 1.6 에서는 이에 대한 어떤 중요한 변경을 가하는 것이 너무 늦었었다 하지만 2.0 에서는 그렇게 했다. Service.setForeground() 는 이제 아무것도 하지 않는다. API 는 항상 서비스가 사용자에게 진행중인 공지사항(ongoing notification)을 보이면서 처리해야 할 어떤 것이 있다는 것을 의도하는 것이었다; foreground 에 있다는 것을 말함으로써 사용자는 어떤 방식으로 서비스가 실행 중이라는 것을 알게 되고 그 서비스를 중지하는 방법을 알리도록 하는 것이다. 그리하여 옛 API 대신에 안드로이드 2.0 은 공지가 foreground 에서 보이도록 하는 두 가지 새로운 API 를 소개한다.

 

    public final void startForeground(int id, Notification notification);
   
public final void stopForeground(boolean removeNotification);

 

이것은 또한 서비스와 함께 공지 상태를 관리하는 것을 우연히 더 쉽게 만드는 것이 아니다, 왜냐하면 시스템은 이제 서비스가 foreground 에 존재하는 동안 항상 공지 있다는 것을 보증할 수 있기 때문이다, 그리고 서비스가 사라질 때마다 그 공지도 사라진다.

 

많은 개발자들은 2.0 이후 버전 뿐만 아니라 더 오래된 플랫폼에서도 동작하는 서비스를 작성하길 원할 것이다; 이것은 선택적으로 새로운 API 가 이용가능 할 때 그 API 를 호출하는 아래 코드처럼 사용함으로써 이루어 질 수 있다.

 

    private static final Class[] mStartForegroundSignature = new Class[] {
       
int.class, Notification.class};
   
private static final Class[] mStopForegroundSignature = new Class[] {
       
boolean.class};
   
   
private NotificationManager mNM;
   
private Method mStartForeground;
   
private Method mStopForeground;
   
private Object[] mStartForegroundArgs = new Object[2];
   
private Object[] mStopForegroundArgs = new Object[1];
   
   
@Override
   
public void onCreate() {
        mNM
= (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
       
try {
            mStartForeground
= getClass().getMethod("startForeground",
                    mStartForegroundSignature
);
            mStopForeground
= getClass().getMethod("stopForeground",
                    mStopForegroundSignature
);
       
} catch (NoSuchMethodException e) {
           
// Running on an older platform.
            mStartForeground
= mStopForeground = null;
       
}
   
}

   
/**
     * This is a wrapper around the new startForeground method, using the older
     * APIs if it is not available.
     */

   
void startForegroundCompat(int id, Notification notification) {
       
// If we have the new startForeground API, then use it.
       
if (mStartForeground != null) {
            mStartForegroundArgs
[0] = Integer.valueOf(id);
            mStartForegroundArgs
[1] = notification;
           
try {
                mStartForeground
.invoke(this, mStartForegroundArgs);
           
} catch (InvocationTargetException e) {
               
// Should not happen.
               
Log.w("MyApp", "Unable to invoke startForeground", e);
           
} catch (IllegalAccessException e) {
               
// Should not happen.
               
Log.w("MyApp", "Unable to invoke startForeground", e);
           
}
           
return;
       
}
       
       
// Fall back on the old API.
        setForeground
(true);
        mNM
.notify(id, notification);
   
}
   
   
/**
     * This is a wrapper around the new stopForeground method, using the older
     * APIs if it is not available.
     */

   
void stopForegroundCompat(int id) {
       
// If we have the new stopForeground API, then use it.
       
if (mStopForeground != null) {
            mStopForegroundArgs
[0] = Boolean.TRUE;
           
try {
                mStopForeground
.invoke(this, mStopForegroundArgs);
           
} catch (InvocationTargetException e) {
               
// Should not happen.
               
Log.w("MyApp", "Unable to invoke stopForeground", e);
           
} catch (IllegalAccessException e) {
               
// Should not happen.
               
Log.w("MyApp", "Unable to invoke stopForeground", e);
           
}
           
return;
       
}
       
       
// Fall back on the old API.  Note to cancel BEFORE changing the
       
// foreground state, since we could be killed at that point.
        mNM
.cancel(id);
        setForeground
(false);
   
}

 

서비스 생명주기 변경

 

1.6 에서 부적절하게 자신을 foreground 로 만드는 서비스를 무시함으로 인해 갈수록 늘어나는 또 다른 상황을 보고 있었다, 많은 장치들이 이용가능 한 메모리를 얻기 위해 백그라운드에서 서로 다투고 있는 수많은 서비스를 가지게 되었다.

 

이 문제는 실행되어야 하는 것보다 많이 실행하고 있는 서비스들이거나 장치 상에서 처리되기를 시도하는 너무 많은 것들이 존재하기 때문이다. 하지만, 우리는 또한 서비스와 플랫폼 사이의 사용호작용 내에서도 많은 이슈들을 발견 하였다, 이 이슈들은 서비스가 올바른 일을 수행하려고 시도할 때 조차 어려움 없이 애플리케이션이 서비스가 실행 중인 채로 남게 만들었다. 일반적인 시나리오를 살펴 보자.

 

1.     애플리케이션은 startService() 를 호출한다.

2.     이 서비스는 onCreate(), onStart() 을 획득하고 그리고 나서 어떤 작업을 처리할 백그라운드 쓰레드를 생성한다.

3.     시스템은 메모리가 꽉 차게 되었고, 그래서 현재 실행중인 서비스를 종료시켜야 한다

4.     메모리가 해제된 이후에, 그 서비스는 다시 시작되고, onCreate() 가 호출된다 하지만 onStart() 는 호출되지 않는다 왜냐하면 새로운 인텐트 명령을 가지고 startService() 에 대한 다른 호출이 없기 때문이다.

 

이제 이 서비스는 어떤 작업을 하고 있는 지를 깨닫지 못한 채 그리고 언제 자신을 멈추어야 하는 지를 모른 채, 생성된 채로 남아 있을 것이다.

 

이것을 해결하기 위해, 안드로이드 2.0에서는 Service.onStart() 이 폐기되었다. (이전 버전의 플랫폼에서 사용되었기 때문에 비록 여전히 존재하고 동작하지만). 이 함수는 시스템이 서비스를 관리하기 위한 더 좋은 제어를 제공하기 위해 새로운 Service.onStartCommand() 콜백으로 대체 되었다. 여기에서의 핵심은 이 함수에 의해 반환되는 새로운 결과 코드에 있다. 이 결과 코드는 만일 서비스 프로세스가 실행 중에 종료된다면 시스템이 그 서비스를 어떻게 해야 하는 지를 알려준다.

 

l  START_STICKY 는 기본적으로 이전과 동일하게 동작하도록 한다, 서비스가 시작된채로 남아 있다면 나중에 시스템에 의해 재 시작될 것 이다. 이전 버전의 플랫폼과의 유일한 차이점은 만일 프로세스가 종료되어 재시작 된다면, 아예 호출되지 않는 것이 아니라 onStartCommand() null 인텐트를 가지고 다음 서비스의 인스턴스에서 호출될 것이다. 이러한 모드를 사용하는 서비스는 항상 이러한 케이스를 체크해야 하고 올바르게 이것을 처리해야 한다.

l  START_NOT_STICKY onStartCreated() 가 반환된 이후에, 만약 전달할 어떤 시작 명령(start command)도 남아 있지 않은 채 프로세스가 종료된다면, 그 서비스는 재시작 되는 대신 중지될 것이다. 이것은 서비스에 전송된 명령어를 실행하는 동안만 수행하길 원하는 서비스의 경우에 좀더 많이 의미가 통한다. 예를 들어, 서비스가 어떤 네트워크 상태를 조사하기 위해 알람으로부터 매 15분마다 시작된다고 하자. 만일 그 작업을 하는 중에 종료된다면, 그 서비스를 그냥 중지된 채로 놔두고 다음 알람 시 시작되는 것이 최선일 것이다.

l  START_REDELIVER_INTENT 는 만약 서비스의 프로세스가 해당 인텐트에서 stopSelf() 가 호출되기 전에 종료된다면, 그 인텐트가 완료될 때까지 서비스에 재배달 되는 것을 제외하면 START_NOT_STICKY 와 같다. (만일 몇번의 시도에도 여전히 완료할 수 없다면, 특정 시점에 시스템은 포기한다). 이것은 처리해야 할 명령어를 받아서 전송된 각 명령을 반드시 완료하도록 하기 위한 서비스에 유용하다.

 

현존하는 애플리케이션과의 호환성 때문에, 이전 버전의 플랫폼에서 개발된 애플리케이션의 디폴트 반환값은 null 인텐트를 가지고 onStart() 를 호출하지 않는 이전 동작을 제공하는 특수한 START_STICKY_COMPATIBILITY 코드이다. 만약 API 버전 5 이상에서 개발한다면, 디폴트 값은 START_STICKY 이고 null 인텐트를 가지고 호출되는 onStart() 또는 onStartCommand() 를 처리할 준비가 되어 있어야 한다.

 

플랫폼에 따라 이전 그리고 새로운 API를 모두 사용하는 서비스 역시 쉽게 작성할 수있다. 해야할 모든 것은 아래 코드를 가지고 2.0 SDK 에서 컴파일 하는 것이다.

 

    // This is the old onStart method that will be called on the pre-2.0
   
// platform.  On 2.0 or later we override onStartCommand() so this
   
// method will not be called.
   
@Override
   
public void onStart(Intent intent, int startId) {
        handleStart
(intent, startId);
   
}

   
@Override
   
public int onStartCommand(Intent intent, int flags, int startId) {
        handleStart
(intent, startId);
       
return START_NOT_STICKY;
   
}

   
void handleStart(Intent intent, int startId) {
       
// do work
   
}

 

새로운 실행중인 서비스사용자 인터페이스

 

언급할 마지막 이슈는 한 장치에서 사용가능한 많은 메모리 내에서 너무 많은 서비스가 실행되고 있다는 점이다. 이것은 설치된 애플리케이션의 버그 또는 설계 결함 때문이거나 아니면 사용자가 너무 많은 것을 처리하려고 하는 것 때문이다. 사용자는 시스템에서 현재 무슨 일이 일어나고 있는지를 볼 수 없었다, 그러나 서비스의 사용이 사용자 경험에 점점 더 많은 영향을 끼치게 되므로 이 것을 노출하는 것이 중요하게 되었다.

 

이것을 지원하기 위해, 안드로이드 2.0 은 애플리케이션 시스템 설정에서 이용가능한 새로운 실행중인 서비스액티비티를 소개한다. 이것이 불려질 때, 다음과 같이 보인다.

 


 

메인 콘텐츠는 사용자가 관심을 가지는, 실행중인 프로세스에 의해 정렬된, 모든 실행중인 서비스의 목록이다. 여기 예에서는 세가지 서비스가 보인다.

 

l  GTalkService 는 표준 구글 애플리케이션 집합의 일부이다; 구글의 “gapps” 프로세스 내에서 실행 중이고, 현재 6.8 MB 의 메모리를 소비하고 있다. 3시간 55 분 전에 시작되었고, 이 장치에서는 처음 부팅된 시간이다.

l  ActivityService Phonebook App 의 일부이고, 4MB 의 메모리를 소비하고 있다. 이 것 역시 부팅 때부터 실행되고 있다.

l  SoftKeyboard 3rd Party 의 입력 수단이다. 입력 메소드를 전환 때문에 약 4분전 부터 실행되고 있다.

 

사용자는 서비스를 제어하기 위해 목록을 선택할 수 있다; 명시적으로 시작되어 실행 중인 일반 서비스의 경우, 서비스를 멈추도록 하는 대화상자를 보여줄 것이다.

 


 

입력 메소드와 같은 일부 다른 서비스들은 다른 이유로 실행 중이다. 이들의 경우, 그 서비스를 선택하게 되면 그 서비스를 관리하기 위한 UI 가 나타나게 될 것이다. (입력 메소드의 경우 시스템의 입력 설정)

 

마지막으로, 화면의 하단에는 몇몇 숫자가 보인다. 이 숫자를 번역하는 법을 안다면, 장치의 메모리 상태에 대한 많은 정보를 얻게 된다.

 

l  Avail: 38MB+114MB in 25 38 MB 의 완벽하게 프리한 (또는 unrequired cache 에 사용될 수 있는) 메모리를 가지고 있고, 언제든 종료될 수 있는 25 개의 백그라운드 프로세스에 114 MB 의 또다른 이용가능한 메모리가 있다는 것을 말한다.

l  Other: 32MB in 3 는 장치에 종료될 수 없는 3개의 프로세스(다시 말해서, 현재 foreground 로 간주되어 실행을 계속 해야 하는 프로세스) 내에 이용할 수 없는 메모 리가 32 MB 정도 있다는 것을 의미한다

 

대부분의 사용자에게, 이 새로운 사용자 인터페이스는 현존하는 태스크 킬러애플리케이션과 비교하여 백그라운드 애플리케이션을 제어하기 위한 좀더 효과적인 방법이 될 것이다. 대부분의 경우 장치를 느리게 만드는 이유는 너무 많은 서비스가 실행하려고 하고 있기 때문이다. 이것은 시스템이 어떤 백그라운드 프로세스도 실행할 수 없도록 하고, 그리고 궁극적으로 모든 서비스가 실행될 수 없는 결과를 초래할 수 있다. 실행중인 서비스 UI 는 어떤 서비스를 중지할지를 결정하는 것을 돕기 위해 실행중인 서비스에 관한 정보를 제공하고자 하는 것이다. 이것은 수많은 방법으로 무심코 애플리케이션을 파괴할 수 있는 애플리케이션 강제 종료 API 를 사용하지 않는다.

 

개발자에게 이 UI 는 서비스가 제대로 동작하고 있는지를 확인하기 위한 중요한 도구이다. App 을 개발할 때, 우연히 서비스가 실행 중인 채로 남게 되지 않도록 하기 위해 실행 중인 서비스를 계속해서 주시해야 한다. 또한 사용자가 자유롭게 서비스를 중지할 수 있다는 것도 이제 기억해야 한다.

 

안드로이드의 서비스는 매우 강력한 도구이지만, 애플리케이션 개발자가 전반적인 사용자 경험에 해를 끼칠 수 있는 주요한 그리고 미묘한 방법중의 하나이다.

 

.