Service - Messengerを使ったProcess間通信

前回、密かに予告していましたが、今日はMessengerを使用したServiceとのやり取りを実装したいと思います。今回も単純にServiceからのCallbackを受けて画面に表示されているTextを更新するということを試してみたいと思います。Android developer siteで紹介しているものの簡易版になります

Messengerとは

MessengerはHandlerに紐づけられます。HandlerにMessengerを紐づけることにより、あるProcess内で動いてるHandlerと別のProcess内で動いているHandlerの間でMessage objectを使用したProcess間通信を可能にします。AIDLでinterfaceを定義する代わりにMessengerを使ったServiceとのProcess間通信をする事ができます

実装の要点を下記にまとめます

Activity
  1. Handlerを作成
    • Handler#handleMessage()をoverrideしServiceからのMessageを受け取った際の処理を実装
  2. 作成したHandlerからMessengerを作成
  3. Context#bindService()でServiceとのbindを実装
  4. Context#unbindService()でServiceとのunbind処理を実装
  5. ServiceConnectionを拡張したclassを実装
    • ServiceConnection#onServiceConnected()で渡される引数、IBinderからServiceのHandlerに紐づけられるMessengerを取得
    • ServiceからのCallbackを受け取る為にMessage objectを作成しMessage.replyTo fieldにNo.2で作成したMessangerを格納
    • 作成したMessage objectをServiceのHandlerに送信する為にServiceのMessenger、Messenger#send() methodを記述
    • 必要であればServiceConnection#onServiceDisconnected()にCallback時の処理を実装
  6. ServiceからのCallbackを受け取らないようにする為の処理を実装
    • Message objectを作成しMessage.replyTo fieldにNo.2で作成したMessangerを格納
    • 作成したMessage objectをServiceのHandlerに送信する為にServiceのMessenger、Messenger#send() methodを記述
Service
  1. manifest xmlにservice tagを追加
  2. ActivityのMessengerを保持するためのListを作成
  3. Handlerを作成
    • Handler#handleMessage()をoverrideしActivityからのMessageを受け取った際の処理を実装
      • Message.replyToに格納されたActivityのMessengerをNo.1で作成したListに格納/削除する処理を実装
      • Listに格納したMessengerのMessenger#send() methodを使用しActivityへのCallbackを実装
  4. 作成したHandlerからMessengerを作成
  5. Service#onBind()の戻り値としてMessenger#getBinder()で取得したIBinderを返す

それでは実装です

Activity

