2010年1月17日日曜日

Android Serviceで困ったこと

例によって、マニュアルを読むと解決することが半分。
ソース落として中身をしっかり見れば解決するであろうことが半分。
とりあえず後者に関する内容です。

bindService() 直後にはサービスを利用できない。

具体例としては以下のようなやつはNG
private IService service;
private ServiceConnection conn = new ServiceConnection() {

 public void onServiceConnected(ComponentName arg0, IBinder arg1) {
  // TODO 自動生成されたメソッド・スタブ
  service = IService.Stub.asInterface(arg1);
 }

 public void onServiceDisconnected(ComponentName arg0) {
  // TODO 自動生成されたメソッド・スタブ
  service = null;
 }
};
private void onChk1(){
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);
 service.serviceAPI(); // ここで落ちます
}

ようはbindService を呼んですぐにonServiceConnected() まで到達しないということです。
上記のような場合、onChk1 を完全に抜けてからでないと絶対に到達しませんでした。

以下NG集

private void onChk2(){
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);

 while( service != null){ // 無限ループに陥る
  wait(1000);
 }
 service.serviceAPI();
}
private void onChk3(){
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);

 new Thread(){ // スレッド化しても無駄
  @Override
  public void run() {
   while( service != null){ // 無限ループに陥る
    wait(1000);
   }
   service.serviceAPI();
  }
 }.start();
}

ちなみにbindService と service.serviceAPI を別関数化し、スレッド化して呼んでもダメなものはダメでした。
もちろんonChk3 のようなスレッドの作り方が良くないのかもしれませんが。
その辺、javaに詳しい人なら即座に解決策がでるんでしょうか?

では、なんでこんなことをしようと思ったかと言うと、
・onCreate / onResume 時に サービスから初期値を取得したい
という状況が発生したからです。

一応以下の方法でそれっぽくなりますが、なんかもっと簡単な方法ないものでしょうか。
完全解決という感じではないし。
自分でメッセージループ管理しだすと、VCチックな感じも・・・。
ただ、この面倒な感じは、BREWアプリを想像させるような・・・。
まあBREWなんかと比較したら失礼ですね。あれはマジ終わってる。

onCreate でサービスをバインドし、初期値もサービスから取得っぽく実装
private IService service;
private ServiceConnection conn = new ServiceConnection() {
 // 略
};

private static final int _MY_BIND_SERVICE = 1;
private static final int _MY_INIT_DATA = 2;
private Handler mHandler = new Handler(){
 @Override
 public void dispatchMessage(Message msg) {
  // TODO 自動生成されたメソッド・スタブ
  switch( msg.what){
  case _MY_BIND_SERVICE:
   removeMessages(_MY_BIND_SERVICE);
   _bind();
   break;
  case _MY_INIT_DATA:
   removeMessages(_MY_INIT_DATA);
   _init_data();
   break;
  default:
   super.dispatchMessage(msg);
  }
 }
};
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 mHandler.sendEmptyMessage(_MY_BIND_SERVICE);
 mHandler.sendEmptyMessage(_MY_INIT_DATA);
}

private void _bind(){
 if( service != null){
  return;
 }
 Intent intent = new Intent(IService.class.getName());
 bindService(intent, conn, BIND_AUTO_CREATE);
}

private void _init_data(){
 if(service == null){
  mHandler.sendEmptyMessage(_MY_BIND_SERVICE);
  mHandler.sendEmptyMessageDelayed(_MY_INIT_DATA, 500);
  return;
 }
 service.serviceAPI(...);
 // 略
}



改めて書くとなんと強引な…。
しかもあくまで「onCreate内でやってるっぽい」だけで、普通にonCreateそのものは終わってますから。
そういう意味では完全解決ではありませんね。
removeMessagesを読んでいるのは、処理が重複しないように念のために呼んでいるだけです。

0 件のコメント:

コメントを投稿