リア充爆発日記

You don't even know what ria-ju really is.

AndroidのBound Serviceのまとめメモ

http://d.hatena.ne.jp/ria10/20121110/1352529426の続き。
http://developer.android.com/guide/components/bound-services.html
ここを読んでのメモ。

基本

  • http://d.hatena.ne.jp/ria10/20121110/1352529426に記載されているとおりBound Serviceはクライアント-サーバ形式のインターフェースを持つServiceで、Activityなどのコンポーネントと連携することができる。
  • Bound Serviceの提供にはonBind()をオーバライドしIBinderを継承したクラスを返しておく必要がある。
  • クライアント(Acitivityとか)はbindService()を呼ぶことによってServiceをバインドすることができる。
    • bindService()が呼ばれたときはServiceとのコネクションを監視するServiceConnectionの実装を提供する必要がある。
    • bindService()は即座に返り値なしでreturnされるが、システムはクライアントとServiceの接続を確立するとServiceConnectionのonServiceConnected() を呼び出し、IBinderを渡す。このIBinderインターフェースを通してクライアントとServiceは連携する。
      • 複数のクライアントがServiceに接続することができるが、onBind()が呼ばれるのは最初の一度だけで、その後はonBind()は呼ばれずに最初に作られたIBinderと同じものが返される。
  • 最後のクライアントがアンバインドされるとServiceは終了する。該当ServiceがstartService()によって起動してたら終了しない。(この意味がわからなかったらバックグラウンド処理に使うServiceのまとめメモを読んだほうがいい。)
  • Bound Serviceを実装するにあたって最も重要なことはonBind()で返されるIBinderの実装である。
  • IBinderの実装にあたってはいくつか方法があるのでここで紹介する。

Bound Serviceを作る

  • IBinderの実装方法は3つある。
    • Binderクラスの拡張
      • Serviceが自身のアプリケーションからのみ利用されることを想定したprivateなもので、クライアントと同じプロセス内で動く(普通はこの動き)のであれば、Binderクラスを拡張したものをonBind()で返すのがよい。
      • Serviceが自身のアプリケーションにとって、単なるバックグラウンド処理を担うだけのものであれば、この方法がオススメ。
      • この方法が適さない場合とは、Serviceが別のアプリケーションから利用される場合(もしくは特別な実装をした別プロセスからの利用)のみ。
    • Messengerを使う
      • 別アプリケーションとの連携がしたいならMessengerを使う。※今回僕がこの方法は必要ないので詳細は省いちゃう。
    • AIDLを使う
      • AIDL (Android Interface Definition Language)を使うやりかたはMessengerを使うやりかたのベースとなる方法。
      • この方法は相当複雑なのであまり使わないほうがいい。※ということでここも詳細は略。
Binderクラスの拡張
  • もっとも一般的である同一アプリケーション同一プロセスで動くServiceを作る場合はBinderクラスを拡張する。
  • 以下セットアップ方法
    1. Service内でBinderクラスインスタンスを作る。そのクラスは以下のいずれかの条件を満たすこと。
      • クライアントから呼び出せるpublicメソッドが含まれ、
    2. 上記の条件を満たしたBinderをonBind()で返す
    3. クライアント側ではonServiceConnected() でBinderを受け取り、このBinderに実装されたメソッドを介してServiceとやり取りする。
  • 以下は、"クライアントから呼び出せるpublicメソッドを含んだServiceインスタンス(つまり自分自身)を返す"サンプル。
public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

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

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}
  • この例ではBinder(LocalBinder)はgetService()をクライアントに提供している。
  • クライアントはこのgetService()を通してServiceを利用できる。以下はその利用サンプル。
public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

Messengerを使う

現状僕が必要ないので省略。必要になったときにここを埋めます。。。







Serviceにバインドする

  • クライアントはbindService()を使うことによってServiceをバインドする。
  • バインディングは非同期に行われるため、bindService()は即座に返り値なしにreturnする。
  • IBinderを受け取るにはクライアントはServiceConnectionのインスタンスを作ってbindService()に渡す。
  • ServiceConnectionはIBinderを渡すためのコールバックメソッドを実装しておく。
  • Activity,Service,Content ProviderのみがServiceにバインドすることができる。ブロードキャストレシーバからはバインドできない。
  • ということで、クライアントからServiceにバインドするには・・・
    1. ServiceConnectionを実装する
      • 実装においては以下の2つのメソッドをオーバーライドする必要がある
        • onServiceConnected()
          • システムはonBind()で返されるIBinderをクライアントに渡すためにこのメソッドを呼び出す
        • onServiceDisconnected()
          • Serviceがkillされたときやクラッシュしたときなど、Serviceとのコネクションが予期せず失われた時に呼ばれる。クライアントがアンバインドしたときは呼ばれない。
    2. bindService()を呼び、ServiceConnectionの実装を渡す。
    3. onServiceConnected()が呼ばれた時から、提供されたIBinderを介してServiceとの連携ができるようになる。
    4. コネクションを切るときはunbindService()を呼ぶ。クライアントがdestroyされればServiceからもアンバインドされるが、Serviceが不要になったタイミングでアンバインドすること。
  • 以下は前述の「Binderクラスの拡張」の項で作成したServiceとコネクションを張るサンプル。
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};
  • このServiceConnectionによって、クライアントはbindService()を呼び出せばServiceに繋げることができる。
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • bindService()の最初の引数はバインドするServiceを指定したIntent。
  • 二番目はServiceConnectionオブジェクト。
  • 最後の三番目はバインドオプション。通常はBIND_AUTO_CREATE。その他にBIND_DEBUG_UNBINDBIND_NOT_FOREGROUND、もしくはどれでもない"0"がある。
補足
  • 以下、重要な補足事項。
    • コネクションが切れた時に投げられるDeadObjectExceptionを常に処理すること。
    • オブジェクトはプロセス間に渡って参照がカウントされる。
    • バインドとアンバインドは極力セットで利用すること。

※詳細は省くので原文を参照。





Bound Serviceのライフサイクル管理

 -Bound Serviceは最後のクライアントがアンバインドされると終了する。startService()によって起動してたら終了しない。

  • このように"started"かつ"bound"なServiceのライフサイクルは複雑になるので、以下の図を参照しつつ、ちゃんと管理すること。(超省いたので詳細が気になる人は原文参照のこと。)




終わり。