[android] AIDLによるプロセス間通信 – Callback編 9


前回に続いてAIDLのお話です。今回はCallbackしてみます。

流れとしては、

  1. ActivityでServiceをBind
  2. ServiceのCallback登録Interfaceをコール
  3. Serviceに実装したCallback登録Interfaceで、受け取ったCallback情報をリストに登録
  4. Callbackリストを参照して登録されているCallback Interfaceをコール
  5. Activityに実装したCallback Interfaceでなんかする

みたいな感じです。

図にするとこんな感じ。

2009072001

で、コードです。
まずはCallback登録用のInterface。

package jp.xfutures.android.sample;

import jp.xfutures.android.sample.ISampleServiceCallback;

oneway interface ISampleService {
    /**
     * Callback Interfaceを登録する.
     */
    void registerCallback(ISampleServiceCallback callback);

    /**
     * Callback Interfaceを登録解除する.
     */
     void unregisterCallback(ISampleServiceCallback callback);
}

Serviceからの応答を受けないときは、onewayを指定すると良いかも。
次にCallback Interface。

package jp.xfutures.android.sample;

oneway interface ISampleServiceCallback {
    /**
     * 画面を更新するかも.
     */
    void update(String value);
}

Serviceです。
onCreate()でタイマーを仕掛けて、1秒間隔でCallbackを叩くタスクが起動するようにしています。

ISimpleServiceの実装では、registerCallback()でRemoteCallbackListにアイテムを登録、unregisterCallback()で解除します。
こいつはこれだけです。

onCreate()で仕掛けたタイマータスクでCallbackListからアイテムを取得し、実行していきます。
beginBroadcast()でCallback処理を開始、getBroadcastItem()で登録されているアイテムを取得、finishBroadcast()でCallback処理終了です。

package jp.xfutures.android.sample;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;

public class SampleService extends Service {

    /** Callback Interfaceのリスト. */
    private final RemoteCallbackList<ISampleServiceCallback> callbackList
        = new RemoteCallbackList<ISampleServiceCallback>();

    /** たいまー. */
    private Timer timer = new Timer();

    @Override
    public void onCreate() {
        super.onCreate();

        // Callback Interfaceを叩くタイマータスクを実行する
        timer.schedule(task, 0, 1000);

    }

    @Override
    public IBinder onBind(Intent intent) {
        if(ISampleService.class.getName().equals(intent.getAction())){
            // ISampleServiceの実装クラスのインスタンスを返す
            return sampleSerciceIf;
        }
        return null;
    }

    /**
     * ISampleServiceの実装.
     */
    private ISampleService.Stub sampleSerciceIf = new ISampleService.Stub(){
        @Override
        public void registerCallback(ISampleServiceCallback callback)
                throws RemoteException {
            // Callbackリストに登録.
            callbackList.register(callback);
        }

        @Override
        public void unregisterCallback(ISampleServiceCallback callback)
                throws RemoteException {
            // Callbackリストから登録解除.
            callbackList.unregister(callback);
        }
    };

    /**
     * Callback Interfaceを叩くタスク.
     */
    private TimerTask task = new TimerTask(){
        @Override
        public void run() {
            // Callbackリストの処理を開始
            int n = callbackList.beginBroadcast();

            // 全アイテム分ループ
            for(int i = 0; i < n; i++){
                try{
                    // Callback Interfaceを叩く
                    callbackList.getBroadcastItem(i).update(
                            String.valueOf(new Date().getSeconds())
                    );
                }
                catch(RemoteException e){
                    // なんかヤバイ
                }
            }

            // 終わり
            callbackList.finishBroadcast();
        }
    };
}

最後にActivityです。
ServiceConnection.onServiceConnected()でISampleService.registerCallback()を叩いてISampleServiceCallbackを登録します。
あとは、ISampleServiceCallback実装にCallback処理を記述するだけです。今回は単純にServiceから受け取った文字列を表示するだけ。

package jp.xfutures.android.sample;

import android.app.Activity;
import android.content.ComponentName;
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.widget.TextView;

public class SampleActivity extends Activity {

    /** SampleServiceのインターフェイス. */
    private ISampleService sampleServiceIf;

