My Photo

« Androidスマホの時刻合わせ | Main | FF11 シーフでアットワスラッグNMに挑んだ »

October 30, 2011

Androidアプリ開発メモ033:サービス その5:Messengerを使用したバインドされたサービス

サービスについての以前の記事:
Androidアプリ開発メモ021:サービス
Androidアプリ開発メモ030:サービス その2
Androidアプリ開発メモ031:サービス その3:IntentService
Androidアプリ開発メモ032:サービス その4:バインドされたサービス

Messengerの利用

異なるプロセスから利用できるサービスは、Messengerを使って実装することができる。
コンポーネントクライアントがサービスと異なるプロセスの場合、コンポーネントクライアントでServiceConnection#onServiceConnected()のIBinderを使ってもサービスの参照を得ることはできない。
なのでクライアントコンポーネントがサービスのメソッドを呼び出すのではなく、MessengerとHandlerでMessageを相互にやり取りする。
具体的には以下のようにする。

  • サービスでクライアントからそれぞれのコールバックを受け取るHandlerを実装する。
  • HandlerはMessengerオブジェクト(これはHandlerへの参照となる)を作成するために使用される。
  • MessengerはサービスがonBind()からクライアントに返すIBinderを作成する。
  • クライアントはIBinderを使ってクライアントがMessageオブジェクトをサービスに送信するために使用するMessenger(サービスのハンドラのHandler参照となる)をインスタンス化する。
  • サービスはそのHandler、具体的にはhandleMessage()メソッドで、それぞれのMessagerを受け取る。

MessengerとHandler

Handlerを指すMessengerを作成し、Messengerを別のプロセスに渡すことによってプロセス間の通信をすることができる。

android.os.Messengerクラスのコンストラクタ・メソッド

public Messenger(Handler target)
与えられたHandlerを指す新しいMessengerを作成する。

public Messenger (IBinder target) 
raw IBinder からMessengerを作成する。IBinderはgetBinder()で明白に読み出されたものである。

public IBinder getBinder()
このMessengerが関連するHandlerと通信するために使うIBinderを読み出す。

public void send(Message message)
このMessengerのHandlerへMessageを送信する。

android.os.Handlerクラスのメソッド
public void handleMessage(Message msg)
サブクラスはメッセージを受けるようにこれを実装しなければならない。

Message

MessageはMessengerからHandlerに送られるデータのクラスである。
追加の2つのintフィールド、追加のオブジェクトのフィールドがある。多くのケースでこれらの追加のフィールドは割り当てをしなくてもよい。

主なフィールド

public int arg1
public int arg2
public Object obj
public Messenger replyTo このメッセージへの返信が送られる任意のMessenger(?)
pulic int what 受信者がこのメッセージがなんについてであるか識別できるユーザー定義メッセージコード

public static Message obtain()
新しいMessageインスタンスをglobal poolから作成する。多くの場合、新しいオブジェクトの割り当てを回避することを許す。

public static Message obtain(Handler h, int what, int arg1, int arg2)
obtain()と同じだが、ターゲット、what、arg1、arg2メンバーをセットする。

ApiDemoの com.example.android.apis.app.MessengerService の概要は以下。
  • Handlerを作り、
  • そのHandlerをコンストラクタの引数にしてMessengerを作り、
  • onBind()は、そのMessengerのgetBinder()の戻りのIBinderを返している

ApiDemoの com.example.android.apis.app.MessengerServiceActivity の概要は以下。
  • Handlerを作り、
  • そのHandlerをコンストラクタの引数にしてMessenger(replyTo用)を作る
  • onServiceConnected()が呼ばれると、引数のIBinderをコンストラクタの引数にしてMessenger(送信用)を作り、このMessengerでメッセージを2つ送る。1つはクライアント登録コマンドでreplyToにMessenger(replyTo用)をセットしている。もう1つは値登録コマンドでreplyToには何もセットしていない。

