「Parcelable」とは、Parcelによりバイト配列化、またはバイト配列からインスタンスへと変換が可能になるインターフェイスです。Serializableと似ているのですが、Serializableとは異なり、抽象メソッドを実装しなければいけません。また、決まった名前のフィールドも定義しなければなりません。
以下は、サンプルアプリのParcelableの実装です。
public class CalculatorExpression implements Parcelable { // 【1】 public CalculatorElement mOp; public CalculatorElement mLhs; public CalculatorElement mRhs; // 【2】 public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public CalculatorExpression createFromParcel(Parcel in) { // 【3】 return new CalculatorExpression(in); } @Override public CalculatorExpression[] newArray(int size) { // 【4】 return new CalculatorExpression[size]; } }; public CalculatorExpression() { } private CalculatorExpression(Parcel in) { // 【5】 mOp = (CalculatorElement) in.readSerializable(); mLhs = (CalculatorElement) in.readSerializable(); mRhs = (CalculatorElement) in.readSerializable(); } @Override public void writeToParcel(Parcel out, int flags) { // 【6】 out.writeSerializable(mOp); out.writeSerializable(mLhs); out.writeSerializable(mRhs); } @Override public int describeContents() { // 【7】 return 0; } }
【1】のように、プロセス間通信で受け渡したいクラスにParcelableインターフェイスを実装します。
【2】のように、ParcelableにはstaticなCREATORという名前のParcelable.Creatorフィールドが必要です。【3】のように、Parcelからインスタンスを生成する実装を行います。【4】のように、配列を生成する実装を行います。
【5】は、Parcelからデータを読み込んでフィールドを初期化するコンストラクタです。【6】のように、Parcelにデータを書き込むメソッドを実装します。上記の【5】と【6】でデータを読み書きする順番は、常に同じでなければなりません。
【7】のように、もしこのParcelableにファイルディスクリプタが含まれているのであれば、「CONTENTS_FILE_DESCRIPTOR」を返します。そうでないならば「0」を返します。
Parcelableは、対象のインスタンスの状態が保持されている最低限のフィールドをParcelに書き込み、読み出せばよいわけです。
ようやくbindService()のライフサイクルの説明です。冒頭の図を再掲します。
bindService()の場合も、onCreate()はstartService()の場合と同様、1度しか呼び出されません。
次に、Service#onBind(Intent)が呼び出されます。
@Override public IBinder onBind(Intent intent) { return mStub; }
このメソッドは「IBinder」を返します。startService()の場合は、このメソッドは使わないのでnullを返せばよかったのですが、bindService()ではサービスの実体を返します。
サンプルのソースコードを見てもらえれば分かると思いますが、コードのほぼすべてが「mStub」の実装です。以下に抜粋して説明します。
// 【1】 private ICalculatorService.Stub mStub = new ICalculatorService.Stub() { @Override public int add(int lhs, int rhs) throws RemoteException { // 【2】 return lhs + rhs; } };
【1】のように、「ICalculatorService.Stub」というクラスを実装したクラスのインスタンスを生成します。ここでは「無名クラス」として実装しています。このICalculatorService.Stubというクラスは「ICalculatorService.aidl」から自動生成されるクラスで、ICalculatorService.aidlで定義したメソッドが「抽象メソッド」として定義されています。
【2】のように、インターフェイスで定義したメソッドを実装します。ここでは足し算を実装しています。
このICalculatorService.Stubは「Binder」というクラスを継承していて、BinderはIBinderを実装しています。「ParcelとAIDLで扱えるデータ型」の表を見直してもらえると分かりますが、AIDLでは、IBinderをプロセス間で受け渡しすることが可能で、onBind()で返したIBinderは、プロセスをまたがって呼び出し元に渡されます。
バインドしているサービスから切断する際には、「Context#unbindService(ServiceConnection)」を呼び出します。この際サービス側ではonUnbind()が呼び出されます。このメソッドは「boolean」を返すようになっており、trueを返すと、次に「bindService()」が呼び出された際に「onRebind()」が、falseを返すと「onBind()」が呼び出される仕組みです。
「onDestroy()」はバインドしているすべてのクライアントがなくなると呼び出されます。
startService()で実行するサービスは、バックグラウンドで動き続けるという特徴があります。bindService()で実行するサービスとは、相互通信が行えるという特徴があります。
startService()で実行するサービスは自身で停止できますが、bindService()で実行するサービスはクライアントの接続がなくなるまで停止すべきではありません。
startService()で実行するサービスは、どちらかというと指示を受けた後、自律的に動作するようなケースで使われることが多いのに対し、bindService()で実行するサービスはIPCで明確に指示されて動作するケースとなるでしょう。
startService()は、どのコンテキストでも呼び出せますが、bindService()はBroadcastReceiverのコンテキストからは呼び出せません。
サービスを使用して何かを実現する場合、startService()でもbindService()でもどちらでも実現可能であるケースが多いと思いますが、上記を参考に設計してみてください。
今回は、主にサービスのライフサイクルに関して解説しました。サンプルを添付したものの、今回は、ほとんどその内容に触れていませんでしたが、次回は、サンプルと照らし合わせながら、もう少し踏み込んで「サービス」を解説します。
Copyright © ITmedia, Inc. All Rights Reserved.