X

Serviceを使う(1) LocalServiceによる常駐型アプリ

Androidで常駐型アプリケーションを作成する場合に便利なServiceについてライフサイクル・使い方を解説します。サービスの利用例はステータス通知(Notification)を変化させる等をご確認ください。Serviceを使う(1)では簡単化のため、Remote Messenger Serviceを次回以降として、LocalServiceに特化して解説します。

  • Serviceのライフサイクル
    • onCreate / onStartCommand / onDestroyの3つの状態遷移
    • サービスの実行方法によってライフサイクルが異なる
  • サービスの実行方法はContext#startServiceとContext#bindServiceの2種類
  • startService/stopService
    • Service全般として実行中はServiceからActivityへIntentの発行が可能
    • サービス起動後はActivityからServiceを制御する経路がない
    • Serviceの生存期間はActivityに依存しない。明示的にstopServiceが呼ばれるまで動き続ける。
  • bindService/unbindService
    • バインド(bind)という仕組みを使い、ActvitiyとServiceでコネクションを確立する(接続する)
    • バインドを使うことでActivityからServiceを制御できる
    • Serviceの生存期間はコネクションに依存。コネクションが切断されるとServiceは終了する。

サンプルコード、詳細は以下から。はじめに一般的なサービスのライフサイクル、startServiceによるサービス起動を紹介します。バインドのサンプルコードは記事後半になるので、適宜読み飛ばしてください。

サービスのライフサイクル


Context#startServiceによる実行の場合、サービスのライフサイクルはonCreate / onStartCommand / onDestroy の3つのコールバックメソッドが呼ばれます。

  • public void onCreate()
  • public void onDestroy()
  • public int onStartCommand(Intent intent, int flags, int startId)

onCreateはサービスのインスタンス生成時(複数回startServiceを実行した場合、初回のみ)呼び出されます。onStartCommandメソッドではstartServiceで送られたIntentを受けとります。
stopServiceメソッドによるサービス終了のタイミングでonDestroyが呼ばれます。ActivityでstopServiceを呼ばず、アプリケーションを終了した場合はServiceは終了せず、バックグラウンドで動き続けます
※IntentはIntentを使って画面を遷移する(明示的Intent)を参照してください。

サンプルコード

サンプルとして以下の画像のようにServiceを実行するボタンを持つActivityを準備します

サービスの実装

MyService.java

public class MyService extends Service {

	static final String TAG="LocalService";

    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate");
    	Toast.makeText(this, "MyService#onCreate", Toast.LENGTH_SHORT).show();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand Received start id " + startId + ": " + intent);
    	Toast.makeText(this, "MyService#onStartCommand", Toast.LENGTH_SHORT).show();
        // 強制終了時、システムによる再起動を求める場合はSTART_STICKYを利用
        // 再起動が不要な場合はSTART_NOT_STICKYを利用する
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy");
    	Toast.makeText(this, "MyService#onDestroy", Toast.LENGTH_SHORT).show();
    }
}

6,12,20行目:コールバックメソッドのみ実装した簡単なServiceです。サービスを使う際は、AndroidManifestに記述を追加する必要があります。

AndroidManifest.xml

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="ServiceControllerActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name="MyService" />
    </application>

サービスを起動するActivityの実装

ServiceControllerActivity.java

public class ServiceControllerActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button btn = (Button) findViewById(R.id.StartButton);
        btn.setOnClickListener(btnListener);//リスナの登録

        btn  = (Button) findViewById(R.id.StopButton);
        btn.setOnClickListener(btnListener);//リスナの登録
    }

    private OnClickListener btnListener = new OnClickListener() {
        public void onClick(View v) {

        	switch(v.getId()){

        	case R.id.StartButton://startServiceでサービスを起動
                startService(new Intent(ServiceControllerActivity.this, MyService.class));
        		break;
        	case R.id.StopButton://stopServiceでサービスの終了
                stopService(new Intent(ServiceControllerActivity.this, MyService.class));
        		break;
        	}
        }
    };

13行目から始まるOnClickListener(ボタンが押下されたときに呼び出される)でサービスの起動/終了を行っています。
19行目、22行目でIntent(Context, Class)の形でIntentを生成してstartService/stopServiceの引数にして、サービス(ここではMyService)を呼び出します。

StartButton、StopButtonの順番で押下した際のログでライフサイクルを確認してみます。

Logcat

02-15 06:45:10.280: INFO/LocalService(1619): onCreate
02-15 06:45:10.290: INFO/LocalService(1619): onStartCommand Received start id 1: Intent { cmp=org.jpn.techbooster.sample.serviceActivity/.MyService }
02-15 06:45:18.170: INFO/LocalService(1619): onDestroy

StartButton、StartButton、StopButtonの順番で押下すると、onCreateメソッドの呼び出しが初回のみであることがわかります。
Logcat

02-15 06:45:19.530: INFO/LocalService(1619): onCreate
02-15 06:45:19.540: INFO/LocalService(1619): onStartCommand Received start id 1: Intent { cmp=org.jpn.techbooster.sample.serviceActivity/.MyService }
02-15 06:45:20.170: INFO/LocalService(1619): onStartCommand Received start id 2: Intent { cmp=org.jpn.techbooster.sample.serviceActivity/.MyService }
02-15 06:45:22.080: INFO/LocalService(1619): onDestroy

バインド利用時のサービスのライフサイクル

バインド利用時のライフサイクル startService利用時のライフサイクル

バインド(bind)という仕組みを使い、ActvitiyとServiceを接続するケースでは、ライフサイクルが多少異なります。図にある通り、onStartCommandメソッドが呼び出されることがありません。
通常のonCreate/onDestroyに加えて、バインド時は以下のコールバックメソッドを利用します。

  • public abstract IBinder onBind (Intent intent)
  • public boolean onUnbind (Intent intent)
  • public void onRebind (Intent intent)

