Android Local Service
1.Service
1.1 概要
Activityと同じように、Androidのコンポーネントの一つです。ただし、Activityと違って画面を持ちません。画面を持たない、いわゆる裏方として働くコンポーネントです。
画面を持つActivityはActivity間で画面の取り合いをしています。だから、処理時間が長いものは、画面を使っていないとみなされて、画面から消される恐れが常にあります。この時間はたった5秒です。
Serviceは、常にではありませんが、長い時間かかる処理を受け持ちます。Activityと関係なく実行されますので、たとえ、Activityが消滅しても処理を続けます。
Serviceはまた、「監視」にも向いています。一定の間隔で、対象を「見る」処理です。たとえば、株価の変化を監視する処理です。
この他にサービスの使用例を次に挙げます。、
例
音楽演奏(該当するActivityがなくなっても演奏を続ける)
・RSSのポーリング
・チャットのコネクション(チャットクライアントが電話にフォーカスを奪われても続行)
Serviceコンポーネントの基本クラスはandroid.app.Serviceクラスです。すべてのServiceはこのクラスを継承しなければなりません。
よく混乱されることですがServiceについて次の2点に注意して下さい。
●Serviceはそれがが属するアプリに対応するプロセスで動きます。Service用の別プロセスで動くわけではありません。
●Serviceはスレッドでもありません。主スレッドを離れて動くわけではありません。
Serviceは次の二つの機能を提供します。なお、単独型、結合型という呼び方はこの資料の中kでの呼び方です。正式な名称はないようです。
(1) 単独型
あるアプリが裏(バックグランド)でしてもらいたい仕事があると、そのときにAndroidに(そのアプリに所属する)Serviceの起動を依頼するService。
Serviceの利用者(例:Activity)がServiceの起動をAndroidに依頼し、その後、停止を依頼するまで又はService自身が停止するまで実行を続けます。通常、依頼した仕事が完了すると、どちらかの方法でServiceを停止します。
この形のサービスはContext.startService()メソッドを呼び出して開始します。
(2) 結合型
あるアプリが他のアプリに対して機能(メソッド)を公開しているService。
Serviceの利用者は、該当するサービスにバインド(結合)することをAndroidに依頼します。その後、Serviceとデータを受け渡しします。Service自身の起動と停止はServiceの利用者が制御できません。
この形のServiceはContext.bindService()メソッドを呼び出して、該当するServiceを利用し始めます。
Serviceは、本来はアプリ間で利用できるものですが、他のアプリから利用されないServiceも作成できます。アプリ間で利用できるサービスをリモートサービス、一つのアプリ内でしか利用できないサービスをローカルサービスと呼びます。
ローカルサービスの作りは、Java言語レベルでのクラス間のアクセスの機能が使えますのでリモートに比較して簡単です。リモートサービスの場合は、異なったプロセス間でデータをやり取りしなければなりませんので、Java言語レベルでのクラス間のアクセスの機能では間に合いません。このため、インターフェース記述言語を(IDL、特にAndroidの場合はAIDL)で定義したインタフェースを実装する必要があり、ローカルサービスに比べると複雑です。
図1.1はローカルサービスを利用する手順を図にしたものです
![]() |
図1.1 ローカルサービスの利用手順
①Serviceの利用者は、Context.bindService()メソッドを使ってbindをAndroidOSに要求します。
②AndroidOSはServiceのonBind()メソッドを呼び出します。
③onBindメソッドは型がIBinderの戻り値を戻します。この中に該当Serviceの参照を取得するメソッドを仕組んで起きます。IBinderはServiceの参照そのものではありません。
④AndroidOSはService利用者の定められたメソッド(onServiceConnected)を呼び出します。このメソッドを含むオブジェクトはContext.bindService()メソッドの引数としてAndroidOSに知らされています。
⑤Service利用者は、onServiceConnected()メソッドの引数から、Serviceの参照を取得します。
⑥Service利用者は、この参照を使って、Serviceのメソッドを呼び出します。
1.2 ライフサイクル ---生成と消滅
1.2.1 単独型Serviceのライフサイクル
Context.onStart()メソッドが呼ばれると、Serviceが生成されて、onCreate()が呼ばれ、さらにonStartCommand()(*1)が呼ばれる。複数回、Context.onStart()メソッドが呼ばれた場合は、onStartCommand()だけはそのたびに呼び出されます。
注*1 onStart()も使えるが、旧い仕様です。 onStart()については本資料では触れません。
Context.onStop()又はstopSelf()が呼ばれるまで走り続けます。複数回、Context.onStart()メソッドが呼ばれたとしても、一度だけContext.onStop()又はstopSelf()が呼ばれるとServiceは停止します。selfStop()は、開始された要求の処理が完了するまでServiceを停止しません。
Serviceの走り方に二つのモードがあります。これはonStartCommandの戻り値がこのモードを決めます。
(1)START_STICKY
明示的に停止されるまで走る。
(2)START_NOT_STICKY 又はSTART_REDELIVER_INTENT
依頼された処理が完了すると停止する。
1.2.2 結合型のライフサイクル
Context.bindService()を使うこともできます。これを使うと、Serviceと永続的に結合できます。Serviceがまだ生成されていない場合は生成します。そのときonCreate()メソッドが呼ばれますがonStartCommand()は呼ばれません。Context.bindService()の呼び出し側(クライアント)が、onBind(Intent)を呼び出すと IBinder(を実装した)オブジェクトを取得できます。これは、戻り値ではなく登録したリスナーのメソッドの引数として渡されます。これを使って、Serviceを呼び出すことができます。渡されたIBinder参照をクライアントが保持するか否かに関係なく、Serviceは走り続けます。通常IBinderのインタフェースは複雑なのでAIDL(Androidインタフェース記述言語)で記述するのが普通です。前に述べた通り、ローカルサービスの場合はAIDLは使いません。
サービスは、開始された状態であることもあり、またそのサービスへのバインドが存在する状態であることもあります。このような場合には、Androidシステムは開始されている状態か又はContext.BIND_AUTO_CREATEフラグ(*1)が付いたそのサービスへのバインドが存在する限りは、サービスを走らせ続けようとします。 これらの状況のどちらも成立しないと、サービスのonDestroy()メソッドが呼ばれます。そして、サービスは停止します。onDestroy()から戻る時点で、すべてのクリーンアップ(スレッドの停止、レシーバの登録抹消)が完了していなければなりません。
注*1 Context.bindService()メソッドの第3引数で指定する値の一つ。変数(定数)名が示す通り、該当するServiceが生成されていない場合、生成する。
2.実装例
2.1 サービスの実装法
サービスとその利用者(Activity)間のデータのやり取りのが、サービスを実装する上での課題です。
データの授受の方式は、大きく分けて次の3種類あります。これらは排他的ではありません。
(1)callback
(2)ブロードキャスト
(3)通知
ブロードキャストと通知については別途説明しますので、本資料には含まれません。本資料ではcallbackの方法だけを説明します。
callbackを使う場合も、具体的な実装の方法は一意ではありません。本資料では、Observerパターンを使った方法を説明します。さらに、Serviceの参照(リファレンス)の取得法で実装法が分かれます。それに着目して、実装法を次の3種類に分けます。
# |
種類 |
データの授受方式 |
適用範囲 |
1 |
staticメソッド式 |
リスナー登録メソッドをstaticにする。 |
同一アプリ内 |
2 |
ローカルonBind式 |
onBindを使う。 |
同一アプリ内 |
3 |
リモートサービス |
onBind + AIDL |
異なるアプリ(プロセス)間 |
本資料の説明は、ローカルサービスに限定します。リモートサービスは、インテントやブロードキャスト、通知を習得してから学習した方が理解しやすいからです。
Javaのおさらい Observerパターン
callbackを使ってActivityがServiceを利用する方法を図2.1に示します。クラスやメソッドの名称は任意です。
![]() |
図2.1 callbackを使ったデータの受け渡し
①ActivityはServiceの参照を取得します。
②ActivityはListenerを登録します。addListenerメソッドを呼び出します。
③Service内で処理が完了する等、「何か」が起きるとServiceのnotify()が呼び出されます。
④Serviceのnotify()メソッドは、登録されたリスナーのupdate()メソッドを呼び出し、「何か」が起きたことと、その情報を伝えます。
注Activityを実行しているスレッドとServiceを実行しているスレッドは同じものという保証はありません。したがって、Serviceの延長で実行されるupdate()メソッドを実行するスレッドはActivityのスレッドと異なる前提で処理を考えなければなりません。update()メソッドの中、又はその先で画面表示を更新する場合がありますが、この場合はUIスレッドに必ず切り替えなければなりません。スレッドについては、本資料のシリーズのスレッドの説明を参照して下さい。 |
リスナーは一つとは限りません。addListenerを呼び出して登録したListenerのupdate()メソッドが適宜、呼び出されます。登録さえすれば呼び出してもらえます。当然、update()メソッドの処理内容は、各Listenerによって異なります。こういう場合に次の手法が定番で使われます。
(1)Listenerインタフェースを作り、実際のListenerはこのインタフェースを実装する。
(2)Listener抽象クラスを作り、、実際のListenerはこのクラスを継承する。
一つのクラスが複数のインタフェースを実装できるので、この場合は、インタフェースの方が便利です。
図2.1ではListenerはActivityの外に置いていますが、Activityの中に置くのが普通です。
コード例は「付録 Aobserverパターンのコード例」を参照して下さい。Service内で「にせイベント」を起こす処理が含まれています。これはObserverパターンとは直接関係がありません。指定した時間間隔で、そのときの時刻をnotifyService()メソッドに渡しています・
2.2staticメソッド式実装方式の雛形
「staticメソッド式実装方式」という呼び方は本資料で通用するだけです。これは、Listenerを登録又は抹消するメソッドをstaticなメソッドで実装する方法です。staticなメソッド・フィールドを使う場合はメモリ・リークを起こすリスクがあります(*1)。その点で実用のアプリにはお薦めではありません(*2)。しかし、Serviceの本質はつかみやすいので本資料で取り上げます。
注*1 staticなモノを使うと、GC(ガベージコレクション)に関係するリスクがあります。onDestroyでstaticな変数は必ずnullに設定しなければなりません。忘れると、コンポーネント(例:Service)のオブジェクトへの参照がメモリ中に残り、そのオブジェクトはGCの対象になりません。
*2 Listenerの登録をIntent(に設定したデータ)を使う方法も考えられます。さらに、データの授受にcallback以外の方法も使えます。
Serviceの雛形を示します。もちろん、雛形からおおきくはずれて実装することもできます。最初のうちは、できるだけ雛形に従うのが得策と思います。
Serviceの雛形
/** * MyServiceTemplate * */ public class MyServiceTemplate extends Service { @Override public IBinder onBind(Intent intent) { return null; }
@Override public void onCreate() { super.onCreate(); // 1 } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 2 return START_STICKY;//明示的に停止されるまで走る。// or START_NOT_STICKY } @Override public void onDestroy() { super.onDestroy(); listeners.clear(); // 3 } /* * 利用者とデータの受け渡しをするためのコード * Observerパターンによる。 */
private void notifyEvent(Object o /* for example */){// 4 int i; for(i=0;i<listeners.size();i++){ listeners.get(i).update(o); // 5 実装 } }
private static List<IListenerTemplate> listeners = new ArrayList<IListenerTemplate>(); // 6変更 public static void addListener(IListenerTemplate listener) {// 7 変更 if(!listeners.contains(listener)){ listeners.add(listener); } } public static void deleteListener(IListenerTemplate listener) {// 8 変更 if(listeners.contains(listener)){ listeners.remove(listener); } } }//end class MyServiceTemplate |
基本クラスServiceから継承するメソッドは次の通りです。
・onBind()
・onCreate()
・onStartCommand()
・onDestroy()
このうち、onBind()メソッドは、staticメソッドを使う方法では使いませんので雛形の通りにします。その他のメソッドは、実装するServiceに応じて、必要なコードを書きます。コメントの1から3が該当する箇所です。
データの授受のために実装するメソッドは次の通りです。
・notifyEvent()
・addListener()
・deleteListener()
notifyEvent()メソッドは、実装するServiceの内容によって、引数の型と引数の個数が変わるはずです(コメント 4)。また、呼び出すupdate()メソッドも、その名前、引数、戻り値が変わります(コメント 5)。ロジックそのものは大きく変わらないはずです。その他の必要な処理は、notifyEvent()メソッドを呼び出す前に済ませておくべきです。
addListener()メソッドとdeleteListener()メソッドは、引数の型が変わるだけのはずです(コメント7、8)。
callbackしなくて済む場合は、notifyEvent()とaddListener()、deleteListener()は不要です。
Serviceの雛形の中に使われている、Listenerインタフェースの雛形を次に示します。
Listenerインタフェースの雛形
public interface IListenerTemplate { public void update(Object o); } |
update()メソッドの名前、引数は実装によって変わります。
次は、Serviceの雛形を使う利用者の雛形です。Activityを前提に記述していますが、他のコンポーネントで使う場合も同じです。
Serviceの利用者の雛形
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
// start MyServiceTemplate Intent svc = new Intent(this, MyServiceTemplate.class); startService(svc); MyServiceTemplate.addListener(listener); } @Override protected void onDestroy() { super.onDestroy();
// stop MyServiceTemplate MyServiceTemplate.deleteListener(listener); Intent svc = new Intent(this, MyServiceTemplate.class); stopService(svc); }
private IListenerTemplate listener = new IListenerTemplate() { @Override public void update(Object o) { System.out.println("listener update o=" + o); } }; |
AndroidManifest.xml
<application ...> <service android:name=".MyServiceTemplate" /> ... <activity ... ... </activity> </application> |
2.3staticメソッド式実装例
staticメソッド式実装方式の雛形を使って、「Javaのおさらい Observerパターン」で実装した処理をServiceとして実装します。雛形から変更した部分は太字で示しています。
Service
/* * based on MyServiceTemplate * */ public class MyFirstService extends Service { @Override public IBinder onBind(Intent intent) { return null; }
@Override public /* protected */ void onCreate() { super.onCreate(); startupService(); // 追加
}
@Override public /* protected */ void onDestroy() { super.onDestroy(); listeners.clear(); shutdownService(); // 追加
} /* * 利用者とデータの受け渡しをするためのコード * Observerパターンによる。 */
private void notifyEvent(String o){// Stringに変更 int i; for(i=0;i<listeners.size();i++){ listeners.get(i).update(o); // 実装 } }
private static List<IMyFirstListener> listeners = new ArrayList<IMyFirstListener>();// 変更 public static void addListener(IMyFirstListener listener) {// 変更 if(!listeners.contains(listener)){ listeners.add(listener); } } public static void deleteListener(IMyFirstListener listener) {// 変更 if(listeners.contains(listener)){ listeners.remove(listener); } }
/* * にせイベントをつくる */ private Timer timer = new Timer(); private static final long RUN_PERIOD = 5000; public void startupService(){ 「Javaのおさらい Observerパターン」と同じ内容 } public void shutdownService(){ // 該当スレッドを停止する。 「Javaのおさらい Observerパターン」と同じ内容 }
}//end class MyServiceTemplate |
Listenerインタフェース
public interface IMyFirstListener { public void update(String o); // 変更 } |
Activity
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
// start MyServiceTemplate Intent svc = new Intent(this, MyFirstService.class); startService(svc); MyFirstService.addListener(listener); } @Override protected void onDestroy() { super.onDestroy();
// stop MyService MyFirstService.deleteListener(listener); Intent svc = new Intent(this, MyFirstService.class); stopService(svc); }
private IMyFirstListener listener = new IMyFirstListener() { @Override public void update(String o) { System.out.println("listener update o=" + o); } }; |
AndroidManifest.xml
<application ...> <service android:name=".MyFirstService" /> ... <activity ... ... </activity> </application> |
注 この実装例では、結果を画面でなく、Logで出力していますので、update()メソッドの中でスレッドをUIスレッドに切り替えていません。画面に出す場合はUIスレッドに切り替えなければなりません。
2.4onBind()メソッド
onBind()メソッドはServiceの必須のメソッドです。staticメソッド式の場合は、実質的な処理は実装しませんでしたが、メソッドとしてはServiceに含めました。
onBind()メソッドは、Activity等のServiceの利用者がContext.bindService()メソッドを呼び出したときに、その先で呼び出されます(*1)。
バインド(結合)が実現したときに、onBind()メソッドの戻り値をServiceの利用者が受け取れます。バインド(結合)が実現すると、Context.bindService()メソッドの第2引数で指定したリスナーのメソッドが呼び出され、その引数にonBind()メソッドの戻り値が設定されているのです。
この戻り値(オブジェクト)の作り方はプログラム開発側に任されます。onBind()メソッド方式では、この戻り値にServiceのオブジェクトの参照を取得するメソッドを潜ませます。この戻り値が、即Serviceではありません。キャストしてServiceオブジェクトとして使う方法も応用としてありえますが、そのような例は見かけませんので、本資料でも使わないことにします。
注*1時期(タイミング)やスレッドの関係については不明です。Context.bindService()メソッドの呼び出し後、即、onBind()メソッドが呼ばれると考えない方が賢明です。また、スレッドはContext.bindService()メソッドを実行しているスレッドとは異なる前提で考えた方がいいでしょう。
さらに詳しく説明します。
Context.bindService()メソッドの仕様は次の通りです。
public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)
引数
# |
引数名 |
説明 |
1 |
service |
Intent。Service特定する情報 |
2 |
conn |
バインドが実現したとき/バインドが解消されたときのイベントリスナー |
3 |
int |
バインドの仕方を指示するコード。0又はBIND_AUTO_CREATE |
戻り値 boolean
バインドに成功した場合true。この場合、引数connのリスナーのメソッドが呼び出される。バインドに失敗した場合はfalse。この場合、引数connのリスナーのメソッドは呼び出されない。
第2引数ServiceConnection(android.contentパッケージ)はインタフェースです。
定義されているメソッドは次の二つです。
(1)onServiceConnected(ComponentName name, IBinder service)
引数
# |
引数名 |
説明 |
1 |
name |
コンポネントを特定する情報。詳細はandroid.content.ComponentName参照下さい。 |
2 |
service |
onBind()メソッドの戻り値。型はandroid.os.IBinder |
戻り値 void
Serviceへのバインドが完結したときに呼ばれます。第2引数がServiceと情報を授受するチャネルです。
(2)onServiceDisconnected(ComponentName name)
引数
# |
引数名 |
説明 |
1 |
name |
コンポネントを特定する情報。詳細はandroid.content.ComponentName参照下さい。 |
戻り値 void
Serviceへのバインドが終結したときに呼ばれます。
IBinderは、リモートサービスも実現できる仕様です。ローカルサービスの場合は、その機能は使いませんので説明は省略します。onBind()の戻り値(ServiceConnectionインタフェースのonServiceConnected()メソッドの第2引数)は、IBinder型でなければならないので、その型は使います。
IBinderを実装したクラスとしてBinder(android.os)クラスがあります。作成しようとしているIBinder型のクラスは、Binderクラスを継承することによって、IBinderで定義されているメソッドは記述しなくて済みます。
2.5 ローカルonBind式とstaticメソッドのコードの比較
staticメソッドのコードと比較しながら、さらに具体的に説明します。
staticメソッド式実装方式とローカルonBind式実装方式の大きな違いは、Serviceの参照の取得の仕方です。staticメソッド式実装方式ではServiceの参照を取得しないで、代わりにstaticなメソッドを使いました。ローカルサービスは、ServiceのonBind()の機能を使って、Serviceの参照の取得します。
Serviceのコード
public class MyLocalServiceTemplate extends Service {
private final IBinder mBinder = new LocalBinder(); ①
public class LocalBinder extends Binder { ② MyLocalServiceTemplate getService() { ③ return MyLocalServiceTemplate.this; } }
@Override public IBinder onBind(Intent intent) { return mBinder; ④ }
// onCreate(),onStartCommand(),onDestroy()については、 // staticメソッドと同じ(必須のメソッド) ⑤
// notifyEvent()についても、 // staticメソッドと同じ (必須ではないメソッド) ⑤
private public 処理はstaticメソッドと同じ } public 処理はstaticメソッドと同じ } } |
①IBinder型のオブジェクトの参照(このオブジェクトの目的は先に説明しました)
②IBinder型のクラスを内部クラスとして定義します。Javaの文法上は内部クラスである必要である必要は必ずしもありません。このクラスの使われ方として、内部クラスが妥当だということです。IBinderインタフェースを直接、実装するのでく、それを実装したクラスを継承しています。
③Serviceの利用者が、このServiceの参照を得るためのメソッドです。このメソッドはIBinderで規定されたものではありません。メソッド名、引数、処理内容は開発者に任されます。この例はServiceの原資料に掲載されているものです。
④onBind()メソッドの実装です。IBinder型を戻さなければなりません。内部クラスLocalBinderのインスタンスを戻しています。
⑤onCreate()、onStartCommand()、onDestroy()、notifyEvent()はstaticメソッド式の場合と変わりません。
⑥addListenerとdeleteListener、listenerのリストはstaticでなくなります。処理の内容は同じです。
リスナーのインタフェースも、staticメソッド式の場合と変わりません。
Serviceの利用者のコード
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
// start MyLocalServiceTemplate Intent svc = new Intent(this, MyLocalServiceTemplate.class); bindService(svc,
mConnection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy();
// stop MyLocalServiceTemplate unbindService(mConnection); }
private IListenerTemplate listener = new IListenerTemplate() { ⑥ 変更なし };
private MyLocalServiceTemplate mBoundService; ⑦
private ServiceConnection mConnection = new ServiceConnection() { ⑧ public void onServiceConnected(ComponentName className, IBinder service) { ⑨ mBoundService = ((MyLocalServiceTemplate.LocalBinder)service).getService(); ⑩
// Serviceをアクセスする処理 mBoundService.addListener(listener); ⑪ }
public void onServiceDisconnected(ComponentName className) { ⑫ // Serviceをアクセスする処理(後始末) mBoundService.deleteListener(listener); ⑬
mBoundService = null; ⑭ } }; |
①startService()をbindService()に変更します。
②削除します。対応する処理はonServiceConnected()メソッドに移ります。
③削除します。対応する処理はonServiceDisonnected()メソッドに移ります。
④バインドを解除する際、Intentは不要なので削除します。
⑤stopService()をunbind()に変更します。
⑥リスナーは変更ありません。
⑦IBinderを実装したServiceの内部クラスの参照です。
⑧バインドのリスナーを設定します。
⑨リスナーのうち、バインド完結イベントのリスナーです。
⑩第2引数IBinder型の変数を、IBinderを実装したServiceの内部クラスにキャストします。このクラスに、目的のメソッドgetService()が含まれています。IBinderインタフェースにはこのメソッドは定義されていないことに注意して下さい。
⑪Serviceのイベントのリスナーを登録します。②から移したものです。staticなメソッドでないことに注意して下さい。また、バインドに関するリスナーとは違うことにも注意して下さい。
⑫リスナーのうち、バインド解消イベントのリスナーです。
⑬Serviceのイベントのリスナーを抹消します。③から移したものです。
⑭バインドを解消したので、Serviceの内部クラスへの参照をnullにします。
AndroidManifest.xmlへの登録はstaticメソッド式と同じです。
2.6 ローカルonBind式 実装例
付録B onBind()メソッド式の実装例を参照下さい。
2.7 画面に表示する。
Serviceの実装のしくみを分かりやすくするために、表示はログもしくは標準出力に出してきました。これは実用のアプリではありえないことです。画面に出すことは、Serviceのしくみとは別の問題で、別途、演習してきました。これらを組み合わせると、画面に表示できます。参考までに、TextViewに表示する例を説明します。これはスレッドの演習の直接的な応用です。
ローカルonBind式の実装例をベースに説明します。
(1)main.xmlに結果を表示するためのTextViewを追加して下さい。id名はresultとします。
<TextView android:id="@+id/result" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="result" /> |
(2)Activityを変更します。
public class ... extends Activity {
private TextView tv; ① private Handler handler = new Handler(){ ② @Override public void handleMessage(Message msg){ Bundle map = msg.getData(); String s = map.getString("result"); showResult(s); } }; private void showResult(String s){ ③ tv.setText(s); }
@Override public void onCreate(Bundle savedInstanceState) { ... tv =(TextView)findViewById(R.id.result); ④ ... } ... private IMyFirstListener listener = new IMyFirstListener() { @Override public void update(String o) { System.out.println("listener update o=" + o); Message msg = Message.obtain(handler); ⑤ Bundle map = new Bundle(); map.putString("result",o); msg.setData(map); handler.sendMessage(msg); } }; ... } |
①表示するTextViewの参照のフィールドに追加します。
②Handlerを継承してhandleMessageを実装します。ここで、画面表示をします。handleMessage()メソッドは、このHandlerクラスをインスタンシェートしたスレッドで実行されるという点が大切な点です。
③実際に表示するメソッドを追加します。Handlerの中のhandleMessage()メソッドを簡潔にする工夫です。
④TextViewの参照のフィールドに値を設定します。
⑤標準出力だけでしたが、ここでMessageオブジェクトに必要な情報を設定して、それを引数にしてsendMessage()メソッドを呼び出します。その延長上で、スレッドを切り替えてhandleMessage()メソッドが実行されます。
テストする場合はAndroidManifest.xmlにServiceを登録して下さい。
2.8Serviceのインスタンスをstaticメソッドで取得する。
staticメソッド式はリスナーのメソッドをstaticにして、Serviceへの参照を取得しなくても済む方法でした。
Serviceの参照をstaticなメソッドで確保できるないものか考えます。
問題は、Serviceのインスタンスは、startService()を呼び出した直後に生成されているというわけではないことです。実際、確かめると分かります。ActivityのonCreate()メソッドでstartService()を実行してもその直後では参照は取得できません。onCreate()から戻った後にstartService()が実行されるようです(推測)。
推測の入る方式を、実用アプリ中で使ってはいけません。ただ、周辺を調査することは、より深く知る助けになります。この節は、その目的で書いていることを念頭において読んで下さい。
onStart()の中でも、まだServiceは生成されていないことが確認できた。たとえ、この方法でServiceのインスタンスが確保できても、停止するタイミングは分からない。
複雑になるが、別スレッドで監視する方法が考えられる。実際この方法でServiceの生成が確認できる。この方法は処理が複雑である。
単独型のServiceでコールバックが必要な場合は、staticメソッド式が妥当と考えます。まだ、説明していませんが、ブロードキャストや通知で済む場合は、callbackよりそちらを優先すべだと思います。
ところで、なぜ、単独型と結合型があるのでしょうか?onStartCommand()メソッドが呼び出されるか否かという違いがありますが、これ相当の処理をアプリ開発者側で補うのは難しくありません。単独型はオーバヘッドやリソース消化等の点で何かいいことがあるのでしょうか。外部仕様からは推測できません。
そういうことが不明なので決定的にはいえませんが、callbackが必要なら結合型で対応することを検討することも選択肢と思います。
付録
付録 A observerパターンのコード例
FakeActivity
public class FakeActivity {
private FakeService service;
package com.example.service;
public class FakeActivity {
private FakeService service;
private void initService(){ service = new FakeService(); service.addListener(listener);
}
private ITestListener listener = new ITestListener(){ public void update(String strDate){ System.out.println("listener update strDate=" + strDate); } };
public static void main(String[] args) { FakeActivity t = new FakeActivity(); t.initService();
}
} private void initService(){ service = new FakeService(); service.addListener(listener);
}
private ITestListener listener = new ITestListener(){ public void update(String strDate){ System.out.println("listener update strDate=" + strDate); } };
public static void main(String[] args) { FakeActivity t = new FakeActivity(); t.initService();
}
} |
FakeService
public class FakeService { private List<ITestListener> listeners = new ArrayList<ITestListener>();
public FakeService(){ startupService();// にせイベント作りを起動 }
public void addListener(ITestListener listener){ listeners.add(listener); } public void deleteListener(ITestListener listener){ // ... 省略 } public void notifyEvent(String strDate){ int i; for(i=0;i<listeners.size();i++){ listeners.get(i).update(strDate); }
}
/* * にせイベントをつくる */ private Timer timer = new Timer(); private static final long RUN_PERIOD = 5000; public void startupService(){ TimerTask timerTask = new TimerTask(){ // Timerで動くスレッドを起動する。 public void run(){ String strDate = (new Date()).toString(); notifyEvent(strDate); } }; timer.scheduleAtFixedRate(timerTask, 0L, RUN_PERIOD);
} public void shutdownService(){ // 該当スレッドを停止する。 if (timer != null){ timer.cancel(); timer = null; } } } |
ITestListener
public interface ITestListener { public void update(String strDate); } |
付録B onBind()メソッド式の実装例
MyFirstLocalService
public class MyFirstLocalService extends Service {
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder{ MyFirstLocalService getService() { return MyFirstLocalService.this; } }
@Override public IBinder onBind(Intent intent) { return mBinder; }
@Override public void onCreate() { super.onCreate(); startupService(); // 追加
}
@Override public void onDestroy() { super.onDestroy(); listeners.clear(); shutdownService(); // 追加
} /* * 利用者とデータの受け渡しをするためのコード * Observerパターンによる。 */
private void notifyEvent(String o /* for example */){// Stringに変更 int i; for(i=0;i<listeners.size();i++){ listeners.get(i).update(o); // 実装 } }
private List<IMyFirstListener> listeners = new ArrayList<IMyFirstListener>();// 変更 public void addListener(IMyFirstListener listener) {// 変更 if(!listeners.contains(listener)){ listeners.add(listener); } } public void deleteListener(IMyFirstListener listener) {// 変更 if(listeners.contains(listener)){ listeners.remove(listener); } }
/* * にせイベントをつくる */ private Timer timer = new Timer(); private static final long RUN_PERIOD = 5000; public void startupService(){ TimerTask timerTask = new TimerTask(){ // Timerで動くスレッドを起動する。 public void run(){ String strDate = (new Date()).toString(); notifyEvent(strDate); } }; timer.scheduleAtFixedRate(timerTask, 0L, RUN_PERIOD);
} public void shutdownService(){ // 該当スレッドを停止する。 if (timer != null){ timer.cancel(); timer = null; } }
}//end class MyServiceTemplate |
Activity
public class HelloService_0_5_onbind extends Activity {
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
// start MyServiceTemplate Intent svc = new Intent(this, MyFirstLocalService.class); bindService(svc, mConnection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy();
// stop MyService unbindService(mConnection); }
private IMyFirstListener listener = new IMyFirstListener() { @Override public void update(String o) { System.out.println("listener update o=" + o); } };
private MyFirstLocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName className, IBinder service) { mBoundService = ((MyFirstLocalService.LocalBinder)service).getService();
// Serviceをアクセスする処理 mBoundService.addListener(listener); }
public void onServiceDisconnected(ComponentName className){ // Serviceをアクセスする処理(後始末) mBoundService.deleteListener(listener);
mBoundService = null; } };
} |
IMyFirstListener
public interface IMyFirstListener { public void update(String o); // 変更 } |
AndroidManifest.xml
... <application android:icon="@drawable/icon" android:label="@string/app_name"> <service android:name=".MyFirstLocalService" /> <activity ... </activity> </application> ... |
以上