Android Architecture Components 雑感2。
主に Lifecycle
と LiveData
について。
Lifecycle
某ポエムサイトにあったクラス図がしっくりこなかったので、自分で書き直してみました。
Lifecycle
のキモは、ライフサイクルメソッドのオーバーライドはもうやめましょう、ということ。
なぜかというと、Activity
や Fragment
を容易に Fat にする上に、処理を追うのが直感的ではなくなってしまうから。
その代わり、Activity
や Fragment
は State
を持つように変わります。ライフサイクルイベントが発生すると、どこからともなく(後述) Event
が投げられて、これを受けて State
の更新が行われます。
State
と Event
は公式サイトの以下の図の通りです。
(Handling Lifecycles より)
このイベントの変更を受け取りたいクラスは、LifecycleObserver
インターフェースを実装します。これはマーカーインターフェースです。任意のオブジェクトが LifecycleObserver
になれます。
その上で、@OnLifecycleEvent
アノテーションを付与して、実際のそれぞれの State
変更時の処理を実装します。
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { // RESUMED State に遷移したときに行って欲しい処理 }
あとはこの LifecycleObserver
を、ライフサイクルイベントを受け取りたい LifecycleOwner
(Activity
または Fragment
、Service
)の LifecycleRegistry
に登録して、購読させます。
これにより、画面を構成する部品単位で、Activity
や Fragment
から切り離された状態で、ライフサイクルに応じた処理が書けるようになります。
同じライフルサイクルメソッドに、複数の処理が書かれることがないので、コードは読みやすくなりますし、LifecycleObserver
マーカーインターフェースを実装するだけでよいので、再利用性も高くなります。
- 任意のコンポーネントがライフサイクルを持てる
Activity
にベタに書かなくて良い- 再利用可能
という特徴から、「不可視の Fragment
じゃん」という感じですが、実装上でもそのテクニックが使われています。
LiveData
さて、LiveData
はリアクティブな感じで値を通知する簡易なモジュールで、Rx のオペレータが使えないので正直メリットを感じないものなのですが、LifecycleObserver
を実装している、という点は見逃せません。
例えばバックグラウンドで通信を行う場合、結果を表示する UI が既にない、という状況はありがちです。
LiveData
は 「STARTED
または RESUMED
でない状態ならば、値を通知しない」という仕組みになっています。購読、購読解除については、instanceof
で LifecycleOwner
であれば勝手にやってくれると思いますので(たぶん)、あんまり細かいことを気にしなくていいのがウリです。
類似品に、RxJava の親戚の RxLifecycle がありますが、こちらは RxFragment
のようなライフサイクルを監視する特定のクラスを継承しなければいけないのに対して、Lifecycle
は Andorid の仕様の穴を突いたような素敵実装になっており、Activity
や Fragment
の実装を選ばない、という点がメリットとして挙げられると思います。
とはいえ、個人的には使わなさそうな感じです。
重要なのは、今までのライフサイクルメソッドを太らせていく記述方法から、データドリブンな記述(Rx に近い考え方)へ転換しましょうね、というのが Livecycle
と LiveData
の狙いなので、もし Rx があんまり好きじゃないならスルーしていいと思います。
Lifecycle の実装
内部を追いかけていたらこのサイトに行き当たって、知りたいことはほぼほぼ説明されていました。
Android Architecture Components のソースコードはまだ公開されていないので、現時点で内部構造を語っているのは世界で唯一このサイトだけだと思います、たぶん。
registerActivityLifecycleCallbacks()
で Activity
のライフサイクルコールバックを受け取って、onActivityCreated()
で不可視の ReportFragment
を埋め込んでライフサイクルを監視してるわけですね。
この ReportFragment
のライフサイクルメソッドの呼び出し時に、
private void dispatch(Event event) { if(this.getActivity() instanceof LifecycleRegistryOwner) { ((LifecycleRegistryOwner)this.getActivity()).getLifecycle().handleLifecycleEvent(event); } }
という感じで、もし Activity
が LifecycleRegistryOwner
を実装していたら、getLifecycle().handleLifecycleEvent()
を呼び出すという単純ながら力強い実装になっています。
registerActivityLifecycleCallbacks()
を行うタイミングがないように思えますが、LifecycleRuntimeTrojanProvider
というダミーの ContentProvider
で実現されている、というのが面白ポイントです。ContentProvider
がアプリケーションプロセス起動時に立ち上がるのを利用しているわけですね。
ViewModel
と画面遷移
いまだにしっくりこないのが、ViewModel
で画面遷移をどう実現するか?という部分です。
Handling Lifecycles の ViewModel
のベストプラクティスの項目には、
Never reference a View or Activity context in your ViewModel. If the ViewModel outlives the activity (in case of configuration changes), your activity will be leaked and not properly garbage-collected.
【私訳】 ViewModel に View または Activity context への参照を持たせないでください。もし ViewModel が Activity より長生きした場合(configuration changes の発生時など)、Activity はリークして、適切に GC に回収されません。
とある通り、ViewModel
に画面遷移に必要な Context
を持たせたくないのですが、とはいえどうすればよいのか。
公式のサンプルだと、NavigationController
を DI しているのですが、これは単一の Activity
だからできるという感じだし、画面の数だけ navigateToXXX()
メソッドを増やしていくのは微妙だなーという感想しかないわけです。
実際的には、Intent
フラグを与えたり、Bundle
でパラメータを受け渡ししたいというようなケースも多々あるので、もうちょっと汎用的に使えるものが欲しい。
また、このサンプル実装だと、commitAllowingStateLoss()
で誤魔化していますが、FragmentManager
もライフサイクルと切っても切れない Android のつらい問題のひとつで、ViewModel
のスコープから画面遷移をキックすると、IllegalStateException
に悩まされるのがオチだと思っていて、その辺のライフサイクルの問題までは、まだまだ解決しそうにないなーという感想なのでした。