Efficient Android Threading #9 서비스
이 글은 Efficient Android Threading 의 일부 내용만 발췌한 내용입니다.
자세한 내용은 책을 구입해서 보세용.
11.1. 비동기 실행을 위해 서비스를 사용해야 하는 이유
-
구성요소 생명주기와 스레드 생명주기의 분리
호스팅 프로세스의 생명주기
서비스는 메모리 누수에 대한 위험과 너무 빨리 태스크가 종료될 위험을 모두 줄일 수 있다.
11.2. 지역, 원격, 전역 서비스
-
전용 원격(다른 프로세스 사용) 서비스는 자신만의 UI 스레드를 가진다.
따라서 서비스 UI 스레드는 클라이언트 구성요소의 UI 스레드의 실행을 지연하지 않는다.
원격 서비스는 같은 힙 메모리 영역을 공유하지 않는다.
따라서 클라이언트는 원격 메서드를 호출하는 데 바인더 스레드 풀을 포함한 안드로이드 바인더 메커니즘을 통해 서비스를 호출해야 한다.
-
전역 원격 서비스(다른 앱의 다른 process 에서 도는 서비스)는 전용 원격 서비스와 동일하게 자신의 UI 스레드, 힙 메모리, 바인더 스레드에서 실행된다는 특성이 있다.
-
지역 서비스의 장점은 단순함과 저장 메모리다.
프로세스 내에서 자바 객체 공유가 가능하므로 IPC 와 AIDL 의 복잡성을 피할 수 있다.
따라서 클라이언트 스레드에서 서비스 태스크 실행을 제어하기가 쉽다.
각 프로세스는 단지 서비스 구성요소를 호스팅하는 경우에도 수 메가의 메모리를 소비한다.
따라서 지역 서비스로 사용할 경우 메모리가 절약된다.
-
원격프로세스를 사용하면 여러 앱 사이에서 공유를 할 수 있다는 장점이 있다.
또한 서비스를 중지하는 에러가 원격 프로세스에 포함되고, 클라이언트 구성요소에 의해 실행되는 프로세스에는 영향을 미치지 않는다.
11.3. 생성과 실행
-
모든 안드로이드 구성 요소와 마찬가지로, 서비스는 원격 프로세스에 할당되어 실행될 수 있다.
이는 manifest 의 Service 정의에 android:process 속성을 주면 되는데, :(콜론) 으로 시작한다.
다른 앱에서 접근할 수 있는 전역 원격 프로세스의 실행은 프로세스 이름을 대문자로 시작함으로써 정의한다.
11.4. 생명주기
11.5. 시작 서비스
-
서비스 시작 요청은 onStartCommand 로 순차적으로 전달되고, 이전 시작 요청이 처리되거나 혹은 UI 스레드에서 떠넘겨질 때까지 런타임에서 대기 상태로 유지된다.
시작 요청이 순차적으로 처리됨에도 불구하고, startService 로의 호출은 서비스 안에서 처리를 기다려야 하는 경우에도 차단되지 않는다.
-
onStartCommand 는 UI 스레드에서 실행된다.
따라서 반응성을 유지하면서도 여러 시작 요청의 동시 실행이 가능하게 하려면, 오래 걸리는 작업 실행을 위해 메서드 내에 백그라운드 스레드를 생성해야 한다.
** 11.5.1. onStartCommand 구현
-
onStartCommand 는 다음 3개의 인수를 갖는다.
Intent
전달 메서드 ( flags )
0 : 최초 시작, 일반 요청
START_REDELIVER_INTENT, 1 : 이전에 서비스로 전달된 인텐트이다.
START_FLAG_RETRY, 2 : 이전에 전달되었거나 시작할 기회가 없었지만, 서비스가 종료 및 재시작 될 때 보류중이었던 인텐트이다.
시작 ID ( startId )
런타임에서 제공하는 uniqueID, 프로세스가 종료되고 재시작되면 onStartCommand 는 같은 시작 ID 로 호출된다.
-
onStartCommand 의 반환값은 서비스를 재시작하고 Intent 인수를 다시 전송해야 할지(런타임이 자원 부족으로 프로세스를 종료하고 재시작하는 경우) 여부를 런타임에 알려준다.
** 11.5.2. 재시작을 위한 옵션 ( 너무나 복잡한것.. 읽어도 기억은 안 되고.. 매번 reference 를 참고하게 된다.. )
-
onStartCommand 에서는 처리되지 못한 Intent 들에 대해 버릴 수도 있고, 재요청 할 수도 있다.
여기서 다루는 Case 는 Service.stopSelf 를 통한 깔끔한 종료가 아닌, 런타임이나 클라이언트(Context.stopService) 를 통한 종료 케이스에 valid 하다.
onStartCommand 의 반환값과 두 번째 인수(flag)를 통해 서비스가 종료된 후에 발생하는 상황을 제어할 수 있다.
-
현재 처리중인 Intent 를 버리는 선택을 할 경우 다음과 같은 선택을 할 수 있다.
Pending 된 request 가 있거나, client 로부터 새로운 요청이 왔을 때 런타임에 서비스가 재시작을 요청할 수 있다. ( START_NOT_STICKY )
다른 요청의 유무와 상관없이 즉시 런타임에 서비스의 재시작을 요청할 수 있다. ( START_STICKY )
-
onStartCommand 메서드가 실행되는 동안 서비스가 종료되면 전송된 인텐트는 재시작할 기회를 얻을 수 없다.
그러므로 이때의 인텐트는 시작 요청이 아닌 보류 중인 요청으로 간주된다.
-
onStartCommand 의 Intent 처리에 대한 선택은 반환값(운영 모드, operation mode)에 의해 활성화 된다.
START_STICKY ( 1 )
다른 pending request 가 있든지 없든지 새로운 프로세스에서 재시작된다.
종료된 요청의 인텐트는 다시 전송되지 않는다.
그러나 새로 시작하는 서비스는 이전 서비스 프로세스가 종료될 때 전달되지 않고 남은 보류 중인 요청을 받게 된다.
보류 중인 시작 요청은 호출의 두 번째 인수에 START_FLAG_RETRY 플래그와 함께 전달된다.
보류 중인 시작 요청이 없으면 onStartCommand 는 인텐트 인수에 null 값이 들어간 상태로 호출된다.
START_NOT_STICKY ( 2 )
프로세스가 종료되었을 때 pending request 가 있는 경우에만 서비스를 재시작하는 것을 제외하면 START_STICKY 와 같다.
따라서 인텐트는 항상 전달된다.
START_REDELIVER_INTENT ( 3 )
서비스는 재시작되고, 보류 중인 요청과 이전에 시작되었으나 완료될 기회가 없었던 요청 모두를 수신한다.
보류 중인 요청은 두 번째 인수에 START_FLAG_RETRY 플래그 설정과 함께 전달되며, 이전에 시작된 요청은 START_FLAG_REDELIVERY 설정되어 함께 재전달된다.
** 11.5.3. 사용자 제어 서비스
-
사용자 제어 서비스는 다른 안드로이드 구성요소에 의해 서비스가 종료되어야 한다고 지시가 있을 때까지 실행해야 하는 작업에 사용할 수 있다.
종료는 일반적으로 사용자 액션에 의해 시작된다.
이 서비스를 사용할 후보는 보이는 UI 없이 이벤트를 처리하고, 동작 실행을 계속하는 앱이다.
-
스레드는 클라이언트가 stopService 를 호출한 후에도 실행되는 비동기 태스크다.
그리고 시간을 오래 끄는 스레드는 스레드가 참조하는 객체의 메모리 누수 위험이 있다.
서비스에서 스레드를 사용하는 경우, 각 스레드는 다른 Service 인스턴스를 참조하고 메모리 안에 해당 instance 가 유지되도록 강제한다.
이러한 스레드로 인한 메모리 누수와 성능 문제에 대한 위험을 줄이기 위해, 서비스는 가능한한 반복적으로 재시작하면 안 된다.
꼭 필요한 만큼 살아 있게 두는 것이 좋다.
** 11.5.4. 테스크 제어 서비스
-
일반적으로 테스크 제어 서비스는 백그라운드 스레드가 프로세스 종료로 중단될 위험을 감소시키고 실행을 완료할 수 있도록 하는 데 사용된다.
서비스가 stopSelf 로 자체적으로 중지되는 경우, 서비스 구성요소의 생명주기에 대한 제어는 프로세스 테스크의 책임이다.
즉, 구성요소가 소멸되는 시점을 태스크가 결정한다.
-
백그라운드 스레드의 수명은 서비스의 수명을 결정한다.
그러므로 백그라운드 스레드에서 실행 중이면 구성요소는 항상 활성 상태다.
11.6. 바운드 서비스
-
unbindService 를 이용하여 명시적으로 바인딩을 종료할 수 있지만, 바인딩은 클라이언트 구성요소 생명주기가 끝나는 경우에도 런타임에 의해 종료된다.
-
bindService 를 호출할 때 전달하는 Intent 에는 Extra 매개변수를 넣어도, 실제 해당 서비스에 전달되지 않는다.
flag 는 0 또는 허용된 인수의 컬렉션 중 하나를 전달한다. 일반적으로 사용하는 플래그는 BIND_AUTO_CREATE 다.
** 11.6.1. 지역 바인딩
-
가장 일반적인 유형이다.
같은 앱과 프로세스에서 서비스로 바인딩하는 클라이언트 구성요소는 두 구성요소가 같은 VM 에서 실행하고 같은 힙 메모리를 공유하기 때문에 이득을 가질 수 있다.
IPC 의 복잡성을 신경 쓸 필요가 없다.
11.7. 비동기 기술 선정
-
Service 에서 수행하는 태스크들이 순차적으로 실행될 때는 IntentService 를 사용하는 것이 좋다.
실행할 태스크가 더 이상 없을 때 IntentService 는 서비스를 종료시키는 순차적 태스크 실행용 지원을 내장하고 있기 때문이다.
-
AsyncTask 는 같은 실행자가 모든 테스크에서 공유되는 전역 실행자를 갖기 때문에 테스크가 지연되는 위험을 갖고 있다.
따라서 같은 프로세스에서 다른 구성요소로서 실행되는 AsyncTask 는 비전역 실행자 같은 커스텀 실행자를 사용하거나 스레드 풀처럼 대체 기술을 사용해야 한다.
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
Efficient Android Threading #11 AsyncQueryHandler 를 이용한 콘텐트 프로바이더 접근 (0) | 2018.03.27 |
---|---|
Efficient Android Threading #10 인텐트 서비스 (0) | 2018.03.26 |
Efficient Android Threading #8 AsyncTask 로 백그라운드 태스크를 UI 스레드에 묶기 (0) | 2018.03.24 |
Efficient Android Threading #7 Executor 프레임워크를 통한 스레드 실행 제어 (0) | 2018.03.23 |
Efficient Android Threading #6 핸들러 스레드 : 고수준 큐 메커니즘 (0) | 2018.03.22 |
댓글