티스토리 뷰

Android/Android TIP

Processing Ordered Broadcasts

이주성 2013. 8. 21. 16:23

Processing Ordered Broadcasts
[이 포스트는 Bruno Albuquerque 에 의해 작성되었습니다. 그는 구글 브라질의 Belo Horizonte 오피스에서 근무하는 엔지니어 입니다. - Tim Bray]


 제가 생각하기에 안드로이드 플랫폼 상에서 가장 흥미롭고 강력한 기능 중 하나는 바로 브로드 캐스트와 이를 구현한 BroadcastReceiver 클래스입니다. (이 클래스를 구현한 것을 앞으로는 '리시버' 라고 하겠습니다.) 그 중, 이 포스트 에서는 순서가 정해진 브로드캐스트 (Ordered Broadcast) 에 관해서 이야기 하고자 합니다. 혹시나 일반적인 브로드캐스트가 무엇이고 어떤식으로 동작하는지에 관해 궁금하신 분은 안드로이드 개발자 사이트 를 미리 참고하시면 좋을 것 같네요. 뭐, 조금 바쁘신 분들이라면, "브로드캐스트란 시스템에서 뭔가 재미있는 일이 일어나면(예를 들어 와이파이가 끊어진다거나) 생성되어 시스템 전체로 전달 되며, 여러분은 이 브로드캐스트를 수신할 수 있도록 미리 특정한 리시버를 등록할 수 있다"  정도로 알고 계시면 이 포스트를 이해하시는데는 큰 어려움이 없을 듯 합니다.

 저는 얼마전에 Right Number 라는 안드로이드 도구를 개발하게 되었습니다. Right Number 는 여러분이 전세계 어느 곳에 있던지 관계없이 국가 코드와 같은 번잡한 것들은 신경 쓰지 않고, 항상 올바른 방법으로 전화번호를 걸 수 있도록 도와줍니다. 개발 과정 중에 제가 깨우친 한 가지는 바로, Ordered Broadcast 를 처리하는 리시버를 잘못된 방법으로 구현하고 사용하는 개발자들이 제법 있다는 점입니다. 아마도 이와 관련된 안드로이드 개발자 문서가 좀 더 잘 쓰여질 필요가 있을거 같습니다.
 
Non-ordered vs. Ordered Broadcasts
 순서가 없는 일반적인 브로드캐스트 인텐트는 '이론상' 등록된 모든 리시버에게 동시에 전달됩니다. 다시 말해, 어플리케이션 개발자의 입장에서 생각해 본다면, 일반적인 브로드캐스트를 수신하는 어떤 리시버는 동일한 브로드캐스트를 수신하는 다른 리시버와 독립적으로 동작하며, 서로 간에 상호 작용 할 수 있는 인터페이스가 존재하지 않습니다. 간단한 예로, ACTION_BATTERY_LOW 와 같은 인텐트를 들 수 있겠습니다. 여러분은 해당 브로드캐스트를 수신하는 리시버를 구현할 수는 있지만, 리시버 내부에서 또 다른 리시버가 이 인텐트를 수신할 수 없도록 하거나, 송신되는 내용을 변경하는 등의 일을 할 수는 없습니다

 이와는 다르게, 순서 있는 브로드 캐스트 (Ordered Broadcast) 는, 등록된 각각의 리시버에 정해진 순서대로 전달됩니다. (이 순서는 여러분이 메니페스트 상에 리시버를 정의할 때 설정해둔 android:priority 속성 값에 의해 결정됩니다. 숫자가 높을 수록 우선 순위가 높다고 생각하시면 됩니다.) 한 번에 하 나씩 차례로 리시버가 동작하기 때문에, 리시버 내부에서 브로드 캐스트 전송 작업을 중지 시켜, 자신 보다 우선 순위가 낮은 리시버들은 이 메세지를 전달 받지 못하도록 하거나, setResult() / getResult() 등의 메서드를 이용하여 전달될 메세지의 내용을 수정하거나 상위 리시버가 전달해준 내용을 확인 할 수 있습니다. 예를 들자면 ACTION_NEW_OUTGOING_CALL 같은 브로드캐스트 인텐트가 있습니다. 이 인텐트를 가지고 Ordered Broadcast 리시버를 사용할 때 주의할 점에 관해 좀 더 이야기해보겠습니다.