    /**
     * Activityができたよ.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // SampleSericeにbindする
        Intent intent = new Intent(ISampleService.class.getName());
        bindService(
                intent,
                sampleServiceConn,
                BIND_AUTO_CREATE
        );
    }

    /**
     * Activityがヤラれたよ.
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();

        // unbindService()を叩いてもすぐにunbindされないみたいなので、
        // 先にCallbackの登録を解除しておく
        try{
            sampleServiceIf.unregisterCallback(callback);
        }
        catch(RemoteException e){
           // ムリ・・・
        }

        // SampleServiceからunbindする
        unbindService(sampleServiceConn);
    }

    /**
     * Serviceに接続・切断したときにアレするやつ.
     */
    private ServiceConnection sampleServiceConn = new ServiceConnection(){
        /**
         * サービスに接続したときに叩かれるメソッド.
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // サービスIFを取得する
            // このIFを使ってサービスとやり取りする
            sampleServiceIf = ISampleService.Stub.asInterface(service);

            try{
                sampleServiceIf.registerCallback(callback);
            }
            catch(RemoteException e){
                // もう、だめ・・・
            }
        }

        /**
         * サービスから切断したときに叩かれるメソッド.
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            sampleServiceIf = null;
        }
    };

    /**
     * 画面更新用のHandler.
     */
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            // 受け取ったメッセージを出すだけ
            TextView textView = (TextView)findViewById(R.id.TextView01);
            textView.setText((String)msg.obj);
        }
    };

    /**
     * Callback Interfaceの実装.
     * 画面からこれが叩かれる.
     */
    private ISampleServiceCallback callback = new ISampleServiceCallback.Stub(){
        @Override
        public void update(String value) throws RemoteException {
            // 受け取った文字列を画面に設定する.
            handler.sendMessage(Message.obtain(handler, 0, value));
        }
    };
}

まとめ。
AndroidSample2009072001.zip


コメントをする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

9 thoughts on “[android] AIDLによるプロセス間通信 – Callback編

  • 川端大地

    初めまして川端と申します。
    一点android開発のコールバックについてお聞きしたいことがあるのですがよろしいでしょうか。
    Callback登録Interfaceで、受け取ったCallback情報をリストに登録していると思うのですが、
    なぜcallbackをリストに登録しているのでしょうか。似た処理を現在実装しているのですが、
    手元の参考書にこの説明がないためお聞き致しました。宜しくお願い致します。

    以上です。

  • nouzui2007

    はじめまして。
    このサイトを見ながらサービスの勉強をしています。
    1点質問なんですが、サービスクラスのonBindのif文の意味を教えてください。
    この処理をすることで、なにが実現できているのでしょうか?
    よろしくお願いします。

  • mt 投稿者

    @川端大地

    すいません、コメントを見逃していました。

    Callbackインスタンスをリストに登録しているのは、このサンプルがタイマー起動でクライアントにメッセージを投げる (Callbackする) 作りになっているからです。

    クライアントはCallbackインスタンスを登録するだけで、以降はサービスから情報が来るまで放置。
    サービスは登録されているリストを定期的に参照し、Callbackインスタンスが入っていたらCallbackするだけ。
    クライアントとサービスが非同期に動くため、情報を共有するためにリストを使っているイメージです。

  • mt 投稿者

    @nouzui2007

    このサンプルではonBind()のif文はなくても問題ありません。
    onBind()でActionを参照して返すI/F切り替えると、1-Serviceで複数のI/Fを実現できます。

  • sawauti

    はじめまして。
    このサイトの記述を参考にさせていただき、アプリを作成しています。
    一つ質問させてください。
    作成したアプリのヒープダンプを確認すると、
    ISampleServiceCallback.Stabクラスが残ってしまいメモリリークが発生しているようなのです。
    コールバックインターフェイスの開放をどのように行うかご存知ないでしょうか?
    よろしくお願いいたします。

  • mt 投稿者

    @sawauti
    サービスから切断してもISampleServiceCallback.Stubのインスタンスが解放されないということでしょうか?

    Activity側に関しては、SampleActivityのPrivateメンバとして生成していますので、SampleActivityと生死を共にします。

    Service側に関しては、unregisterCallbackが叩かれたタイミングでリストから削除していますので、そのうちGCで回収されるはずです。
    GCを走らせても解放されないのでしたら、ちょっとわからないです。

  • sawauti

    @mt
    mtさんおっしゃる通り、GCが走った際に、回収されました。
    メモリリークの調査を行っていたことがあるのですが、
    私が確認した原因は解決した時点で、画面を何度起動してもインスタンスが増えなくなったため、
    「メモリリーク発生」の定義は、このインスタンスが増えることと考えていたのですが、
    違うのですね。
    ありがとうございました。

  • sawauti

    先日はありがとうございました。
    現在Callbackの処理について悩んでおります。
    CallbackからActivityに通知を行った後、各Activityからダイアログを表示する処理を実装しているのですが、
    画面が遷移のタイミングで、Callbackから通知を送ると、
    callbackList.getBroadcastItem(i)のinterfaceを実行する箇所で、エラーが発生してしまいます。
    しかも、その後は常時通信できなくなってしまうのです。(interfaceが失敗した情報を保持してるのか?)
    エラーはAndroidの内部(Parcelクラス)で発生していたため、その対処よりも、回避策を考えています。
    Activityを開きなおすと通信は回復するので、
    Service側から再接続出来ればと思っているのですがそんなことは可能でしょうか。
    エラーキャッチ時に、unRegister→Registerを再度行うことで通信が回復するかと試したのですが、
    出来ませんでした。
    このような現象、または回避策をご存知でしたら、教えていただけないでしょうか?

  • sawauti

    解決しました。
    コールバックを受信する画面の実装に問題がありました。
    お騒がせしてすみませんでした。