【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を用いて返す際、下記の手順を踏む事に成ります。
- EventModelにinterface Callbackを定義する
- EventModelでCallbackのメンバーを持ちセッターを用意する
- VCはrequestGetAllを呼ぶ前にCallbackをつくりEventModelにセットする
- VCからEventModel.requestGetAllを呼ぶ
- 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を考えると下記の手順を踏むことになります。
- EventObserverを定義する
- EventModelでEventObserverのリストのメンバを持つ
- VC側でObserve開始時にEventModel.addObserveし、Observe終了時でEventModel.deleteObserverする
- VC側で取得完了時に呼ばれるonGetAllを定義する
- VCからEventModel.requestGetAllを呼ぶ
- 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を考えると手順は下記のようになります。
- EventModel.getAllStreamを実装しストリームを返すようにする
- VC側でCompositeSubscriptionのメンバを持つ
- VC側でストリーム監視開始時にそのストリームをCompositeSubscriptionに登録し、監視終了時にCompositeSubscriptionから外す
- VCからEventModel.getAllStreamを呼ぶ
- VCで取得したストリームをsubscribeする
- 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を考えます。
- OnGetAllEventという取得完了用のクラスを用意する
- VCはonCreateなどで eventBus.registerし、onDestroyなどでeventBus.unregisterする
- VCは void onEventMainThread(OnGetAllEvent) を実装する
- 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しなければならないとかいう制約もないので本当に便利です。