Ordered Broadcast 를 사용하는 방법
 앞서 이야기 한 것 처럼, ACTION_NEW_OUTGOING_CALL 인텐트는 순서있는 브로드캐스트 인텐트입니다.. 사용자가 전화를 걸 때 발생하는데, 안드로이드 개발팀에서 이런 브로드캐스트 인텐트를 구현한데는 몇 가지 이유가 있습니다. 대표적으로 다음 두 가지를 들 수 있겠네요.

  • 개발자들이 외부로 전화를 거는 것을 금지하는 서비스를 만들고자 할 때.
  • 개발자들이 전화를 걸기 전에 해당 번호의 값을 수정하는 서비스를 만들고자 할 때.

 예를 들어 특정 번호대로는 아예 전화가 걸릴 수 없는 보안 어플리케이션이라던가, 정해진 시간대에만 전화 걸기 기능이 가능해지는 어플리케이션 혹은 서비스를 생각해볼 수 있겠습니다. 그리고, 또 한가지 제가 작성한 Right Number 어플리케이션은 바로 두 번째 시나리오에 해당하는 경우인데, 사용자가 전 세계 어디에서 전화를 걸던지 간에 관계없이 올바른 방식으로 전화를 걸 수 있도록 전화번호 내용을 수정하는 역할을 수행합니다.

아주 쉽게 Right Number 와 같은 역할을 수행하는 리시버를 만들어 본다면, 아마 다음과 같은 형태가 될 것 입니다. (당연히 메니페스트 파일에 ACTION_NEW_OUTGOING_CALL 를 수신한다고 등록시켜 두셔야 합니다.)
public class CallReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Original phone number is in the EXTRA_PHONE_NUMBER Intent extra.
    String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);

    if (shouldCancel(phoneNumber)) {
      // Cancel our call.
      setResultData(null);
    } else {
      // Use rewritten number as the result data.
      setResultData(reformatNumber(phoneNumber));
  }
}
 리시버는 정해진 규칙에 따라 브로드 캐스트 자체를 취소하거나 전화 번호를 변경하도록 구현되었습니다. 별다른 문제가 있을까요?  만일, 이 리시버가 ACTION_NEW_OUTGOING_CALL 을 수신하는 유일한 리시버라면 정확하게 원하는대로 동작할 것 입니다. 하지만 그렇지 않다면, 문제가 발생할 수 있습니다. 예를 들어, 이 리시버가 동작하기 전에 동작하는 또 하나의 상위 리시버가 이미 전화 번호를 변경하는 기능을 수행한다고 상상해 보시기 바랍니다.

올바른 방법으로 구현하기
위에서 제기한 문제를 해결하기 위해서는 다음과 같이 수정되어야 합니다.
public class CallReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Try to read the phone number from previous receivers.
    String phoneNumber = getResultData();

    if (phoneNumber == null) {
      // We could not find any previous data. Use the original phone number in this case.
      phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
    }

    if (shouldCancel(phoneNumber)) {
      // Cancel our call.
      setResultData(null);
    } else {
      // Use rewritten number as the result data.
      setResultData(reformatNumber(phoneNumber));
  }
}
두 가지 구현 방식의 가장 큰 차이점은 바로 상상력입니다. 두 번째 구현에서는 자신 외에 자신과 유사한 일을 수행하는 리시버가 존재할 수 있다는 점을 염두해 두고 코드가 작성되었습니다. 따라서, 위 코드 상에서는 우선 보다 상위의 Prioirty 를 갖고있는 리시버가 생성했을 수도 있는 전화 번호 정보를  getResultData() 메서드를 이용하여 확인 한 후, 그렇지 않은 경우에 한해서, EXTRA_PHONE_NUMBER 인텐트 익스트라 값을 활용하도록 구현되었습니다.