以下、ApiDemoのMessengerService.javaとMessengerServiceActivityのコード。コメント、import、パッケージ名など少し変えている。
package com.example.messengerserviceexample;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;

import java.util.ArrayList;

/**
 * API Demoのコードほぼそのまま。
 * コメントを削ったり、import文を削っただけ。 
 */
public class MessengerService extends Service {

  NotificationManager mNM;
  /** 登録されたクライアントのメッセンジャーを保持するリスト */
  ArrayList mClients = new ArrayList();
  /** クライアントにセットされた最後の値を保持する */
  int mValue = 0;
  
  /**
   * サービスにクライアント登録コマンド。クライアントはサービスからコールバックを受信する。
   * メッセージのreplyToフィールドはクライアントのメッセンジャーでなければならない。
   * このサービスをバインドする別のアプリのためにpublicにした。
   */
  public static final int MSG_REGISTER_CLIENT = 1;
  
  /**
   * サービスにクライアント登録解除コマンド。クライアントはサービスからのコールバック受信を止める。
   * メッセージのreplyToフィールドはクライアントのメッセンジャーでなければならない。
   * このサービスをバインドする別のアプリのためにpublicにした。
   */
  public static final int MSG_UNREGISTER_CLIENT = 2;
  
  /**
   * サービスに値セットコマンド。新しい値を供給するためにこれはサービスに送られる。
   * そして新しい値とともに登録されたクライアントどれでもサービスによって送られる。
   * このサービスをバインドする別のアプリのためにpublicにした。
   */
  public static final int MSG_SET_VALUE = 3;
  
  /**
   * クライアントからやってくるメッセージのハンドラ
   */
  class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      case MSG_REGISTER_CLIENT:
        // replyToのメッセンジャーをリストに追加する
        mClients.add(msg.replyTo);
        Log.v("MESSENGER_SERVICE", "MSG_REGISTER_CLIENT:replyTo=" + msg.replyTo);
        break;
      case MSG_UNREGISTER_CLIENT:
        // replyToのメッセンジャーをリストから削除する
        mClients.remove(msg.replyTo);
        Log.v("MESSENGER_SERVICE", "MSG_UNREGISTER_CLIENT:replyTo=" + msg.replyTo);
        break;
      case MSG_SET_VALUE:
        // 送られてきた値を登録されているすべてのクライアントに送る
        mValue = msg.arg1;
        Log.v("MESSENGER_SERVICE", "MSG_SET_VALUE:replyTo=" + msg.replyTo);
        for (int i = mClients.size() - 1; i >= 0; i--) {
          try {
            mClients.get(i).send(
                Message.obtain(null, MSG_SET_VALUE, mValue, 0));
          } catch (RemoteException e) {
            // クライアントが死んでいる。それをリストから除去する。
            // ループの内側でしても安全なようにリストを後ろから前に通過する。
            mClients.remove(i);
          }
        }
        break;
      default:
        super.handleMessage(msg);
      }
    }
  }
  
  /**
   * クライアントのために公開しているターゲット(?)、IncomingHandlerにメッセージを送る。
   */
  final Messenger mMessenger = new Messenger(new IncomingHandler());
  
  @Override
  public void onCreate() {
    Log.v("MESSENGER_SERVICE", "onCreate()");
    
    mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

    // 開始のノーティフィケーションを表示
    showNotification();
  }

  @Override
  public void onDestroy() {
    Log.v("MESSENGER_SERVICE", "onDestroy()");
    
    // 永続的なノーティフィケーションをキャンセル
    mNM.cancel(R.string.remote_service_started);

    // 停止を伝える
    Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
  }
  
  /**
   * サービスにバインドするとき、サービスへメッセージを
   * 送るためのメッセンジャーへのインターフェースを返す。
   */
  @Override
  public IBinder onBind(Intent intent) {
    Log.v("MESSENGER_SERVICE", "onBind()");
    
    return mMessenger.getBinder();
  }

  /**
   * サービスが動作している間ノーティフィケーションを表示する。
   */
  private void showNotification() {
    // このサンプルでは、tickerとexpanded notificationで同じでテキストを使う
    CharSequence text = getText(R.string.remote_service_started);

    // アイコン、スクロールするテキスト、タイムスタンプをセット
    Notification notification
      = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis());

    // ユーザーがこのノーティフィケーションを選択した場合にアクティビティをランチするPendingIntent
    PendingIntent contentIntent
      = PendingIntent.getActivity(this, 0,
          new Intent(this, MessengerServiceActivity.class), 0);

    // ノーティフィケーションパネルで表示するビューの情報をセットする
    notification.setLatestEventInfo(this, getText(R.string.remote_service_label),
        text, contentIntent);

    // ノーティフィケーションを送る
    // 我々は文字列IDを使用する。なぜならそれはユニークな数だからだ。後でキャンセルするときにそれを使用する。
    mNM.notify(R.string.remote_service_started, notification);
  }
}

