Service - Bindあり - ServiceからのCallback
さて前回に引き続きServiceの実装を試してみます。
今回はServiceからのCallbackを利用出来る様にしてみたいと思います。
BackgroundでServiceを走らせておいて「音楽の再生が終わったよ」というようなServiceで行っていた処理に何らかのEventが発生したらActivityに通知したいというような時に使用できます。
今回、実現する機能はServiceからのCallbackをもとにTextViewに表示するStringをupdateするというものです。
実装の要点をまとめます
Activity側
- Activity用のAIDLにCallback interfaceを定義
- ActivityにServiceからのCallbackを受け取るためのCallback interfaceを実装
- ActivityにServiceとBindするContext#bindService() methodを記述
- ActivityにServiceとUnbindするContext#unbindService() methodを記述
- ServiceConnection interfaceを実装したclassを作成
- 上記ServiceConnection#onServiceConnected()の引数として渡されるIBinder objectを使用しService用のAIDLで定義されたinterfaceを取得
- 上記で取得したinterfaceを利用しAIDLで定義されたmethodにてObserver登録/解除
Service側
- manifest xmlにservice tagを追加
- Service用のAIDLにCallback interfaceをObserver登録/解除するinterfaceを定義
- ServiceでAIDL fileに定義したinterfaceの実装
- RemoteCallbackListというClassを用意しCallback Interfaceを保持する
- Observer登録method内でRemoteCallbackList#register() methodで引数に渡されたCallback interfaceを登録する
- Observer解除method内でRemoteCallbackList#register() methodで引数に渡されたCallback interfaceを削除する
- Service#onBind()の戻り値としてNo.3で実装したclassのobjectを返す
- Callback intefaceのCallback methodを呼び出す為にRemoteCallbackList#beginBroadcat()でCallbackの準備
- RemoteCallbackList#getBroadcastItem()でCallback interfaceを取り出しCallback methodを呼び出す
- RemoteCallbackList#finishBroadcast()でbroadcast stateをclear
気づいた点をまとめます
- AIDLで定義されたmethodはBinder thread上で呼ばれます。ですので、それらのmethod内で直接はUIの操作は出来ません。Activityで実装したCallback interfaceだろうが、Serviceで実装したObserver登録/解除のmethodだろうが同じです(ここではUI threadで生成したHandlerを使用してUIを更新してます)
- AIDL内ではimport文は自動生成されないので自前で記述する
んで、実装です
Callback interfaceを定義したAIDL(Activity用)
package com.android.practice; //Activityで実装する用のCallback interfaceを定義 interface MyServiceObserver{ void onDataReady(String message); }
Activity
package com.android.practice; 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.RemoteException; import android.util.Log; 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 static final String TAG = "MyActivity"; private static final int TEXT_UPDATE = 0; private Context mContext; private Handler mHandler; private MyServiceObserver mObserver; 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); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case TEXT_UPDATE: mTextView.setText((String)msg.obj); } } }; //ActivityにServiceからのCallbackを受け取るためのCallback interfaceを実装 mObserver = new MyServiceObserver.Stub() { //Callback methodを実装 public void onDataReady(String message) throws RemoteException { Log.d(TAG, "onDataReady called by " + Thread.currentThread().getName()); //AIDLに定義したmethodはBinder thread上で実行されるのでUIの操作は出来ない //UIをupdateするためHandlerを使用 Message msg = mHandler.obtainMessage(TEXT_UPDATE, message); mHandler.sendMessage(msg); } }; //ServiceConnectionを拡張したclassを実装する mConnection = new ServiceConnection(){ //ServiceConnection#onServiceConntected()の引数で渡される //IBinder objectを利用しAIDLで定義したInterfaceを取得 public void onServiceConnected(ComponentName name, IBinder service) { mService = MyBindService.Stub.asInterface(service); Toast.makeText(mContext, "ServiceConnection onServiceConnected called by " + Thread.currentThread().getName(), Toast.LENGTH_LONG).show(); try{ //取得したinterfaceを利用しService用のAIDL fileで定義されたmethodでObserver登録/解除 mService.setObserver(mObserver); }catch(RemoteException e){ } mBind = true; } //Serviceを動かしてるProcessがcrashするかkillされない限り呼ばれない public void onServiceDisconnected(ComponentName name) { mService = null; Toast.makeText(mContext, "ServiceConnection onServiceDisConnected calle by " + Thread.currentThread().getName(), Toast.LENGTH_LONG).show(); } }; bindButton.setOnClickListener(new OnClickListener(){ public void onClick(View v) { //Intentを作成 Intent intent = new Intent(mContext, MyService.class); //Context#bindService()でServiceとのConnectionを確率 bindService(intent, mConnection, BIND_AUTO_CREATE); } }); unbindButton.setOnClickListener(new OnClickListener(){ public void onClick(View v) { if(mBind){ try { //取得したinterfaceを利用しService用のAIDL fileで定義されたmethodでObserver登録/解除 mService.removeObserver(mObserver); } catch (RemoteException e) { } //Context#UnbindService()でServiceとのConnectionを破棄 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){ mTextView.setText((CharSequence)mService.getString()); } }catch(RemoteException e){ } } }); } }
Observerを登録/解除するmethodを定義するAIDL(Service用)
package com.android.practice; //AIDL内ではimport文は自動生成されないので自前で記述する import com.android.practice.MyServiceObserver; //AIDL fileにServiceで使用するinterfaceを定義 //Eclipseで保存するとjava fileが生成されます interface MyBindService{ String getString(); //AIDL fileで定義されるmethodの引数、戻り値に指定出来るのはprimitive型、String、 //List、Map、CharSequence、その他のAIDLに定義されたinterface、Parcelable interfaceを実装したclass //MyServiceObserverはAIDLで定義されているのでOK void setObserver(MyServiceObserver observer); void removeObserver(MyServiceObserver observer); }
Service
package com.android.practice; 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.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; public class MyService extends Service { private static final String TAG = "MyService"; private static final int TIMER_TASK = 0; private static final long DELAY = 1000L; private Context mContext; private Handler mHandler; private int mCount = 1; //RemoteCallbackListというClassを用意しCallback Interfaceを保持する private RemoteCallbackList<MyServiceObserver> mObservers = new RemoteCallbackList<MyServiceObserver>(); //ServiceでAIDL fileに定義したinterfaceの実装 //ただし、生成されたjava fileのStub classを実装する private final MyBindService.Stub mMyBindService = new MyBindService.Stub() { public String getString() throws RemoteException { Log.d(TAG, "getString called by " + Thread.currentThread().getName()); return "This string is provided by Service"; } //Observer登録method内でRemoteCallbackList#register() methodで引数に渡されたCallback interfaceを登録する public void setObserver(MyServiceObserver observer) throws RemoteException { Log.d(TAG, "setObserver called by " + Thread.currentThread().getName()); mObservers.register(observer); } //Observer解除method内でRemoteCallbackList#register() methodで引数に渡されたCallback interfaceを削除する public void removeObserver(MyServiceObserver observer) throws RemoteException { Log.d(TAG, "removeObserver called by " + Thread.currentThread().getName()); mObservers.unregister(observer); } }; //生成したStub classをService#onBind()でreturnする @Override public IBinder onBind(Intent arg0) { Toast.makeText(mContext, "onBind called by " + Thread.currentThread().getName(), Toast.LENGTH_LONG).show(); mHandler.sendEmptyMessage(TIMER_TASK); 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; mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case TIMER_TASK: //Callback intefaceのCallback methodを呼び出す為に //RemoteCallbackList#beginBroadcat()でCallbackの準備 int observerNum = mObservers.beginBroadcast(); for(int i = 0; i < observerNum; i++){ try { //RemoteCallbackList#getBroadcastItem()でCallback interfaceを取り出し //Callback methodを呼び出す mObservers.getBroadcastItem(i).onDataReady( "This is the text provided by MyService. Count: " + mCount); } catch (RemoteException e) { } } //RemoteCallbackList#finishBroadcast()でbroadcast stateをclear mObservers.finishBroadcast(); mCount++; sendEmptyMessageDelayed(TIMER_TASK, DELAY); } } }; 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; } }
というような感じです。もう一つMessengerというものを使用しActivityとService間でやりとりをする方法があるようです。
次回、それを実装してみるかどうかは乞うご期待!