package com.android.practice4;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class MyActivity extends Activity {
	
	private static final String TAG = "MyActivity";
	
	private Context mContext;
        //Activity用のHandler
	private Handler mActivityHndlr;
        //Activity用のMessenger
	private Messenger mActivityMsgr;
        //Service用のMessenger
	private Messenger mServiceMsgr;
	private ServiceConnection mConnection;

	private boolean mIsBound;
	private TextView mTextView;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTextView = (TextView) findViewById(R.id.text_view);
        mContext = this;
        //Handlerを作成
        mActivityHndlr = new ActivityHandler();
        //作成したHandlerからMessengerを作成
        mActivityMsgr = new Messenger(mActivityHndlr);
        //ServiceConnectionを拡張したclassを実装
        mConnection = new ServiceConnection(){
                //ServiceConnection#onServiceConnected()で渡される引数、IBinderから
                //ServiceのHandlerに紐づけられるMessengerを取得
    		public void onServiceConnected(ComponentName arg0, IBinder arg1) {
    			mServiceMsgr = new Messenger(arg1);
    			mTextView.setText("Attached");
    			try {   
                                //ServiceからのCallbackを受け取る為にMessage objectを作成し
                //Message.replyTo fieldにNo.2で作成したMessangerを格納
                                Message msg = Message.obtain(null, MyService.SET_LISTENER);
    				msg.replyTo = mActivityMsgr;
                //Message objectをServiceのHandlerに送信する為に
                                //ServiceのMessenger、Messenger#send() methodを記述
    				mServiceMsgr.send(msg);
    			} catch (RemoteException e) {
    				e.printStackTrace();
    			}
    			Toast.makeText(mContext, "onServiceConnected", Toast.LENGTH_SHORT).show();
    		}
                //必要であればServiceConnection#onServiceDisconnected()にCallback時の処理を実装
    		public void onServiceDisconnected(ComponentName name) {
    			mServiceMsgr = null;
    			mTextView.setText("Disconnected");
    			Toast.makeText(mContext, "onServiceDisconnected", Toast.LENGTH_SHORT).show();
    		}
        	
        };
    }
    
    @Override
	protected void onResume() {
		super.onResume();
        Intent intent = new Intent(this, MyService.class);
                //Context#bindService()でServiceとのbindを実装
		bindService(intent, mConnection, BIND_AUTO_CREATE);
		mIsBound = true;
		mTextView.setText("Bound");
	}
    
	@Override
	protected void onPause() {
		super.onPause();
		if(mIsBound){
			if(mServiceMsgr != null){
                                //ServiceからのCallbackを受け取らないようにする為の処理を実装
                                //Message objectを作成しMessage.replyTo fieldにNo.2で作成したMessangerを格納
				Message msg = Message.obtain(null, MyService.UNSET_LISTENER);
                                //作成したMessage objectをServiceのHandlerに送信する為に
                                //ServiceのMessenger、Messenger#send() methodを記述
				msg.replyTo = mActivityMsgr;
				try {
					mServiceMsgr.send(msg);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
                        //Context#unbindService()でServiceとのunbind処理を実装
			unbindService(mConnection);
			mIsBound = false;
			mTextView.setText("Unbound");
		}
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
	}
	
	class ActivityHandler extends Handler {
            //Handler#handleMessage()をoverrideしServiceからのMessageを受け取った際の処理を実装
	    @Override
	    public void handleMessage(Message msg) {
	        switch (msg.what) {
	            case MyService.TEXT_UPDATE:
	                mTextView.setText("Received from service: " + msg.arg1);
	                break;
	            default:
	                super.handleMessage(msg);
	        }
	    }
	}
}

manifest xml抜粋

<!-- manifest xmlにservice tagを追加 -->
<service android:name=".MyService" android:process=":remote"/>

Service

package com.android.practice4;

import java.util.ArrayList;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;

public class MyService extends Service {
	
	private static final String TAG = "MyService";
	
	public static final int SET_LISTENER = 0;
	public static final int UNSET_LISTENER = 1;
	public static final int TEXT_UPDATE = 2;
        
        //Service用のHandler	
	private Handler mServiceHndlr;
    //Service用のMessenger
	private Messenger mServiceMsgr;
	private Context mContext;
        //ActivityのMessengerを保持するためのListを作成
	private ArrayList<Messenger> mListenerMsgrs;
	private int mValue;
	private int mCount;
	
	@Override
	public void onCreate() {
		super.onCreate();
		mContext = this;
                //ActivityのMessengerを保持するためのListを作成
		mListenerMsgrs = new ArrayList<Messenger>();
                //Handlerを作成
		mServiceHndlr = new ServiceHandler();
                //作成したHandlerからMessengerを作成
		mServiceMsgr = new Messenger(mServiceHndlr);
		mCount = 1;
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return super.onStartCommand(intent, flags, startId);
	}
	
	@Override
	public IBinder onBind(Intent arg0) {
		Toast.makeText(mContext, "onBind", Toast.LENGTH_SHORT).show();
		mServiceHndlr.sendEmptyMessage(TEXT_UPDATE);
                //Service#onBind()の戻り値としてMessenger#getBinder()で取得したIBinderを返す
		return mServiceMsgr.getBinder();
	}
	
	@Override
	public boolean onUnbind(Intent intent) {
		return super.onUnbind(intent);
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
	}
        
        //Handler#handleMessage()をoverrideしActivityからのMessageを受け取った際の処理を実装	
	class ServiceHandler extends Handler{
		@Override
                public void handleMessage(Message msg) {
                      switch (msg.what) {
                      case SET_LISTENER:
                           //Message.replyToに格納されたActivityのMessengerを
                           //No.1で作成したListに格納する処理を実装
                           mListenerMsgrs.add(msg.replyTo);
                           break;
                      case UNSET_LISTENER:
                           //Message.replyToに格納されたActivityのMessengerを
                           //No.1で作成したListから削除する処理を実装
                	   mListenerMsgrs.remove(msg.replyTo);
                           break;
                      case TEXT_UPDATE:
                           mValue = msg.arg1;
                           for (int i = mListenerMsgrs.size() - 1; i >= 0; i--) {
                                try {
                                     //Listに格納したMessengerのMessenger#send() methodを使用し
                                     //ActivityへのCallbackを実装
                        	     mListenerMsgrs.get(i).send(Message.obtain(null,
                                         TEXT_UPDATE, (mValue + mCount), 0));
                                } catch (RemoteException e) {
                        	     mListenerMsgrs.remove(i);
                                }
                           }
                           mCount++;
                           mServiceHndlr.sendEmptyMessageDelayed(TEXT_UPDATE, 3000L);
                           break;
                      default:
                           super.handleMessage(msg);
                }
            }
	}
}

AIDLを書くよりも楽にServiceとのやり取りが出来たと思います。ようはActivity、Service、それぞれ紐づけられたHandlerにお互いのMessengerを通してMessageを送信しあうというイメージですね。

HashTag #android, #java