슈퍼드로이드 카페의 안드로이드 강좌가 책으로 나왔습니다.
도서명 : 이것이 안드로이드다.
도서링크 : http://www.yes24.com//24/goods/13950202
================================================================================================
IntentService 란 무엇일까?
늘 반복해서 강좌마다 말을 하고 있지만,
IntentService 역시 너무나도 쉬운 객체이다.
모든 Class는 우리를 괴롭히려고 만들어 진 것이 아니라,
좀더 개발을 편하게 하기 위하여 존재한다.
그러므로 쉽게 생각하고 접근하면 어려울 것이 없다.
하지만 기반이 되는 기초 정보 없이 바라본다면 쉬운 것이 없기 마련이다.
앞의 강좌를 충실히 보았으면 바란다.
Service강좌까지 꾸준히 보아 왔다면 이런 것도 있구나 할 것이다.
아래와 같이 Class 구조를 보면
Service를 상속 받아 만들어진 abstract class이다.
그러므로 IntentService는 Service Component이다.
abstract class 이므로 미완성된 Class이며,
우리는 해당 IntentService를 상속받아 몇가지를 구현해 주면 된다.
지금까지 서비스에 대해서 알아 보았고,
많은 기능을 제공하였다.
그러나 IntentService는 Service의 파생 클래스이므로,
Service의 특성을 가지고 어떤일을 함에 있어서 보다 간단하고
편리한 구조를 가질 것이 뻔하다.
우선 간단한 Sample을 구현해 보면서 이해하자.
아래는 IntentServiceTest 패키지 이다.
위의 IntentServiceTest.java와 AndroidManifest.xml 만을 간단히 수정할 것이다.
IntentService 역시 서비스이므로
아래와 같이 서비스 component를 정의하자.
이제 IntentService를 상속받아서
두가지 함수를 추가한다.
1번은 생성자이니 별도의 설명이 필요없을 것 같다.
2번은 onHandleIntent() 라는 함수를 오버라이딩 했다.
이게 끝이다.
결국 onHandleIntent 함수만 구현한 것이다.
나중 이해를 위해 위와 같이 각 함수에 로그를 남기도록 하자.
자 해당 서비스를 사용할 Client 패키지를 하나더 만들어 보자.
위와 같은 패키지 구조를 가진다.
워낙 테스트를 많이 해 보아서 무엇을 할지 대략 짐작이 갈 것이다.ㅋㅋㅋ
자 아래와 같이 AndroidManifest.xml을 수정해 보자.
간단히 테스트를 위한 Activity를 하나 등록하였다.
다음은 Activity에 뿌려질 Layout을 만들자
위와 같이 버튼 하나만 만든다.
다음은 Activity를 구현해 보자.
1번과 같이 버튼을 눌렀을때
IntentService 를 시작하는 startService()가 보인다.
즉 서비스를 시작해 주는 것이다.
자 끝났다.
둘다 설치하고 실행해 보자.
1번과 같이 버튼을 누르면 로그가 출력될 것이다.
2번의 로그를 보면 생성자 하나와 onHandleIntent() 함수가 호출되었다.
이게 뭐지 하는 사람도 있을 것이고
아~ 하는 사람도 있을 것이다.
쉽게 말하자면
여러 Client 패키지에서 IntentService를 실행하면
IntentService에서는 단지 onHandleIntent() 함수가 호출되는 것이다.
구조를 설명하지 않을 수 없다.
아래를 보면 좀더 와닿는 필요성을 느끼게 될 것이다.
1번과 같이 Client에서 startService를 실행해 IntentService를 실행하게 된다.
이때 IntentService로 Intent를 전달할 것이다.
2번과 같이 IntentService는 Intent를 받아서
IntentService에 존재하는 IntentQueue에 Intent를 넣는다.
( 몰라도 되지만 IntentService는 HandlerThread 객체를 하나 가진다. )
3번과 같이 IntentService 내에 Looper는 Queue에 존재하는 Intent를 하나씩 꺼내서
4번과 같이 Looper는 onHandleIntent() 함수를 호출한다. 호출시 해당 Intent를 같이 인자로 전달해 준다.
5번과 같이 Queue가 모두 비워 질때까지 Looper는 onHandleIntent() 함수를 호출한다.
이제 감이 좀 오는가? 와야 하는데 ^^;
Queue가 있다는 것은 동시에 여러개를 처리하는 구조가 일단 아니다.
Client에 어떤 요청이 있으면 큐에 쌓아 두었다가
꺼내서 처리하고 다시 또하나를 꺼내서 처리한다.
그러므로 동시에 처리되는 구조가 아닌 것이다.
이것이 IntentService의 주요 기능이다.
하나씩 꺼내서 처리하기 때문에 동기화 문제는 발생하지 않고 안정된 처리를 할 것이다.
이런 기능이 필요할 일이 있을까?
예를 들어 (이건 순전히 예이다. )
여러개의 파일을 다운로드 한다고 하자.
다운로드할 URL을 Client는 Intent의 Extra등을 통해 IntentService로 전달하고
IntentService는 onHandleIntent() 함수에서 순차적으로 다운로드를 할 수 있을 것이다.
파일 다운로드는 시간이 많이 걸리기 때문에
Thread를 생성하여 처리를 해야한다.
그러나 IntentService를 통해서 하게 되면 Background에서 처리 되기 때문에
ANR에 안전할 수 있다.
!!! 중요한 점은 Queue가 모두 비워지면 IntentService는 종료된다는 것이다.
이건 정말 꼭 기억해야 하는 점이다.
Sample 패키지로 좀더 이해해 보자.
IntentService 패키지에 생명주기를 추가해보자.
1번, 2번과 같이 생명주기에 로그를 남겨 보자.
자 실행하자.
1번과 같이 버튼을 눌러보자.
2번, 3번을 보면 생명 주기인 0nCreate가 호출되고
onHandleIntent() 함수가 호출된다.
그런데 바로 0nDestroy가 호출되면서 Service가 종료됨을 알 수 있다.
위의 과정을 생각해 보면 아래와 같을 것이다.
1. client에서 startService를 호출하여 IntentService의 queue에
Intent가 하나 들어 갔다.
2. IntentService의 Looper가 queue에서 Intent를 하나 꺼내서
onHandleIntent() 함수가 호출되었다.
3. 이제 Queue가 비었으므로 서비스가 종료된다.
이해가 되는가?
왜 위의 과정에서 Queue가 비어질때 서비스가 종료되는 것을
계속 강조하고 있을까?
아래와 같은 실수를 많이 하기 때문이다.
그것이 무엇인지 sample 패키지로 이해해 보자.
IntentService 패키지에 아래와 같이 코드를 추가해 보자.
1번과 같이 멤버 변수를 하나 선언한다.
2번과 같이 onHandleIntent() 함수가 호출될때 함수진입과 멤버 변수의 값을 보기 위해서 로그를 남긴다.
멤버변수 값을 100 증가시킨다.
3번과 같이 onHandleIntent() 함수를 조금 지연되어 처리하도록 wait를 3초간 걸어둔다.
4번과 같이 onHandleIntent() 함수가 끝날때 함수종료와 멤버 변수의 값을 보기위해 로그를 남긴다.
자 아래와 같이 실행해 보자.
1,2,3번과 같이 위에서 버튼을 연속해서 3번 누른다.
위의 과정에서 IntentService로 3번의 Intent가 전달될 것이다.
또한 IntentService에의 Queue에 3개의 Intent가 쌓여진다.
4번을 보면 정확히 3초간 함수가 실행이 되고 로그에 멤버변수 값이 100이 되었다.
아직 큐에 2개의 Intent가 쌓여 있기 때문에 바로 onHandleIntent()함수가 호출될 것이다.
5번을 보면 정확히 3초간 함수가 실행되고 로그에 멤버변수의 증가된 값이 200이 되었다.
이제 큐에 1개의 Intent가 쌓여 있다.
6번을 보면 정확히 3초간 함수가 실행되고 로그에 멤버변수의 증가된 값이 300이 되었다.
이제 큐가 비었으므로 0nDestory() 함수가 호출되면서 서비스가 종료된다.
문제는 그 이후 부터이다.
위의 과정에 이어 아래와 같이 다시 버튼을 눌러보자.
2번과 같이 IntnetService가 호출된다.
그런데 3번의 로그를 보자.
위에서 증가된 멤버변수가 초기화 된 것을 볼 수 있다.
위에서 큐가 비워진 후 0nDestory() 탔기 때문에
IntentService는 종료되었고 IntentService 객체는 소멸되었다.
그러므로 당연히 멤버변수는 초기화 되는 것이다.
내가 너무나도 당연한 얘기를 하고 있을 수 있다.
하지만 많은 사람들이 이러한 구조를 모르고 멤버변수를 사용한다.
그리고 멤버변수 초기화로 인한 오동작의 원인을 모르고 아주 많이 고생을 하는 것이다.
물론 멤버변수를 static으로 처리하거나 파일로 저장해 두면 되긴하다.
하지만 이러한 구조를 이해해서 최대한 값을 유지 시키지 않고
독립적인 일만을 처리하도록 구현하는 것이 좋겠다.
위의 sample 소스는 아래를 참조하자
IntentServiceClientTest.zip
IntentServiceTest.zip
IntentService는 어떻게 보면 receiver와 아주 유사한 모습이다.
Receiver의 경우 특정 onReceiver() 함수를 통해 간단한 처리를 할 수 있었고
onReceiver() 함수가 종료되면 객체가 소멸되었다.
IntentService의 경우에도 onHandleIntent()함수를 통해 어떤 작업을 처리할 수 있었다.
또한 Queue가 비워지면 바로 서비스가 종료되고 객체가 소멸되었다.
하지만 IntentService의 경우 순차적인 처리가 가능하고
작업시간 제한없이 긴 작업도 처리가 가능하다.
무엇을 구현하고자 할때
늘 다양한 구현 방법이 존재한다.
중요한 것은 가장 적합하고 효율적인 방법을 찾는 것이 중요하다.
지금까지 진행되었던 강좌를 보고
기본 적인 구조를 충분히 이해한뒤 개발을 한다면
보다 좋은 결과와 짧은 시간안에 개발이 가능할 것이다.
그동안 Service편을 지켜봐 주신 분들께 감사드립니다. ^^
더욱 좋은 강좌로 찾아 뵙겠습니다.
!!! 위의 주제에 해당하는 적당한 예를 댓글로 남겨 주세요. ^^
활용 방안의 예는 다른 개발자들에게 많은 도움이 됩니다.
!!! 카페의 활성화를 위해 추천 버튼을 눌러 주세요.
첫댓글 항상 좋은 강좌 감사드립니다. 기대하겠습니다.
끝까지 읽어 주셔서 감사드립니다
서비스편 잘 봤스니다. 너무 좋은 강좌 감사드립니다..^^
감사합니다. ^^
좋은 설명 감사 드립니다.
서비스편을 전체적으로 3번 정도 정독 한것 같습니다.
쭉~쭉~ 따라가다 보면, 다시 앞에 부분이 이해가 안가서,,, 롤백하고 또 롤백하고,,,
서비스의 설계 시, 많은 참고가 될 것 같습니다.
꾸벅!!!!
관심가져 주셔서 감사합니다.
많은 도움되었으면 좋겠네요. ^^
많은 도움이 되었습니다. 감사합니다. ^^
감사합니다. ^^ 기쁘네요.
가끔 성근님이 뭐하시는분이지 궁금하네요ㅋ^^! 정말 도움이 많이 됩니다.화이팅입니다!
기획하시는 분들을 만족시켜드리기 위해
최대한 짧은 일정에 최고의 퍼포먼스와 안정화를 위해 고민하는
불쌍한 개발자입니다. T-T
또한 저 같은 개발자들이 가족과 함께 보내는 시간을
조금이나마 보탬이 되도록 자료를 공유하는 것이 제 낙이네요. T-T
아라한님께 도움이 되었다는 큰 낙입니다.
개발자가 대접받는 그날까지...T-T
아 소중한 정보 너무나 감사합니다. IntentService가 구현되어 있는걸 보고 강좌를 다시보니 더욱더 이해가 쉽네요~^^
이와 관련 질문하나만 할려고하는데요..
특정 broadcast메세지가 올때마다 startService로 intentService를 호출하는데
onHandleIntent()의 함수안에서
해야 될것이 bindService를 호출 후 connected가 호출 되면 asInteface를 통해서 service interface를 가져와서 전달된 intent를 이용해야 되는데요.. onHandleIntent() 안에서 전달된 인텐트를 안정적으로 bind후 connected가 호출된 후에 사용하도록 구현을 할 수 있을까요??
자답입니다. 바람직한 것인지 모르겠지만 ServiceConnection class 생성시 전달받은 intent를 담아두었다가 bindService를 통해서 connected 호출이 이루어지면 서비스 인터페이스를 얻어와서 미리 저장해둔 인텐트를 이용하도록 적용하니 문제는 없는거 같네요~
@소울 안녕하세요. 질문을 늦게 보았네요.
intentService의 onHandleIntent 오버라이드 함수는 별도의 스레드에서 돌아 갑니다.
그러므로 맘껏 오래걸리는 작업을 하셔도 됩니다.
제가 제안하자면 onHandleIntent 함수내에서
약 500ms 정도 sleep하면서 루프를 돌립니다.
루프문 내에서는 서비스가 바인드 되었는지 계속 확인하고
바이딩 완료시 루프문을 빠져 나와 맘껏 안전하게 바이더 인터페이스를 사용하시면 되겠네요.
서비스 connection 시간은 매우 짧습니다.
이렇게하는 이유는 IntentService의 경우 onHandleIntent 함수가 리턴되고
다음 처리할 작업이 없다면 서비스를 종료해 버리기 때문입니다.
그러므로 최대한 onHandleIntent 내에서 작업을 끝내야 하기 때문에
동기화된 코드를 사용하기 위함입니다.
물론 저 개인적인 생각입니다.
추신) 바인딩이 끝날때까지 무한정 루프를 도는게 걱정된다면
예외 처리로 Max 카운트를 두고 그 안에 처리가 안되면 에러처리를 하시면 되겠네요.
수고하세요.
좋은 정보 감사합니다. 매번 볼때마다 느끼는 건데.. 정말 설명을 잘 하시네요.. 출판하셔도 될 것 같습니다.
감사합니다. 더 붐업해서 더 좋은 강좌로 보답하겠습니다.
강좌에 너무 감사드립니다. 정말 쏙쏙들어왔습니다. 감사합니다.
배경지식이 짧다보니, 기본적인 것을 묻는거 같아 망설여지네요. ^^;;
IntentService 가 기본적으로 Thread로 동작하고, Queue 가 모두 비워지면, Intentservice 가 종료된다는 것이 중요한 것 같습니다. 다름이 아니라 궁금한 부분은 IntentSevice 내에서 다시 Thread 를 돌도록 짠다면, Thread 가 종료되기 전에 Queue 가 비워질 수 있을까요? Thread 를 돌더라도 한 Process 이기 때문에 Thread 가 종료된 시점에서 Queue 가 끝나게 될까요? IntentService 안에서 다시 Thread 로 짜는건;; ^^;; 좀 이상할까요?
안녕하세요.
네 큐가 모두 비워지면 서비스는 종료됩니다.
따라서 별도의 작업 스레드를 사용하신다면
작업 스레드가 도는 중에 서비스가 종료될 수 있겠네요.
이를 해결하려면
onHandleIntent 함수내에서 작업스레드를 돌릴 때
IntentService의 스레드를 잠시 중단해두는 것입니다.
작업스레드에서 모든 처리를 마친후 노티를 보내고
IntentService의 스레드를 깨우면 되구요.
인텐트서비스 자체가 작업스레드이긴 한데
별도의 작업 스레드를 또 만드셔야 하는 상황이신가 보네요. ^^; 아마도 여러개의 스레드가 필요하신 듯
수고하세요.