onBindはバインド(接続時)、Unbindはバインド解除(切断時)に呼び出されます。onRebindメソッドは図中には現れませんが、Unbind後に再接続する場合、onRebindメソッドを利用することができます。使う際はonUnbind メソッドの返り値をtrueに設定します。

サンプルコード

図のActivitiy下2つのボタン部分とServiceにバインドを実装します。

バインドの実装

MyService.java

public class MyService extends Service {
   // onCreate , onStartCommand , onDestroy() はstartServiceと同じなので省略
    /*
     * 以下はBind時に必要なコード
     * */

    //サービスに接続するためのBinder
    public class MyServiceLocalBinder extends Binder {
		//サービスの取得
    	MyService getService() {
            return MyService.this;
        }
    }
    //Binderの生成
    private final IBinder mBinder = new MyServiceLocalBinder();

	@Override
	public IBinder onBind(Intent intent) {
    	Toast.makeText(this, "MyService#onBind"+ ": " + intent, Toast.LENGTH_SHORT).show();
        Log.i(TAG, "onBind" + ": " + intent);
		return mBinder;
	}

	@Override
	public void onRebind(Intent intent){
    	Toast.makeText(this, "MyService#onRebind"+ ": " + intent, Toast.LENGTH_SHORT).show();
        Log.i(TAG, "onRebind" + ": " + intent);
	}

	@Override
	public boolean onUnbind(Intent intent){
    	Toast.makeText(this, "MyService#onUnbind"+ ": " + intent, Toast.LENGTH_SHORT).show();
        Log.i(TAG, "onUnbind" + ": " + intent);

        //onUnbindをreturn trueでoverrideすると次回バインド時にonRebildが呼ばれる
		return true;
	}
}

18行目、25行目、31行目それぞれonBind、onRebind、onUnbindメソッドを実装、Toastを表示します。

サービスを起動するActivityの変更

ボタンのOnClickListenerにbindService/unbindServiceを追加します。
ServiceControllerActivity.java

    private OnClickListener btnListener = new OnClickListener() {
        public void onClick(View v) {

        	switch(v.getId()){

        	case R.id.StartButton://startServiceでサービス起動
                startService(new Intent(ServiceControllerActivity.this, MyService.class));
        		break;

        	case R.id.StopButton://stopServiceでサービス終了
                stopService(new Intent(ServiceControllerActivity.this, MyService.class));
        		break;

        	case R.id.BindButton://doBindService
        		doBindService();
        		break;

        	case R.id.UnbindButton://doUnbindService
        		doUnbindService();
        		break;

        	default:
        		break;
        	}

        }
    };

バインドするためのServiceConnectionを実装

バインドするためにはServiceConnectionを使います。Activity側でServiceConnectionと以下のServiceConnectionのメソッドを用意します。

  • public void onServiceConnected(ComponentName className, IBinder service)
  • public void onServiceDisconnected(ComponentName className)

ServiceConnection#onServiceConnectedメソッドはActivityとServiceのコネクションが確立した際に呼び出されます。
引数のIBinder serviceで、サービスのバインダーを受け取れます。バインダー経由でServiceのインスタンスを取得することが可能、つまりダイレクトにサービス側のメソッド呼び出しができるようになります。
ServiceConnection#onServiceDisconnectedメソッドはプロセスのクラッシュなど意図しないサービスの切断が発生した場合に利用されます(呼び出されること自体、あまり好ましい状況ではないでしょう)。

ServiceControllerActivity.java

    //取得したServiceの保存
    private MyService mBoundService;
	private boolean mIsBound;

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {

        	// サービスとの接続確立時に呼び出される
            Toast.makeText(ServiceControllerActivity.this, "Activity:onServiceConnected",
                    Toast.LENGTH_SHORT).show();

        	// サービスにはIBinder経由で#getService()してダイレクトにアクセス可能
            mBoundService = ((MyService.MyServiceLocalBinder)service).getService();

            //必要であればmBoundServiceを使ってバインドしたサービスへの制御を行う
        }

        public void onServiceDisconnected(ComponentName className) {
            // サービスとの切断(異常系処理)
        	// プロセスのクラッシュなど意図しないサービスの切断が発生した場合に呼ばれる。
            mBoundService = null;
            Toast.makeText(ServiceControllerActivity.this, "Activity:onServiceDisconnected",
                    Toast.LENGTH_SHORT).show();
        }
    };

    void doBindService() {
    	//サービスとの接続を確立する。明示的にServiceを指定
    	//(特定のサービスを指定する必要がある。他のアプリケーションから知ることができない = ローカルサービス)
        bindService(new Intent(ServiceControllerActivity.this,
        		MyService.class), mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }

    void doUnbindService() {
        if (mIsBound) {
            // コネクションの解除
            unbindService(mConnection);
            mIsBound = false;
        }
    }

サンプル内、doBindServiceメソッドはサービスにバインド、doUnbindServiceメソッドはサービスをアンバインド(切断)するメソッドです。

BindButton、UnbindButtonの順番で押下した際のログでライフサイクルを確認してみます。

Logcat

02-15 07:25:51.090: INFO/LocalService(1619): onCreate
02-15 07:25:51.110: INFO/LocalService(1619): onBind: Intent { cmp=org.jpn.techbooster.sample.serviceActivity/.MyService }
02-15 07:25:52.890: INFO/LocalService(1619): onUnbind: Intent { cmp=org.jpn.techbooster.sample.serviceActivity/.MyService }
02-15 07:25:52.890: INFO/LocalService(1619): onDestroy
mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org
Related Post