genonymous

GenestreamのTechブログ

【Android】Modelからの通知の実装方法における個人的な変遷まとめ

こんにちは。釘宮です。

AndroidにおいてModelからVC(View or Controller というか Acitivity or Fragment)に通知するときの方法が僕の中で変遷してきているなぁと思ったのでサンプルコードも書きつつ振り返ってみたいと思いました。

VCに全部書く

プログラミングを初めて間もないころ、なにも気にせずにVCに全部書いていましたすいません。
MVCとかよくわかってなかったので赴くままに書いていたわけです。

VC側

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // たとえばonCreateから呼ばれるとこにガリガリかいていたわけです
        ArrayList<Event> events = new ArrayList<Event>();
        :
    }

Callbackを使う

VCに全てを書ききるという初心者あるあるを抜け出したときはCallbackを使って実装するようになりました。
この方法はセッターを使ったり、もしくは引数としデータの取得完了時にCallbackでVCに通知する方法です。

たとえばEventを扱うEventModelを考えます。VC側でEventModel.requestGetAllを読んで、その結果をCallbackを用いて返す際、下記の手順を踏む事に成ります。

  1. EventModelにinterface Callbackを定義する
  2. EventModelでCallbackのメンバーを持ちセッターを用意する
  3. VCはrequestGetAllを呼ぶ前にCallbackをつくりEventModelにセットする
  4. VCからEventModel.requestGetAllを呼ぶ
  5. Modelは処理が終わったらcallback.onGetAllを用いてVCに通知する

実際は、データの取得に時間がかかる事が多くThreadなどを用いるためソースは下記のようになります。

Model側

public class EventModel {

    private static final int REQ_GET_ALL = 1;

    private Callback mCallback = null;
    
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case REQ_GET_ALL:
                    if (mCallback != null) {
                        mCallback.onGetAll((ArrayList<Event>)msg.obj);
                    }
                    break;
            }
        }
    };

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    public void requestGetAll() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ArrayList<Event> events = new ArrayList<Event>();
                // ContentProviderだったりhttp通信だったりでeventsを取得する
                Message msg = Message.obtain();
                msg.what = REQ_GET_ALL;
                msg.obj = events;
                mHandler.sendMessage(msg);
            }
        }).start();
    }

    public interface Callback {
        void onGetAll(ArrayList<Event> events);
    }
}

VC側

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        EventModel eventModel = new EventModel();
        eventModel.setCallback(new EventModel.Callback() {
            @Override
            public void onGetAll(ArrayList<Event> events) {
                // 取得した後の処理
            }
        });
        eventModel.requestGetAll();
    }

この方法のデメリットは、たとえばデータの取得に時間がかかる場合にCallbackで通知したものの既にVCが死んでる可能性があるということです。無効なCallbackにアクセスすることでアプリが落ちる可能性がでてきます。

Observer Patternを使う

Callbackに飽きた頃、Martin Flowerさんに習いM->VCにObserver Patternを採用していました。これで無効なCallbackにアクセスすることはなくなります。

またまたEventを扱うEventModelを考えると下記の手順を踏むことになります。

  1. EventObserverを定義する
  2. EventModelでEventObserverのリストのメンバを持つ
  3. VC側でObserve開始時にEventModel.addObserveし、Observe終了時でEventModel.deleteObserverする
  4. VC側で取得完了時に呼ばれるonGetAllを定義する
  5. VCからEventModel.requestGetAllを呼ぶ
  6. Modelは処理が終わったらobserver.onGetAllを用いてVCに通知する

Model側のソースは基本的にCallback関係がObserverになっただけでそんなに変わりません。

Model側

public class EventModel {

     /** オブザーバーリスト */
    private CopyOnWriteArrayList<EventObserver> mObservers = new CopyOnWriteArrayList<EventObserver>();

    /**
     * オブザーバーの取得
     * @return オブザーバー
     */
    protected CopyOnWriteArrayList<EventObserver> getObservers() {
        return mObservers;
    }

    /**
     * Observerを追加
     * @param observer observer
     */
    public void addObserver(EventObserver observer) {
        mObservers.add(observer);
    }

    /**
     * Observerを削除
     * @param observer observer
     */
    public void deleteObserver(EventObserver observer) {
        mObservers.remove(observer);
    }

    private static final int REQ_GET_ALL = 1;
    
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case REQ_GET_ALL:
                    ArrayList<Event> events = (ArrayList<Event>) msg.obj;
                    CopyOnWriteArrayList<EventObserver> observers = getObservers();
                    for (EventObserver observer : observers) {
                        observer.onFetchedAllEvents(events);
                    }
                    break;
            }
        }
    };

    public void requestGetAll() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ArrayList<Event> events = new ArrayList<Event>();
                // ContentProviderだったりhttp通信だったりでeventsを取得する
                Message msg = Message.obtain();
                msg.what = REQ_GET_ALL;
                msg.obj = events;
                mHandler.sendMessage(msg);
            }
        }).start();
    }
}

