[안드로이드-그 한계를 넘어서] 8장. 브로드캐스트리시버

브로드캐스트리시버 및 설정변경

안드 시스템은 최대한 적은 전력을 소모함으로써 가능한 한 오랫동안 배터리를 사용할 있게 하려고한다. 하지만 안드로이드 어플에서 기기를 무척 자유롭게 제어할 수 있는 만큼 각기 다른 이벤트와 기기 변경사항에 잘 반응하게끔 개발자가 어플을 구현하는게 중요.

그런 측면에서 브로드캐스트리시버는 중요한다. 또한 브로드캐스트리시버에서 사용되는 이벤트들도 잘 알고있는것이 좋다. 예를 들어 기기에서 와이파이 연결이 끊기고 더 느린 무선 통신으로 연결될 경우 네트워크 호출 횟수를 줄이거나 좀 더 크기가 작은 사진을 내려 받을 수있다.

안드로이드에서는 어플에서 정의한 새 Intent액션이나 안드 API나 서드파드 어플에서 정의한 액션을 사용해 브로드캐스트를 보낼수 있다. 어플 내에서나 두 어플 사이에서 백그라운드로 이벤트를 보내고 싶을때는 이 방식이 좋다.

이 이벤트를 수신하는 방법에도 여러가지있다.

일부 이벤트는 브로드캐스트 Intent형태로 보내고, BroadcastReceiver를 통해 받는다. 또 어떤 이벤트에서는 자바 콜백을 구현해야 하기도 한다.

BroadcastReceiver

안드에서 이벤트를 전달하는 데 가장 많이 사용하는 방식은 Context.sendBroadcast()메서드를 사용해 BroadcastReceiver로 Intent객체를 전송하는 것.

표준 시스템 이벤트 대부분은 액션 문자열로 정의돼 있으며 Intent 클래스의 API문서에서 찾자.

예를 들어 사용자가 스마트폰을 충저닉에 연결/해제할 때마다 어플에서 알고 싶다면 Intent클래스에 정의된 두 브로드캐스트 액션(ACTION_POWER_DISCONNECTED) 및 (ACTION_POWER_CONNECTED)을 활용하면 된다.

다음 코드는 Intent객체를 수신하는 간단한 BroadcastReceiver코드.

여기서는 Context.startService()를 호출해 실제 작업을 수행하는 서비스.

 public class ChargerConnecedListener extends BroadcastReceiver {
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();

         if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
             context.startService(new Intent(MyService.ACTION_POWER_CONNECTED));
         } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
             context.startService(new Intent(MyService.ACTION_POWER_DISCONNECTED));
         }
     }
 }

BroadcastReceiver를 구현하는 기본 방법은 브로드캐스트를 매니페스트에 선언하는 것/ 브로드캐스트는 사용자가 어플을 시작하지 않았더라도 서비스에 알림을 전달 할 수 있다.

이 사실은 특히 사용자 상호작용 없이도 특정 시스템 이벤트에 따라 어플을 시작해야 하는 상황에 도움이 된다.

<receiver android:name=".ChargerConnectedListener">
            <intent-filter>
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
            </intent-filter>
</receiver>

BroadcastReceiver는 Activity및 Service내에서 프로그래밍적으로 등록할 수도 있다. 브로드캐스트 Intent중에는 프로그래밍적으로 등록할 때만 동작하는 브로드캐스트도 있고, 매니페스트에 선언할 때만 동작하는 브로드캐스트도 있다. 각 브로드캐스트 활용법은 역시나 API문서 공식 확인.

BroadcastReceiver를 등록/해제는 역시나 onPause(), onResume()

public class MyActivity extends Activity {
    private ChargerConnecedListener mPowerConnectionReceiver;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
        intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        mPowerConnectionReceiver = new ChargerConnecedListener();
        registerReceiver(mPowerConnectionReceiver, intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(mPowerConnectionReceiver);
    }

매니페스트가 아니라 프로그래밍적으로 BroadcastReceiver를 등록할 떄는 어플이 실행중이고 활성화되어 있을 때만 이벤트를 처리하려는 경우. 이렇게 하면 어플에서는 매니페스트에 브로드캐스트리시버를선언할 때 보다 적게 리소스를 소모할 수 있다.

어플에서 시스템 이벤트를 수신해야 한다면 항상 매니페스트에 BroadcastReceiver를 등록하는 방식과 Context.registerReceiver()를 통한 등록방식의 차이점을 고려해 필요 이상으로 리소스를 사용하지 않게끔 해야 한다.

로컬 BroadcastReceiver

한 어플의 프로세스 내에서만 브로드캐스르틑 주고받고 싶다면 범용적인 Context.sendBroadcast()메서드 대신 LocalBroadCastManager를 사용하는 것을 고려할 것.

