Activity와 Service간의 통신


Local Broadcast Manager를 이용한 방법 (BR)

그냥 broadcast receiver를 등록해서 사용하는 방법은 다른앱에서도 해당 방송을 들을 수 있기 때문에 private data leak문제가 생긴다.

API 22 (android 5.1)부터 지원하는 Local Broadcast Manager방법으로 구현한다.

Main Activity

리시버 등록 코드

    @Override
    protected void onResume() {
        ...
        // action 이름이 "custom-event-name"으로 정의된 intent를 수신하게 된다.
        // observer의 이름은 mMessageReceiver이다.
        LocalBroadcastManager.getInstance(this).registerReceiver(
                mMessageReceiver, new IntentFilter("custom-event-name"));
    }

    @Override
    protected void onPause() {
         ...
        LocalBroadcastManager.getInstance(this).unregisterReceiver(
                mMessageReceiver);
    }

broadcast receiver 코드

// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        // Get extra data included in the Intent
        String message = intent.getStringExtra("message");
        Log.d("receiver", "Got message: " + message);
    }
};

Service 코드

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        sendMessage();
        return super.onStartCommand(intent, flags, startId);
    }

    private void sendMessage(){
        Log.d("messageService", "Broadcasting message");
        Intent intent = new Intent("custom-event-name");
        intent.putExtra("message", "This is my first message!");
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

실행 결과

실행 결과는 아래와 같이 버튼을 누르면 service가 실행되고 해당 service에서 local broadcast manager를 통해서sendBroadcast를 하면 activity에 등록된 receiver에서 이것을 수신해서 처리하는 방식으로 동작한다.

04-06 16:16:34.110 18075-18075/com.example.user.examactivityservicecommnunication D/messageService: Broadcasting message
04-06 16:16:34.110 18075-18075/com.example.user.examactivityservicecommnunication D/receiver: Got message: This is my first message!

Github 전체코드

Local Bind Service를 이용한 양방향 통신

전통적인 방법인 Bind Service를 이용한 방법을 다룬다.
Started Service까지도 같이 구현하면 Music Player에서 종종 쓰이는 패턴이 된다.
왜냐하면 백그라운드에서 음악이 재생되고 해당 엑티비티가 다시 살아나면 현재 재생되고 있는 음악이 리스트업 되어야 한다. 따라서Bind service로 데이터 통신도 되어야 하기 때문이다.

아래 코드는 간단히 엑티비티에서 서비스의 특정 callback을 호출하고 해당 서비스내의 콜백 함수는 다시 엑티비티의 callback함수를 호출하는식으로 작성 되었다.

main acitivity

   public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                Intent Service = new Intent(this, BindService.class);
                bindService(Service, mConnection, Context.BIND_AUTO_CREATE);
                break;
            case R.id.button2:
                mBindService.myServiceFunc();
        }
    }

    // service connection definition
    private ServiceConnection mConnection = new ServiceConnection() {

        // Called when the connection with the service is established
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            BindService.BindServiceBinder binder = (BindService.BindServiceBinder) service;
            mBindService = binder.getService(); // get service.
            mBindService.registerCallback(mCallback); // callback registration
        }
        // Called when the connection with the service disconnects unexpectedly
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBindService = null;
        }
    };

    // call below callback in service. it is running in Activity.
    private BindService.ICallback mCallback = new BindService.ICallback() {
        @Override
        public void remoteCall() {
            Log.d("MainActivity","called by service");
        }
    };

service

    private final IBinder mBinder = new BindServiceBinder();
    private ICallback mCallback;

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    // declare callback function
    public interface ICallback {
        public void remoteCall();
    }

    // for registration in activity
    public void registerCallback(ICallback cb){
        mCallback = cb;
    }

    // service contents
    public void myServiceFunc(){
        Log.d("BindService","called by Activity");

        // call callback in Activity
        mCallback.remoteCall();
    }

    // Declare inner class
    public class BindServiceBinder extends Binder {
        BindService getService(){
            return BindService.this; // return current service
        }
    }

실행결과

04-09 21:26:18.351 26984-26984/com.example.user.localbindservice D/BindService: called by Activity
04-09 21:26:18.351 26984-26984/com.example.user.localbindservice D/MainActivity: called by service

Github 전체 코드

ResultReceiver로 단방향 메시지 전달

ResultReceiver란 결국 Binder의 wrapper 함수를 의미한다.
따라서 ResultReceiver를 전달 함으로써 Service Activity가 서로 통신할 수 있다.

Activity 쪽 코드

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        syncMessage = (TextView)findViewById(R.id.textview);
        syncButton = (Button)findViewById(R.id.button);
        syncButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) { // (1)
        progressBar.setVisibility(View.VISIBLE);
        syncMessage.setText("sync start");

        Intent intent = new Intent(this, SyncService.class);
        intent.putExtra("RECEIVER", resultReceiver); //(2)
        startService(intent);
    }

    private Handler handler = new Handler();

    private ResultReceiver resultReceiver = new ResultReceiver(handler){ // (3)

        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);

            // SYNC_COMPLETED CODE = 1 
            if (resultCode == 1){  // (4)
                String msg = resultData.getString("msg");
                progressBar.setVisibility(View.GONE);
                syncMessage.setText("Done "+msg);
            }

        }
    };
  • (1): 동기화 버튼을 클릭 할 때를 가정 한다.
  • (3): ResultReceiver를 생성하고 onReceiveResult() 메서드를 오버라이드한다. ResultReceiver 생성자에는 handler 인스턴스를 넣을 수도, null로 할 수도 있다. Service의 백그라운드 스레드에서 ResultReceiver의 send()메시지를 호출하는데, 결과를 받는 쪽에서 UI를 업데이트 하기 때문에 Handler를 거쳐 메인 Looper의 MessageQueue에 Message를 넣은 것이다.
  • (4): SYNC_COMPLETED resultCode를 받으면 ProgressBar를 숨기고, 텍스트 메시지는 R.string.sync_ended로 변경한다.
  • (2): Intent Extra에 ResultReceiver 인스턴스를 전달한다.

서비스 쪽 코드

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mIntent = intent;

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("SyncService","SyncService started");

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Bundle bundle = new Bundle();
                final ResultReceiver receiver = mIntent.getParcelableExtra("RECEIVER"); // (1)
                bundle.putString("msg","Succeed !");
                receiver.send(1,bundle); // (2)
            }
        }).start();

        return START_NOT_STICKY;
    }
  • (1): Intent에서 ResultReceiver를 꺼내오고 작업을 마친후에
  • (2): send() 메서드로 SYNC_COMPLETED라는 결과를 보낸다.

실행 결과

아래의 실행결과는 network 싱크를 가정해서 service에서 5초 thread sleep한 후에 다시 그 결과를 bundle에 넣어서 반환해서 activity에서 그 결과를 표시하는 예제의 결과이다.

작성한 Github 코드

다른 예제 코드: ProAndroidDev: Using ResultReceiver to communicate with IntentService

참고자료

다양한 방법을 설명하고 있음

2012 05 31

말로만 간단히 설명

바인드된 서비스

Android pub, 2011.09.29

Android pub, 2011.10.05

AIDL을 이용한 Service <-> Activity간 Callback 통신

안드로이드 로컬서비스를 이용한 액티비티와 서비스간 통신

Local bind service를 이용한 방법


+ Recent posts