package com.example.messengerserviceexample;

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.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

/**
 * API Demoのコードほぼそのまま。
 * 元のコードはpublic static な入れ子クラスでAcitivityを実装していたが
 * 入れ子じゃない外側のクラスにした。
 */
public class MessengerServiceActivity extends Activity {
  
  /** サービスと通信するためのメッセンジャー */
  Messenger mService = null;
  /** サービスにバインドを呼んだかを示すフラグ */
  boolean mIsBound;
  /** 状態を表示するためのテキストビュー */
  TextView mCallbackText;
  
  /**
   * サービスからやってくるメッセージのハンドラ
   */
  class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      case MessengerService.MSG_SET_VALUE:
        mCallbackText.setText("Received from service: " + msg.arg1);
        break;
      default:
        super.handleMessage(msg);
      }
    }
  }
  
  /**
   * クライアントがIncomingHandlerにメッセージを送るターゲット、われわれが公開した(?)
   */
  final Messenger mMessenger = new Messenger(new IncomingHandler());
        
  /**
   * サービスのメインインタフェースと相互に作用するためのクラス
   */
  private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className,
        IBinder service) {
      
      mService = new Messenger(service);
      mCallbackText.setText("Attached.");

      // サービスと接続している限りサービスを監視したい
      try {
        Message msg = Message.obtain(null,
            MessengerService.MSG_REGISTER_CLIENT);
        msg.replyTo = mMessenger;
        mService.send(msg);
        
        // 例としてある値を与える
        msg = Message.obtain(null,
            MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
        mService.send(msg);
      } catch (RemoteException e) {
      }
      
      // サンプルの一環として、何が起きたかユーザーに表示する
      Toast.makeText(MessengerServiceActivity.this, R.string.remote_service_connected,
          Toast.LENGTH_SHORT).show();
    }
    
    public void onServiceDisconnected(ComponentName className) {
      // これはサービスのコネクションが不意に切断された場合
      // -- すなわちプロセスがクラッシュした場合に呼ばれる。
      mService = null;
      mCallbackText.setText("Disconnected.");
      
      // サンプルの一環として、何が起きたかユーザーに表示する
      Toast.makeText(MessengerServiceActivity.this, R.string.remote_service_disconnected,
          Toast.LENGTH_SHORT).show();
    }
  };
  
  void doBindService() {
    // サービスのコネクションを確立する。明示的なクラス名を使う。
    // なぜなら他のアプリケーションが私たちのコンポーネントを
    // 交換できるようにできるようにする理由がありません。
    bindService(new Intent(MessengerServiceActivity.this, 
        MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
    mIsBound = true;
    mCallbackText.setText("Binding.");
  }
  
  void doUnbindService() {
    if (mIsBound) {
      // サービスを受信し、それゆえに登録している場合は、
      // ここでは、登録を解除する時間である。
      if (mService != null) {
        try {
          Message msg = Message.obtain(null,
              MessengerService.MSG_UNREGISTER_CLIENT);
          msg.replyTo = mMessenger;
          mService.send(msg);
        } catch (RemoteException e) {
          // There is nothing special we need to do if the service
          // has crashed.
        }
      }
      
      // Detach our existing connection.
      unbindService(mConnection);
      mIsBound = false;
      mCallbackText.setText("Unbinding.");
    }
  }
  
  /**
   * 標準的なこのアクティビティの初期化。UIをセットアップし、それから
   * 何かする前にユーザーがそれを突くのを待ちます。(?)
   */
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    setContentView(R.layout.main);
    
    // ボタンのクリックを見張る
    Button button = (Button)findViewById(R.id.bind);
    button.setOnClickListener(mBindListener);
    button = (Button)findViewById(R.id.unbind);
    button.setOnClickListener(mUnbindListener);
    
    mCallbackText = (TextView)findViewById(R.id.callback);
    mCallbackText.setText("Not attached.");
  }
  
  private OnClickListener mBindListener = new OnClickListener() {
    public void onClick(View v) {
      doBindService();
    }
  };
  
  private OnClickListener mUnbindListener = new OnClickListener() {
    public void onClick(View v) {
      doUnbindService();
    }
  };
}