 public void sendLocalBroadcase(Intent broadcastIntent) {
   LocalBroadCastManager localBroadCastManager =
      LocalBroadCastManager.getInstance(this);
      localBroadCastManager.sendBroadcast(broadcastIntent);
 }

  로컬 브로드캐스트를 수신하려면  코드에 나온 LocalBroadCastManager를 사용하면 .

      public class LocalBroadCastManagerDemo extends Activity {
          public static final String LOCAL_BROADCAST_ACTION = "localBroadCast";
          private BroadcastReceiver mLocalReceiver;


          @Override
          protected void onResume() {
              super.onResume();
              LocalBroadCastManager localBroadCastManager = LocalBroadCastManager.getInstance(this);
              IntentFilter intentFilter = new IntentFilter(LOCAL_BROADCAST_ACTION);
              mLocalReceiver = new BroadcastReceiver() {


              }
          }

          @Override
          protected void onPause() {
              super.onPause();
              LocalBroadCastManager localBroadCastManager = LocalBroadCastManager.getInstance(this);
              localBroadCastManager.unregisterReceiver(mLocalReceiver);
          }

로컬 브로드캐스트는 어플 내에서 메시지 및 상태를 전달하는 편리한 수단이다. 로컬브로드캐스트는 표준 전역 브로드캐스트보다 좀 더 효과적이며 오플 외부로 데이터를 유출하지 않다는 점에서 좀 더 안전핟. 로컬 브로드캐스트리시버는 일단 브로드캐스트리시버와 마찬가지로 사용후 항상 해제 해줄 것.

일반 브로드캐스트 및 순차 브로드캐스트

일반 브로드캐스트는 모든 리시버에 비동기적으로 전달되고, 특정한 순서 없이 수신된다. 이 브로드캐스트는 좀 더 효과적이지만 순차 브로드캐스트에 들어 있는 고급 기능 일부가 빠져 있다. 또, 일반 브로드캐스트에는 브로드캐스터로 피드백을 전달할 수 없다.

순차 브로드캐스트 지정한 순서대로 한 번에 등록된 리시버에 전달된다. 브로드캐스트를 수신하는 순서는 매니페스트의 관련 intent-filter태그에서 android:priority 속성을 설정해 지정할 수 있다. 순 서 브로드캐스트의 또 다른 기능으로 abortBroadcast(), setResultCode(), setResultData()를 호출해 리시버가 브로드캐스터에 전달되는 결과를 설정하거나 Intent가 큐상의 다음 리시버에 전달되지 않게 브로드캐스트를 중단할 수 있다는 것.

순차 브로드캐스트를 처리하는 리시버 구현체.

    public class OrderedReceiver extends BroadcastReceiver {
      public void onReceive(Context context, Intent intent) {
        if(isOrderedBroadcast()) {
          setResultCode(Activity.RESULT_OK);
          setResultData("simple response string");
          //현재 응답 extras를 가져오거나 null일 경우 새로 생성
          Bundle resultExtras = getResultExtras(true);
          // extras 응답에 대한 컴포넌트명을 설정
          resultExtras.putParcelable("componentName",
          new componentName(context, getClass()));
        }
      }
    }    

다음 코드에서는 순차 브로드캐스트를 전송하고 응답을 처리하는 법을 봅시다. 응답은 Context.sendBroadcast()로 BroadcastReceiver객체를 넘겨줌으로써 호출 받는다.

public void sendOrderedBroadcastAndGetResponse() {
  Intent intent = new Intent(ACTION_ORDERED_MESSAGE);
  //응답을 처리할 브로드캐스트 리시버
  BroadcastReceiver responseReceiver = new BroadcastReceiver(){

    public void onReceive(Context context, Intent intent) {
      String resultData = getResultData();
      Bundle resultExtras = getResultExtras(false);
      if( resultExtras != null) {
        ComponentName registeredComponent = resultExtras.getParcelable("ComponentName");
      }
      //할 일 : 응답 처리
    }
  };
  sendOrderedBroadcast(intent, responseReceiver, null,
                    RESULT_OK, null, null);
}

어플에서 순차 브로드캐스를 전송할 일은 거의 없지만 다른 어플과 통신할 경우에는 도움이 될 수 있다. 안드로이드 시스템에서 순차 브로드캐스트를 활용한 예로는 문자 메시지 수신을 어플이 감지하는 경우가 있다.

스티키 브로드캐스트

일반 브로드캐스트를 조금 변형한 스티키 브로드캐스트가 있음.

둘 사이의 차이점은 Context.sendStickyBroadcast()를 통해 보낸 Intent는 브로드캐스트가 완료된 후에도 “계속 남아진다”는 점이다.

지정 브로드캐스트

일반 브로드캐스트를 변형한 지정 브로드캐스트도 있음. 지정 브로드캐스트는 브로드캐스트 Intent에서 ComponentName을 설정해 브로드캐스트리시버를 명시적으로 설정할 수 있는 intent-filter 기능을 활용한다. ComponentName은 다음 코드에서 보듯 등록된 BroadcastReceiver의 클래스명과 패키지명의 조합으로 이뤄진다.

