슈퍼드로이드 카페의 안드로이드 강좌가 책으로 나왔습니다.
도서명 : 이것이 안드로이드다.
도서링크 : http://www.yes24.com//24/goods/13950202
================================================================================================
1. Broadcast Receiver에 대해서
Android의 4가지 Component 중 가장 쉬운 것중 하나가 바로
Broadcast Receiver이다.
Broadcast Receiver는 각각의 Component들 간에 메시지를
전달할 수 있는 방법을 제공한다.
우선 여러 패키지에서는 귀를 기울일만한 방송에 대해 Receiver라는 녀석을 등록해 둔다.
이 Receiver는 자신이 관심을 가지는 방송을 듣게되면 동작하게 되는 것이다.
그림으로 보자면
아래에 1번과 같이 특정 패키지 Component에서 sendBroadcast(메시지)를 통해서
전역 방송을 날린다. 이때 방송은 "Test1"이라고 가정하자.
그후 이미 등록된 Receiver 중 3번에 해당하는 receiver가 그 메시지에 관심을 가지고 있으므로 동작하게 될 것이다.
여기서 방송이라는 표현을 썼는데 이 것이 바로 Intent이다.
앞서 Activity를 설명하면서 Intent에 대해서 충분히 설명을 하였다.
Andriod를 하면서 Intent는 아주 중요하다. (조금이라도 이해가 안되었다면 앞의 강좌를 다시보고 또 보자.)
앞으로 계속 배우게 될 BroadcastReceiver, Service에서 계속 활용된다.
Activity에서 설명한 Intent에 대해 이해하면 모두 같은 방식으로 사용되니 한번만 잘 이해하면 된다.
(주변에 Intent에 대해 정확히 이해하는 사람이 많이 없어서 계속 말하는 것이다. ^^;)
어쨌든 참 글로 쓰면 왜 쉬운 내용도 어렵게 전달이 될까? 짜증이 난다. ^^;
카페에 누군가 동영상 강좌로 해 주면 안되겠냐는 글을 보았다.
어쩔때는 정말 그러고 싶다. ㅋㅋㅋ
자 계속해서 Receiver에는 두가지 종류가 있다.
바로 정적 Receiver와 동적 Receiver이다.
별로 어렵지 않으니 계속 강좌를 지켜보길 바란다.
1.1 정적 Receiver
정적 Receiver란 말 그대로 Receiver를 고정해서 등록해 놓고 원하는 방송에
반응하는 Receiver를 말한다.
역시 개발자는 소스를 보고 이해하는 것이 빠를 것이다.
아래의 패키지로 이해해 보자.
두 가지 패키지를 만들 것이다.
우선 첫번째 패키지는 방송을 하는 패키지이다.
아래에 1번과 같이 Intent를 하나 생성하고
Intent에 방송할 내용을 담는다.
방송할 내용 역시 Action이 사용된다. Activity와 똑같다. ㅎㅎㅎ
setData()를 통해 Intent Filter에 매칭될 Scheme을 하나 넣어 보았다. 왜? 그냥.... - _-;
(BroadcastReceiver는 모든 패키지가 받을 수 있다. 그러므로 꼭 원하는 패키지 Receiver에게 전달하게 하려면
적절한 Intent Filter 를 사용하는 것이 좋다.)
1번에서 방송하게 될 Action을 기억해 두자. "android.intent.action.SUPERSK"
자 이제 두번째 패키지 이다.
당연히 이 패키지는 방송을 듣고 반응하는 리시버 이다.
아래는 AndroidManifest.xml에 리시버를 하나 등록하고 있다.
아래는 AndroidManifest.xml에 리시버를 하나 등록하고 있다.
아래는 AndroidManifest.xml에 리시버를 하나 등록하고 있다.
3번 반복한 것은 ^^ 오타가 아니다.
왜냐하면 AndriodManifest.xml에 고정해서 리시버를 등록하기 때문에 정적 Receiver라고 말하는 것이다.
이해가 되는가 안되면 다음에 나올 동적 Receiver까지 보면 이해가 될 것이다.
일단 ^^/ 계속...
위의 Intent Filter는 이전 Activity에서 Intent를 이해했다면 쉽게 볼 수 있을 것이다.
2번을 보면 Action으로 첫번째 패키지가 보내는 방송에 귀를 기울이는 Action 일 것이다.
계속해서 그 방송을 듣게 되면 처리되는 코드는 아래와 같다.
해당 방송이 오면 Toast를 하나 띄울 것이다.
위의 onReceive( Context context, Intent intent) 의 인자인
intent로 방송을 보낼때 전달되었던 Intent가 그대로 전달받게 된다.
바로 이 Intent로 원하는 데이터를 Intent Extra에 담아서 보내면 된다.
자 이제 실행해 보자.
첫번째 패키지에 버튼을 누르면
2번과 같이 두번째 패키지가 방송을 듣고 Toast를 하나 보여 준다.
쉽지 않은가?
테스트 패키지 소스는 아래에 첨부한다.
Broadcast1.zip
Receiver1.zip
1.2 동적 Receiver
동적 Receiver는 AndroidManifest.xml에 Receiver를 등록하지 않는다.
위에서 만든 패키지 중 방송을 보내는 패키지를 그대로 활용하고,
아래의 패키지를 추가로 만들어 보자.
위에서 왜 Activity를 하나 만들었을까?
궁금증을 가지고 계속 아래를 보자.
Activity의 내용이다.
위에서 Activity의 생명 주기중 onCreate에서 Receiver를 등록하고 있다.
차근차근 보자.
1번에서 IntentFilter를 하나 생성하여 방송에 매칭될 Action과 Data의 Scheme을 등록하였다.
2번에서 BroadcastReceiver 객체를 하나 생성한다. 객체 생성과 동시에 onReceiver() 함수를 구현해 주면 된다.
차후 방송이 오게 되면 바로 onReceiver()함수가 호출될 것이다.
3번에서 해당 BroadcastReceiver 객체를 등록한다. registerReceiver()함수는 바로 receiver 객체를 등록하게 해 준다.
당연히 registerReceiver() 인자로 등록될 리시버 객체와 intentFilter를 넣어주면 된다.
자 특정 Component에서 BroadcastReceiver 객체를 동적으로 생성하여 리시버를 등록 하였다.
이 것이 바로 동적 Receiver라고 하는 이유다.
자 잘 동작되는지 보자.
1번과 같이 지금 만든 동적 리시버 패키지를 꼭 실행해 둬야 한다. (이유가 있다.)
2번과 같이 꼭 Home Key를 눌러서 다시 홈으로 가자.
3번에서 방송을 날릴 패키지를 실행하자.
4번의 버튼을 눌러보면 동적 Receiver가 동작하고 Toast를 하나 띄울 것이다.
자 다시 하나면 더 테스트를 해 보자.
아래와 같은 시나리오로 해야한다.
1번에서 동적 리시버 패키지를 실행하자
2번과 같이 Back Key를 눌러 해당 패키지를 종료해 보자.
3번과 같이 홈에서 방송을 보낼 패키지를 실행하고
4번과 같이 방송 버튼을 눌러 Broadcast를 날려 보자.
!!! Toast가 안뜨지 않는가?
이것이 바로 동적 리시버의 특징이다.
동적 리시버는 자신을 등록한 Component의 생명 주기가 끝나면 사라진다.
위에서는 Activity에서 리시버를 등록 했으므로 Activity를 Back Key를 이용해서 종료하게 되면
리시버도 사라지는 것이다.
Activity의 생명 주기는 아래와 같다.
즉 onDestroy()까지 타면 Receiver는 없어 진다.
꼭 이해하도록 하자.
동적 리시버에서 꼭 하나더 알아야 할 것이 있다.
리시버를 등록할때 registerReceiver()라는 함수를 썼다.
그렇다면 분면 unregisterReceiver()라는 함수도 있지 않겠는가 그렇다.
registerReceiver()로 등록된 Receiver는 unregisterReceiver() 함수를 이용해서 등록해제 할 수 있다.
즉 리시버를 더 이상 사용할 필요가 없을 때는 unregisterReceiver() 함수로 해제하면 된다.
또한 한번 등록된 리시버를 중복해서 다시 등록하는 일이 없도록 하자.
만일 두번 등록이 되면 중복된 리시버 모두 동작하기 때문이다.
뿐만아니라 unregisterReceiver()를 통해 해제치 않으면
메모리에 계속 잡고 있는 메모리 누수가 발생한다.
꼭 해제해 주도록하자.
적절한 위치에 등록과 해제 소스를 넣어 두자.
예를 들어 onCreate()에 등록하고 onDestroy()에 해제 한다던가...
테스트 패키지 소스는 아래에 첨부한다.
Receiver2.zip
1.3 동적 Receiver와 정적 Receiver는 어떤 차이가 있을까?
android를 처음 접할때 이 점이 참 궁금했다.
그 이유는 참 간단했다. 하지만 글로 설명해야 하는데 잠시 두렵기도 하다... =_ =;;
일단 정적 리시버는 한번 등록하고 계속 유지할 수 있다는 장점이 있지만
해제가 어렵다는 단점이 있다.
다른 단점을 이해하기 위해서는 동적 리시버의 장점을 설명해야 한다.
동적 리시버는 등록한 component 생명 주기가 끝나면
더이상 동작하지 않는다는 단점이 있다고 했다. 물론 계속 유지 하고 싶다면 정적 리시버를 쓰면 된다.
동적 리시버는 내가 생각하는 아주 편리한 장점 두가지가 있다.
첫번째는 바로 시스템에 큰 부하를 주지 않는 점이다.
매분마다 발송되는 시스템 Action "android.intent.action.TIME_TICK" 이 있다
이 것은 AlarmManagerService란 서비스가 매 분마다 발송한다.
만일 정적 리시버로 해당 Action을 등록했다면 매분마다 계속 이벤트를 받게 되고,
매 분마다 해당 이벤트에 대한 처리를 할 것이다.
이는 시스템 측면에서 전체적이 성능을 저하시킬 것이다.
하지만 동적리시버는 한 Component의 생명주기 내에서만 해당
이벤트를 받으므로 큰 부하를 주지 않는 것이다.
참고로 "android.intent.action.TIME_TICK" 이벤트는 정적 리시버로는 받을 수 없다.
위에서 말한 이유 때문이다. 즉 Android 시스템에서 시스템에 부하를 주는
경우를 당연히 두지 않는다.
이렇게 동적 리시버에서만 Action을 받을 수 있도록 하는 Intent Flag는
"FLAG_RECEIVER_REGISTERED_ONLY" 이다.
( FLAG_RECEIVER_REGISTERED_ONLY는
"17. Broadcast Receiver에 대해서 - IntentFlag 정리"
강좌에서 설명하니 넘어가도록 하자.)
두번째는 다른 Component 내에 소스가 존재한다는 것이다.
리시버를 등록할때를 생각해 보자.
뭐 아래와 같다고 하자.
위에서 Activity 내에 Receiver가 달려 있다.
여기서 mCount 라는 멤버 변수로 접근해 보자.
위와 같이 쉽게 mCount로 접근할 수 있다.
그 객체를 참조할 수 있는 것이다.
정적 Receiver를 생각해 보자.
Receiver에서 사용하고 싶은 특정 Component의 멤버변수를 전혀 접근 할 수 없다.
(물론 Application 객체를 이용하여 접근할 수는 있다. - _-; 혹시 궁금한 사람들은
Activity와 마찬가지로 Receiver 또한 ActivityManagerService가 처리한다.
아래의 그림을 보자.
2번과 같이 ActivityManagerService가 활성화 되어야 할 등록된 Receiver를 호출해 준다.
만일 여러개의 리시버가 등록되어 있다면 순서대로 활성화 시킨다.
3,4,5번을 보면 등록된 리시버가 3개이다.
만일 Receiver1이 처리되는 시간이 3초라면 다음 처리될 Receiver2는 3초후에 활성화 될 것이다.
또한 Receiver2이 6초라는 시간이 소모되면 Receiver3번은 총 9초후에 활성화 될 것이다.
여기서 말하고자 하는 것은 리시버에 대한 처리는 최대한 빨리 끝내는 것이 바람직하다.
^^ 만일 처리시간이 오래 걸리게 되면 Activity와 마찬가지로 ANR이 발생된다.
아래의 테스트 코드로 이해해 보자.
리시버를 하나 등록했다.
해당 리시버는 12초의 지연을 주 예이다.
아래와 같이 실행해 보자.
눈에 에러는 보이지 않으나 에러가 발생될 것이다.
아래를 보고 이해해 보자.
로그를 보자.
6번에서 ANR을 발생시켜 해당 리시버를 죽이기 위해 리시버가 포함된 Process를 죽여 버린다.
ActivityManagerService는 다른 작업을 하기 위해 절대 기다려 주지 않는 다는 것이다.
아래에 Framework 소스를 잠시 보자.
리시버의 Timeout은 정확히 10초임을 알 수 있다.
그렇다고 10초 내에 끝내라는 것이라고 이해하면 안된다.
최대한 간단한 작업만을 하라는 것이다.
리시버는 Activirty 처럼 UI가 존재하지 않는다.
그러므로 Background에서 돌아가는 component이다.
다음에 배우겠지만 service component 역시 background에서 돌아간다.
하지만 둘을 같이 보면 안된다.
Receiver는 제약 사항이 많기 때문이다. 지금 그 제약 사항을 설명하는 것은 어렵다.
Service를 배우지 않았기 때문이다. 차후에 다시 설명 하겠다.
(참고로 몰라도 되지만 ... Receiver에서는 bindService가 허용되지 않는다.)
어쨌든 기억하라. ~!!!!!
리시버는 간단한 작업만 하도록 하자.
DB에 값을 변경한다던지... 꼭 구구절절히 말하기는 힘들지만 어쨌든 onReceiver 함수 내에서만 처리될 수 있는
간단한 작업만을 하자.
간혹 리시버에 네트워크 작업등을 처리하려는 사람을 보았다.
그러지 말자. ^^;;;
첫댓글 그후 이미 등록된 Receiver 중 3번과 4번의 receiver가 그 메시지에 관심을 가지고 있으므로 동작하게 될 것이다.
밑에 그림에서 보면 3번은 있는데 4번도 3번이라고 써져있네요ㅜ_ㅜ....
^^ 제 의도는 3번이라고 쓰여진 그림 모두를 묶어서 3번이라고 한 것인데...
오해할 수도 있겠군요.
감사합니다.
^^ 지적 감사합니다.
많은 도움이 되었습니다. 감사합니다~!
슈퍼성근님! 정적 리시버를 해제하고 싶을 때에는 어떻게 해야하나요?!
^^; 정적 리시버는 해제 할 수 없습니다.
처리하고 싶지 않을때는 receiver 소스에서 그냥 리턴해버리는 방법 밖에는 없네요.
앗 한가지 방법이 있다고 하자면 해당 receiver Component를 비활성화 시켜 버리는 방법도
방법이라면 방법이네요. ^^
말씀하신 방법이 http://www.grokkingandroid.com/enabling-and-disabling-broadcastreceivers/ 여기 글에 해당되는 거 같습니다.
아하 ㅎㅎ 감사합니다 ^^
작살납니다!!
잘보고있습니다 예제중에
정적 리시버는 아이스크림 버젼에서는 안되나요? 아무리 똑같이 해도 되질않네요...
첨부된 테스트패키지도 버젼업도 안되고..
다음 강좌에서
2.3 FLAG_INCLUDE_STOPPED_PACKAGES
을 참조하세요.
이 Flag는 Api Level 12(허니콤)부터 추가되었다.
manifest에 <uses-permission android:name="android.permission.INTERNET"/> 을 추가하니 제대로 동작하네요..
저도 오늘 하루종일 헤맸습니다 ㅋㅋ
항상 강의내용 보고 가는 1인입니다.
참고로 동적리시버의 경우 register만하고 unregister를 하지 않을 경우 메모리릭이 발생합니다.
어떤 App A 의 onResume 에서 registerReceiver 을 하고 onPause 에서 아무런 행위를 하지 않을경우 ..
A를 진입/취소 를 반복하게 되면 어느 순간 memory allocation error 를 발생하면서 A가 강제 종료 됩니다.
요즘 기기들은 메모리가 커서 A한테 할당되는 힙이 여유가 좀 있어서 당장 눈에는 안띌지 모르지만 힙을 계속 차지하기때문에 A 실행속도가 느려진다거나 하는 문제점이 있습니다.
메모리 누수는 개발자로서는 반드시 피해야될 기본중의 하나이기때문에 강조되었으면 좋겠습니다. ^
아하~ 그렇군요~ 액티비티 생명주기에 따라 실제로 동작을 안한다 하더라도 메모리는 차지하고 있다는 말씀이시군요??
매우 좋은 지적이십니다. 요즘 단말기에서는 많은 메모리를 지원함에 있어
메모리 누수에 대한 고려를 많이 하지 않습니다.
하나의 unregister 누락에 어느정도 메모리 누수가 생기는지는 측정해 보아야하겠지만
개발자로써 인지해야 하는 필수 영역이라 생각합니다.
감사합니다.
항상 도움 많이 받고 있습니다. 감사합니다.
1.4 내용에 한가지 추가하자면, 동적 Receiver일 때는, 여러개의 Receiver가 있다고 해도 순서대로가 아니라 동시에 활성화가 되어 각각의 onReceiver() 일을 하게 됩니다. 동적 Receiver에서도 다른 Receiver와 순차적으로 일처리를 하고자 한다면 sendBroadcastOrdered()로 broadcast 할 수 있겠습니다.
좋은 정보 감사드립니다. ^^
질문이 있습니다. 그렇다면.... 정적 receiver경우 순차적으로 활성화 된다는 뜻일까요? broadcast timeout에 걸렸을경우 어느 애가 느리게 일을 처리해서 timeout이 걸렸는지 찾기가 힘드네요.
제가 알기로는 anr이 발생했을때 crash 로그를 보면 어떤 패키지가
문제인지 알수 있습니다. ^^
안녕하세요~ BroadcastReceiver 를 만들때요~ Activity 없이 BroadcastReceiver 만 만들어주니 정적으로 manifest 에 추가해도 실행이 안되네요~
Activity 없이 BroadcasrReceiver 만 달랑 존재하는 app 의경우는 Broadcast 를 수신받을 수 없는건가요?
결론부터 말씀드리자면 Broadcast 날리는 녀석이 어떤 Flag를 사용하냐에 따라
받을 수도 있고 못 받을 수도 있습니다.
그 플래그는 바로 FLAG_EXCLUDE_STOPPED_PACKAGES 입니다.
이 플래그가 설정되면 아래의 리시버는 호출되지 않습니다.
1) 한번도 실행된 적이 없는 앱
Activity가 없다면 한번이라도 앱이 실행되기가 힘들겠네요.
2) 프로그램 관리자에서 사용자가 의도로 강제로 종료한 경우
이 경우를 모두 앱이 Stop 상태라고 보고
리시버가 동작하지 않습니다.
참고로 FLAG_EXCLUDE_STOPPED_PACKAGES가 API 13부터인가 ^^a
굳이 추가하지 않아도 기본값이 된 것으로 알고 있습니다.
그러므로 FLAG_INCLUDE_STOPPED_PACKAGES
어떤 Broadcast 이벤트를 받으시려는지
확인해야 겠네요. ^^
슈퍼성근님 정말 감사드립니다.^^ 인터넷에서 뒤져봐도 안나오고 주변 사람들에게 물어봐도 모르는 내용이라 혼자 고민만 했었는데요~
추가검색해보니,
FALG_INCLUDE_STOPPED_PACKAGES : stop된 application도 target이 될 수 있다.
FALG_EXCLUDE_STOPPED_PACKAGES : stop된 application도 target이 되지 않는다.
위와 같이 2가지 플래그가 있으며,
intent flag를 지정하지 않으면 default인 EXCLUDE가 지정된다.
라고 나오네요...그래서 FLAG_INCLUDE_STOPPED_PACKAGES 추가해주었더니,
ACTIVITY 없어도 RECEIVER 동작 잘 하고 있습니다.
감사합니다.
잘 해결되셔서 다행이네요. 수고하셨습니다.
정적 Receiver 부분 정상적으로 동작하지가 않는데 어떤게 잘못된것인지 모르겠습니다...
올려주신 소스를가지고 동작을 해보아도 Boreadcast 1 버튼 클릭시 아무반응도 나오질 않네요.
강좌 최신화가 되지 않았네요. 죄송합니다.
해당 질문의 원인은 바로 FLAG_EXCLUDE_STOPPED_PACKAGES 속성 때문입니다.
그러므로 정상적으로 정적리시버가 동작하려면
FLAG_INCLUDE_STOPPED_PACKAGES 설정후 브로드캐스트를 날려야 겠네요.
그 이유에 대해서는 위의 댓글에 설명되어 있으니 참조 부탁드립니다.
감사합니다.
안녕하세요 1.4와 관련해서 궁금한 사항이있어서 질문 드립니다.
그러면 어떤 broadcast에 대해 정적리시버 여러개와 동적 리시버 여러개가 등록 되어 있는데
sendBroadcast() 로 broadcast를 송신한다면,
동적리시버는 동시에 처리가 되고
이후 정적리시버들은 priority에 의해 순차적으로 처리되게 되나요?
public void onClick(View v)
{
Intent intent = new Intent("android.intent.action.SUPERSK");
intent.addFlags( Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.setData(Uri.parse("sample:"));
sendBroadcast(intent);
}
본문 예제를 수정했으나 오류가 납니다. 제가 잘못 수정했나요 ?
선생님의 글은 언제나 교과서처럼 반갑습니다.
잘 보고, 도움이 되었습니다.
(정적/동적) BroadcastReceiver 기초/[Android] http://blog.daum.net/andro_java/1226
그런데,
AndroidManifest.xml 파일에서
<protected-broadcast android:name="android.intent.action.TEST" />
지워도 실행에 아무 지장이 없는데 ...
이것을 꼭 등록해야 되는지, 이름 TEST 키워드는 임의로 만들면 되는 것인지 ...
잘 몰라서 도움을 구합니다.