他のプロセスからバインドを許可する場合は、AndroidManifest.xml以下のようにintent-fileter要素を加える。
<service android:name=".MessengerService">
  <intent-filter>
    <action android:name="com.example.messengerserviceexample.MessengerService" />
  </intent-filter>
</service>

service要素にandroid:process属性を追加すると、サービスを別プロセスにできる。
android:process属性の値は、コロンで始まる値だとサービスのプロセスがアプリケーションでプライベートとなり、小文字で始まる場合(ドットで始まる場合も?)はグローバルプロセスになる。プライベートとかグローバルとかの意味はわからないが^^;

上記のサービスにバインドするアクティビティのコード。MessengerServiceActivityとほぼ同じ。bindService()の引数のIntentを作るときの引数を文字列にしている。クラスを渡したらなんかNoClassDefFoundErrorが起きた。
プロジェクトのプロパティのプロジェクト参照でMessengerServiceをチェックし、Javaのビルド・パスのプロジェクトタブでMessengerServiceを追加。
package com.example.messengerserviceclient;

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.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.example.messengerserviceexample.MessengerService;

/**
 * 他のアプリからMessengerServiceにバインドするアクティビティ。
 * API Demoのコードほぼそのまま。
 * 元のコードはpublic static な入れ子クラスでAcitivityを実装していたが
 * 入れ子じゃない外側のクラスにした。
 */
public class MessengerServiceClient extends Activity {
  
  /** サービスと通信するためのメッセンジャー */
  Messenger mService = null;
  /** サービスにバインドを呼んだかを示すフラグ */
  boolean mIsBound;
  /** 状態を表示するためのテキストビュー */
  TextView mCallbackText;
  
