なるようになるかも

力は多くの場合、その人の思いを超えない。

Android Architecture Components 雑感2。

主に LifecycleLiveData について。

Lifecycle

某ポエムサイトにあったクラス図がしっくりこなかったので、自分で書き直してみました。

f:id:quesera2:20170529210511p:plain

Lifecycle のキモは、ライフサイクルメソッドのオーバーライドはもうやめましょう、ということ。

なぜかというと、ActivityFragment を容易に Fat にする上に、処理を追うのが直感的ではなくなってしまうから。

その代わり、ActivityFragmentState を持つように変わります。ライフサイクルイベントが発生すると、どこからともなく(後述) Event が投げられて、これを受けて State の更新が行われます。

StateEvent は公式サイトの以下の図の通りです。

f:id:quesera2:20170529211323p:plain

Handling Lifecycles より)

このイベントの変更を受け取りたいクラスは、LifecycleObserver インターフェースを実装します。これはマーカーインターフェースです。任意のオブジェクトが LifecycleObserver になれます。

その上で、@OnLifecycleEvent アノテーションを付与して、実際のそれぞれの State 変更時の処理を実装します。

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
    // RESUMED State に遷移したときに行って欲しい処理
}

あとはこの LifecycleObserver を、ライフサイクルイベントを受け取りたい LifecycleOwnerActivity または FragmentService)の LifecycleRegistry に登録して、購読させます。

これにより、画面を構成する部品単位で、ActivityFragment から切り離された状態で、ライフサイクルに応じた処理が書けるようになります。

同じライフルサイクルメソッドに、複数の処理が書かれることがないので、コードは読みやすくなりますし、LifecycleObserver マーカーインターフェースを実装するだけでよいので、再利用性も高くなります。

  1. 任意のコンポーネントがライフサイクルを持てる
  2. Activity にベタに書かなくて良い
  3. 再利用可能

という特徴から、「不可視の Fragment じゃん」という感じですが、実装上でもそのテクニックが使われています。

LiveData

さて、LiveData はリアクティブな感じで値を通知する簡易なモジュールで、Rx のオペレータが使えないので正直メリットを感じないものなのですが、LifecycleObserver を実装している、という点は見逃せません。

例えばバックグラウンドで通信を行う場合、結果を表示する UI が既にない、という状況はありがちです。

LiveData は 「STARTED または RESUMED でない状態ならば、値を通知しない」という仕組みになっています。購読、購読解除については、instanceofLifecycleOwner であれば勝手にやってくれると思いますので(たぶん)、あんまり細かいことを気にしなくていいのがウリです。

類似品に、RxJava の親戚の RxLifecycle がありますが、こちらは RxFragment のようなライフサイクルを監視する特定のクラスを継承しなければいけないのに対して、Lifecycle は Andorid の仕様の穴を突いたような素敵実装になっており、ActivityFragment の実装を選ばない、という点がメリットとして挙げられると思います。

とはいえ、個人的には使わなさそうな感じです。

重要なのは、今までのライフサイクルメソッドを太らせていく記述方法から、データドリブンな記述(Rx に近い考え方)へ転換しましょうね、というのが LivecycleLiveData の狙いなので、もし Rx があんまり好きじゃないならスルーしていいと思います。

Lifecycle の実装

www.jianshu.com

内部を追いかけていたらこのサイトに行き当たって、知りたいことはほぼほぼ説明されていました。

Android Architecture Components のソースコードはまだ公開されていないので、現時点で内部構造を語っているのは世界で唯一このサイトだけだと思います、たぶん。

registerActivityLifecycleCallbacks()Activity のライフサイクルコールバックを受け取って、onActivityCreated() で不可視の ReportFragment を埋め込んでライフサイクルを監視してるわけですね。

この ReportFragment のライフサイクルメソッドの呼び出し時に、

private void dispatch(Event event) {
    if(this.getActivity() instanceof LifecycleRegistryOwner) {
        ((LifecycleRegistryOwner)this.getActivity()).getLifecycle().handleLifecycleEvent(event);
    }
}

という感じで、もし ActivityLifecycleRegistryOwner を実装していたら、getLifecycle().handleLifecycleEvent() を呼び出すという単純ながら力強い実装になっています。

registerActivityLifecycleCallbacks() を行うタイミングがないように思えますが、LifecycleRuntimeTrojanProvider というダミーの ContentProvider で実現されている、というのが面白ポイントです。ContentProvider がアプリケーションプロセス起動時に立ち上がるのを利用しているわけですね。

ViewModel と画面遷移

いまだにしっくりこないのが、ViewModel で画面遷移をどう実現するか?という部分です。

Handling LifecyclesViewModel のベストプラクティスの項目には、

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 に悩まされるのがオチだと思っていて、その辺のライフサイクルの問題までは、まだまだ解決しそうにないなーという感想なのでした。