이게 정말 문제인가요?
 우리는 실재로 NEW_OUTGOING_CALL 를 가장 마지막에 처리하도록 구현된, 최하의 priority 를 갖는 리시버가 폰에 설치된 경우를 발견할 수 있었습니다. (이 Reciever 가 가장 마지막에 호출되게 되겠지요.) 그런데, 종종 이녀석이 기존에 다른 리시버들이 처리한 온 갖 유용한 일들을 깔끔하게 무시해 버리더군요.. 이런 경우, 문제를 해결할 수 있는 방안은 오직 한가지 뿐입니다. 바로, 여러분의 리시버도 최하의 priority 즉 0 값을 갖도록 하는 것 입니다. 0 priority 값을 갖는 리시버가 두 개가 있다는 점이 좀 이상해보일 수도 있지만, 어찌되었건 어플리케이션이 동작하기는 할 것 입니다. 하지만 이렇게 되면 NEW_OUTGOING_CALL 를 처리하는 아래의 명시적인 규칙을 위반하는 셈이되며 문제를 점점 더 복잡하게 만들 수도 있습니다.

"일관성을 유지 하기 위하여, 사용자가 전화 거는 것을 금지하기 위한 NEW_OUTGOING_CALL  리시버는 0 priority 를 가져야 한다. 그래야만 실재로 전화가 걸릴 최종 전화 번호를 확인 할 수 있다. 동시에 전화번호를 변경하는 기능을 수행하는 리시버는 반드시 0 보다 큰 priority 를 가져야 한다. 0 보다 작은 priority 값은 시스템이 사용하며, 이것을 사용하는 것은 문제를 일으킬 수 있다."
리시버 Priority 에 관에 알아 두어야 할 사항
  • NEW_OUTGOING_CALL 의 경우, 0 priority 는 전화 송신을 막기 위한 용도로만 사용되어야 한다. 
  • 같은 priority 를 갖는 리시버도 순서에 따라 한 번에 하나 씩 수행된다. 단 그 순서는 항상 다를 수 있다.
  • Priority 값으로 항상 0 보다 큰 값을 사용해야한다. 음수 값은 시스템에서만 사용되어야 하며, 이를 지키지 않으면 플랫폼 전체가 오작동 할 수 있다.

결론
 세상에는 다른 어플리케이션과 사이좋게 지내지 못하는 프로그램들이 있습니다. 혹시 이번 포스트를 읽으며, 자신이 작성한 프로그램도 그 중 한가지라는 생각이 드시는 분은 꼭 관련 내용을 수정해 주시면 좋겠습니다.그러면 안드로이드 개발자들도 그리고 사용자들도 좀더 행복해 질 것 입니다. 혹시, 다른 어플리케이션과 좀 더 친하게 지내고 싶으신 분들은 개발자 사이트의 Desigining for Seamless(번역) 포스트를 읽어 보셔도 좋겠습니다.


------------------------------------------------------------------------------
그렇지 않아도 최근에 Ordered Broadcast 를 사용할 필요가 있어서, 몇 가지 자료를 찾고 있었는데 마침 개발자 블로그에 관련된 포스트가 올라와서 부지런히 번역해보았습니다. 내용이 조금 빈거 같아서, 나름 몇 가지 기본적인 내용을 덧 붙였습니다;;; 혹시라도 잘못된 부분이 있을가 걱정이긴 한데 너그러이 용서해 주시길 바랍니다.


'Android > Android TIP' 카테고리의 다른 글

TextUtils.isEmpty Mockup 만들기 (JUnit)  (0) 2018.04.12
Implicit Intent broadcast using a custom permission  (0) 2018.04.10
DP and SP  (0) 2013.07.04
코딩 팁  (0) 2012.10.06
Android TIP  (0) 2012.09.28
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함