  /**
   * サービスからやってくるメッセージのハンドラ
   */
  class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      case MessengerService.MSG_SET_VALUE:
        mCallbackText.setText("Received from service: " + msg.arg1);
        break;
      default:
        super.handleMessage(msg);
      }
    }
  }
  
  /**
   * クライアントがIncomingHandlerにメッセージを送るターゲット、われわれが公開した(?)
   */
  final Messenger mMessenger = new Messenger(new IncomingHandler());
        
  /**
   * サービスのメインインタフェースと相互に作用するためのクラス
   */
  private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className,
        IBinder service) {
      
      mService = new Messenger(service);
      mCallbackText.setText("Attached.");

      // サービスと接続している限りサービスを監視したい
      try {
        Message msg = Message.obtain(null,
            MessengerService.MSG_REGISTER_CLIENT);
        msg.replyTo = mMessenger;
        mService.send(msg);
        
        // 例としてある値を与える
        msg = Message.obtain(null,
            MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
        mService.send(msg);
      } catch (RemoteException e) {
      }
      
      // サンプルの一環として、何が起きたかユーザーに表示する
      Toast.makeText(MessengerServiceClient.this, R.string.remote_service_connected,
          Toast.LENGTH_SHORT).show();
    }
    
    public void onServiceDisconnected(ComponentName className) {
      // これはサービスのコネクションが不意に切断された場合
      // -- すなわちプロセスがクラッシュした場合に呼ばれる。
      mService = null;
      mCallbackText.setText("Disconnected.");
      
      // サンプルの一環として、何が起きたかユーザーに表示する
      Toast.makeText(MessengerServiceClient.this, R.string.remote_service_disconnected,
          Toast.LENGTH_SHORT).show();
    }
  };
  
  void doBindService() {
    // サービスのコネクションを確立する。明示的なクラス名を使う。
    // なぜなら他のアプリケーションが私たちのコンポーネントを
    // 交換できるようにできるようにする理由がありません。
//    bindService(new Intent(MessengerServiceClient.this, 
//        com.example.messengerserviceexample.MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
    bindService(
        new Intent("com.example.messengerserviceexample.MessengerService"),
        mConnection, Context.BIND_AUTO_CREATE);
    mIsBound = true;
    mCallbackText.setText("Binding.");
  }
  
  void doUnbindService() {
    if (mIsBound) {
      // サービスを受信し、それゆえに登録している場合は、
      // ここでは、登録を解除する時間である。
      if (mService != null) {
        try {
          Message msg = Message.obtain(null,
              MessengerService.MSG_UNREGISTER_CLIENT);
          msg.replyTo = mMessenger;
          mService.send(msg);
        } catch (RemoteException e) {
          // There is nothing special we need to do if the service
          // has crashed.
        }
      }
      
      // Detach our existing connection.
      unbindService(mConnection);
      mIsBound = false;
      mCallbackText.setText("Unbinding.");
    }
  }
  
  /**
   * 標準的なこのアクティビティの初期化。UIをセットアップし、それから
   * 何かする前にユーザーがそれを突くのを待ちます。(?)
   */
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    setContentView(R.layout.main);
    
    // ボタンのクリックを見張る
    Button button = (Button)findViewById(R.id.bind);
    button.setOnClickListener(mBindListener);
    button = (Button)findViewById(R.id.unbind);
    button.setOnClickListener(mUnbindListener);
    
    mCallbackText = (TextView)findViewById(R.id.callback);
    mCallbackText.setText("Not attached.");
  }
  
  private OnClickListener mBindListener = new OnClickListener() {
    public void onClick(View v) {
      doBindService();
    }
  };
  
  private OnClickListener mUnbindListener = new OnClickListener() {
    public void onClick(View v) {
      doUnbindService();
    }
  };
}

参考:
Android: MessengerでServiceとbindする << えふログ
Service - Messengerを使ったProcess間通信 - The blog :: Memo & Journal
サービスをアプリケーションプロセスから分離する - Kazzzの日記

« Androidスマホの時刻合わせ | Main | FF11 シーフでアットワスラッグNMに挑んだ »

Androidアプリ開発」カテゴリの記事

Comments

Post a comment

(Not displayed with comment.)

TrackBack

TrackBack URL for this entry:
http://app.cocolog-nifty.com/t/trackback/26461/53121693

Listed below are links to weblogs that reference Androidアプリ開発メモ033:サービス その5:Messengerを使用したバインドされたサービス:

« Androidスマホの時刻合わせ | Main | FF11 シーフでアットワスラッグNMに挑んだ »

August 2017
Sun Mon Tue Wed Thu Fri Sat
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
無料ブログはココログ

日本blog村

  • にほんブログ村 IT技術ブログへ
  • にほんブログ村 アニメブログへ
  • にほんブログ村 サッカーブログ アルビレックス新潟へ

好きな音楽家

メモ

XI-Prof