     public void sendDirectedBroadcast(String packageName, String className, String action) {
       Intent intent = new Intent(action);
       intent.setComponent(new ComponentName(packageName, className));
       sendBroadcast(directBroadcastIntent);
     }

이렇게 하면 다른 리시버에서 같은 Intent액션을 등록했더라도 등정 리시버에서만 이 브로드캐스트를 수신하게 된다. 이 방식이 제대로 동작하려면 리시버의 패키명과 클래스명을 모두 알아야한다는 점을 주의!!

지정 브로드캐스트 방식은 플러그인 기능을 제공하는 애플리케이션에서 유용하게 활용 활수 있다. 플래그인이 등록(설치)되면 플러그인은 지정 브로드캐스트를 활용해 메인 어플로 관련 정보를 보낼 수 있다.

리시버 활성화 및 비활성화

수신하려는 브로드캐스트를 매니페스트에 선언한 리시버에만 사용할 수 있는 경우 다른 방식으로 시스템 부담을 줄일 수 있다. PackageManager를 활용하면 어플에서 어플리케이션에서 컴포넌트를 활성화/비활성화할 수 있는데, 사용자가 특정 행동(이를테면 설정변경)을 할 때까지 리시버를 비활성화하려는 경우 이 기법이 도움이 된다.

다음의 두 메서드에서는 ComponentName을 기반으로 프로그래밍적으로 특정 컴포넌트를 활성화/비활성화하는 법을 볼 수 있다. 매니페스트에서는 컴포넌트의 기본값을 android:enabled=”false”로 지정하고 나중에 다음 코드를 사용해 값을 true로 바꾸면 된다.

     public void enableBroadcastReceiver() {
         PackageManager packageManager = getPackageManager();
         packageManager.setComponentEnabledSetting(new ComponentName(this, ChargerConnecedListener.class),
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
     }

     public void disableBroadcastReceiver() {
         PackageManager packageManager = getPackageManager();
         packageManager.setComponentEnabledSetting(new ComponentName(this, ChargerConnecedListener.class),
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
     }

이때 PackageManager.DONT_KILL_APP 을 setComponentEnabledSetting()의 마지막 파라미터로 사용한 점에 주의! 이는 플랫폼에서 ㅇㅓ플을 죽이지 않게 한다.

시스템 브로드캐스트 인텐트

안드로이드 API에서는 시스템 이벤트와 관련한 여러 브로드캐스 액션을 정의.

많은 정의가 존재 하므로 자세한건 API문서 확인하고, 여기서는

가장 많이 쓰는 3가지를 살펴보자.

  1. 어플리케이션 자동 시작

직접적으로 애플리케이션을 자동으로 실행하는 방법은 없다. 하지만 항상 호출되는 특정 이벤트를 수신하고 이를 활용해 애플리케이션을 시작할 수는 있다. 또, 과거 버전에서 새 버전으로 애플리케이션을 업그레이드할 때 발생하는 이벤트도 있다.

다음 코드에서는 Intent.ACTION_BOOT_COMPLETED 및 Intent.ACTION_MY_PACKAGE_REPLACED 브로드캐스트를 리스닝하는 리시버를 매니페스트에 선언하는 법. 참고로 여기서는 리시버가 기본으로 비활성화돼 있다. 이 설정은 특히 Intent.ACTION_BOOT_COMPLETED를 수신할 때 권장 이렇게 하지 않으면 매번 기기가 재부팅할 때마다 시스템 리소스를 낭비할 가능성이 있다.

    <receiver android:name=".xxxx" android:enabled="false">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED">
        <action android:name="android.intent.action.MY_PACKAGE_REPLACED">
<생략>
  1. 잠금 해제 및 화면 상태

사용자가 기기를 잠글 때 (즉 전원 버튼을 눌러 기기를 끌때) 현재 activity 는 onPause()호출을 받고 포커스를 읽어버렸음을 통보 받는다. 마찬가지로 잠금 화면이 해제되고 activity가 다시 포커스를 받으면 activity는 onResume()메서드가 호출, android:name=”android.intent.action.SCREEM_OFF android:name=”android.intent.action.SCREEM_ON android:name=”android.intent.action.USER_PRESENT

  1. 네트워크 및 연결 상태 변경

배터리 수준 / 기기의 전원 연결 같은 시스템 이벤트

http://www.journaldev.com/10356/android-broadcastreceiver-example-tutorial

http://www.vogella.com/tutorials/AndroidBroadcastReceiver/article.html