Service - Bindあり - ActivityからServiceの提供する(Compositeする)method呼び出し

前回に引き続きServiceの実装をしてみたいと思います。今回はServiceとのBindを試みたいと思います。
Bindする事によりActivityからServiceが提供する(Compositeする)methodを呼び出す事が出来ます。
前回のServiceの説明でした2の使用例になります。
ServiceとのBindする際に呼ばれるCallback method内でToastを表示するという単純な機能を実装します。

実装の要点をまとめます

  1. manifest xmlにservice tagを追加
  2. AIDL(Android Interface Definition Lanugage)というfileにServiceで使用するinterfaceを定義(Eclipseで保存するとgen folder以下に機能を実装するためのjava fileが自動生成されます)
  3. ServiceでAIDL fileに定義したinterfaceの実装
  4. Service#onBind()の戻り値としてNo.3で実装したclassのobjectを返す
  5. ActivityにServiceとBindするContext#bindService() methodを記述
  6. ActivityにServiceとUnbindするContext#unbindService() methodを記述
  7. ServiceConnection interfaceを実装したclassを作成
  8. 上記ServiceConnection#onServiceConnected()の引数として渡されるIBinder objectを使用しAIDLで定義されたinterfaceを取得
  9. 上記で取得したinterfaceを利用しAIDL fileで定義された(& Serviceで実装された)methodを使用

気づいた点をまとめます

  • ServiceConnection#onServiceDisconnect() methodはServiceを実行しているProcessがcrash、もしくはkillされないと呼ばれない
  • ServiceのCallback methodはUI thread上で実行される
  • Context#unbindService()はbindしていないServiceに対して呼ばれるとExceptionを返すので注意が必要

ServiceのCallback methodに関して

  • onCreate()はServiceが起動していなければContext#bindService()、Context#startService()を契機に呼ばれる
  • onBind()はContext#bindService()を契機に呼ばれる(一度、bindされると再び呼ばれることはありません)
  • onStartCommand()はContext#startService()が呼ばれる度に毎回呼ばれる(Bind中でも呼ばれます)
  • onRebind()は一度、bind/unbindをした後、Serviceがまだ起動中の時に再度、Context#bindService()が呼ばれると呼ばれる
  • onUnbind()はContext#unbindServiceを契機に呼ばれる
  • onDestroy()はContext#unbindService()、Context#stopService()、Service#stopSelf()が契機になって呼ばれる。bindもしくはstartが他のclientから実行されている場合にはonDestroy()は呼ばれない。Context#unbindService()を使用した場合にはonUnbind()のみ呼ばれる

それでは実装です

Manifest xml抜粋

<!-- manifest xmlにservice tagを追加する
     "android:name"属性にServiceの名前を定義(AIDLに定義するInterfaceの名前ではない)
     ServiceのProcessを分けたいなら"android:process"属性にProcess名を定義 -->
<service android:name=".MyService" android:process=":remote">

AIDL file

package com.android.practice;

//AIDL fileにServiceで使用するinterfaceを定義
//Eclipseで保存するとjava fileが生成されます
interface MyBindService{
	String getString();
}

Service

package com.android.practice;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;

public class MyService extends Service {
	
	private Context mContext;
	
	//ServiceでAIDL fileに定義したinterfaceの実装
	//ただし、生成されたjava fileのStub classを実装する
	private final MyBindService.Stub mMyBindService = new MyBindService.Stub() {
		
		public String getString() throws RemoteException {
			return "This string is provided by Service";
		}
	}; 
	
    //Service#onBind()の戻り値として生成したStub classのobjectを返す
	@Override
	public IBinder onBind(Intent arg0) {
		Toast.makeText(mContext, "onBind called by " 
                      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
		return mMyBindService;
	}

	@Override
	public boolean onUnbind(Intent intent) {
		Toast.makeText(mContext, "onUnbind called by "
                      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
		return true;
	}

	@Override
	public void onCreate() {
		mContext = this;
		Toast.makeText(mContext, "onCreate called by "
                      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
		super.onCreate();
	}

	@Override
	public void onDestroy() {
		Toast.makeText(mContext, "onDestroy called by "
                      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
		super.onDestroy();
	}

	@Override
	public void onRebind(Intent intent) {
		Toast.makeText(mContext, "onRebind called by "
                      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
		super.onRebind(intent);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Toast.makeText(mContext, "onStartCommand called by "
                      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
		return START_STICKY;
	}
}

Activity

package com.android.practice;

import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MyActivity extends Activity {
	
	private Context mContext;
	private ServiceConnection mConnection;
	private MyBindService mService;
	private TextView mTextView;
	private boolean mBind = false;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        
        setContentView(R.layout.main);
        mTextView = (TextView)findViewById(R.id.status_text);
        Button bindButton = (Button)findViewById(R.id.bind_button);
        Button unbindButton = (Button)findViewById(R.id.unbind_button);
        Button startButton = (Button)findViewById(R.id.start_button);
        Button stopButton = (Button)findViewById(R.id.stop_button);
        Button stringButton = (Button)findViewById(R.id.get_string_button);
        
        //ServiceConnection interfaceを実装したclassを作成
        mConnection = new ServiceConnection(){
        	
        //ServiceConnection#onServiceConnected()の引数として渡される
                //IBinder objectを使用しAIDLで定義されたinterfaceを取得
    		public void onServiceConnected(ComponentName name, IBinder service) {
    			mService = MyBindService.Stub.asInterface(service);
    			Toast.makeText(mContext, "ServiceConnection onServiceConnected : "
    			      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show(); 
				mBind = true;
    		}
    		
                //Serviceを動かしてるProcessがcrashするかkillされない限り呼ばれない
    		public void onServiceDisconnected(ComponentName name) {
    			mService = null;
    			Toast.makeText(mContext, "ServiceConnection onServiceDisConnected : "
    			      + Thread.currentThread().getName(), Toast.LENGTH_LONG).show();
    		}
    	};
    	
        bindButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				//Intentを作成
				Intent intent = new Intent(mContext, MyService.class);
				//ActivityにServiceとBindするContext#bindService() methodを記述
                                //3つ目の引数はServiceがcreateされていなかった場合にcreateするという意味
				bindService(intent, mConnection, BIND_AUTO_CREATE);
			}
        });
        
        unbindButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				if(mBind){
					//ActivityにServiceとUnbindするContext#unbindService() methodを記述
					unbindService(mConnection);
					mBind = false;
				}
			}
        });
        
        startButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				Intent intent = new Intent(mContext, MyService.class);
				startService(intent);
			}
        });
        
        stopButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				Intent intent = new Intent(mContext, MyService.class);
				stopService(intent);
			}
        });
        
        stringButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				try{
					if(mBind){
                                            //取得したinterfaceを利用しAIDL fileで定義された(& Serviceで実装された)methodを使用						                    
                                            mTextView.setText((CharSequence)mService.getString());
					}
				}catch(RemoteException e){
				}
			}
        });
    }
}

Serviceの実装、ややこしいです。。。Service Callback methodの呼ばれ方もしかり。
次回はServiceからのActivity Callback methodの呼びだしを試してみたいと思います。

HashTag #android, #java