Observer

public interface EventObserver {
    void onGetAll(ArrayList<Event> events);
}

VC

public HogeActivity implements EventObserver {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // observerの登録
        mEventModel.addObserver(this);
        // どこか呼びたいタイミングで。ここじゃなくてもいい
        mEventModel.requestGetAll();
    }

    @Override
    public void onDestroy() {
        // observerを解除
        mEventModel.deleteObserver(this);
        super.onPause();
    }

    @Override
    public void onFetchedCache(ArrayList<Event> events) {
        // 取得した後の処理 
    }
}

デメリットはあるとすれば、ソースがなんか煩雑ってことかなと思います。

Rxを使う

最近流行のRxAndroidを使ってみてました。
ストリームの監視対象と監視する側のスレッドを選べるので今までのようなThreadやHandlerは出てこなくなるのでソースがきれいになります。
またまたEventModelを考えると手順は下記のようになります。

  1. EventModel.getAllStreamを実装しストリームを返すようにする
  2. VC側でCompositeSubscriptionのメンバを持つ
  3. VC側でストリーム監視開始時にそのストリームをCompositeSubscriptionに登録し、監視終了時にCompositeSubscriptionから外す
  4. VCからEventModel.getAllStreamを呼ぶ
  5. VCで取得したストリームをsubscribeする
  6. Model側は処理が終わったらsubscriver.onNextする

Model

public class EventModel {

    public rx.Observable<ArrayList<Event>> getAllStream() {
        return Observable.create(new Observable.OnSubscribe<ArrayList<Event>>() {
            @Override
            public void call(Subscriber<? super ArrayList<Event>> subscriber) {
                 ArrayList<Event> events = new ArrayList<Event>();
                // ContentProviderだったりhttp通信だったりでeventsを取得する
                subscriber.onNext(events);
                subscriber.onCompleted();
            }
        }).subscribeOn(Schedulers.newThread());
    }

}

VC側

public class RxActivity extends Activity {

    private final CompositeSubscription mCompositeSubscription = new CompositeSubscription();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Observable<ArrayList<Event>> eventListStream = eventModel.getAllStream();

        // Subscriptionの登録
        mCompositeSubscription.add(
                eventListStream.observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Action1<ArrayList<Event>>() {
                            @Override
                            public void call(ArrayList<Event> events) {
                                // 取得した後の処理 
                            }
                        }
                )
        );
    }

    @Override
    protected void onDestroy() {
        mCompositeSubscription.clear();
        super.onDestroy();
    }
}

デメリットはRxの学習コストが高い(オブジェクト指向の概念だけでは足りないところがある)ところだと思います。チームで使うとなるとチームのみんなが理解してないと使わない方がいいです。1人だけ理解しててもその人しか読めないっていう現象が発生します。 あとは個人的に思ってるだけなんですが、Rxが枯れていないというところかなと思います。少し使うだけならいいけど、相当な理解と使い続けるんだって言う意気込みとかないとM->VCとかの設計の大きなところで使うのは止めた方がいいかもしれないです。

EventBusを使う

EventBusをこの正月に使ってみたところ、すごくすっきり書く事ができました。またいままで挙げたようなデメリットもなく大変良いです。

ここでもEventModelを考えます。

  1. OnGetAllEventという取得完了用のクラスを用意する
  2. VCはonCreateなどで eventBus.registerし、onDestroyなどでeventBus.unregisterする
  3. VCは void onEventMainThread(OnGetAllEvent) を実装する
  4. Modelは処理が完了したら EventBus.getDefault().post(onGetAllEvent) でVCに通知する

Model側

public class EventModel {

    public void requestGetAll() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                ArrayList<Event> events = new ArrayList<Event>();
                // ContentProviderだったりhttp通信だったりでeventsを取得する
                OnGetAllEvent onGetAllEvent = new OnGetAllEvent();
                onGetAllEvent.events = events;
                EventBus.getDefault().post(onGetAllEvent);
            }
        }).start();
    }

}

イベント

public class OnGetAllEvent {
    public ArrayList<Event> events;
}

VC側

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }

    public void onEventMainThread(OnExitEvent event) {
        // 取得した後の処理 
    }

デメリットは今のところ特になにも感じてないです。

まとめ

まとめてみると結構スタイルが変わってきているんだなぁと改めて思いました。

そのときは正解でも、時代が変われば正解じゃなくなる事もあると思うのでこれからもアンテナを張り続けていければと思います。

それにしてもEventBusは学習コストも高くなく、イベントオブジェクトがParcerableをimplementしなければならないとかいう制約もないので本当に便利です。