Lifecycleのコードラボをやりつつ、ViewModelやLiveDataの仕組みを調べたのでメモしておこと思います。ViewModelstep1はアプリが起動したらタイマーがカウントアップし、画面回転するとタイマーのカウントがクリアされてしまう、という問題にViewModelというものを使ってどう対処するのかという話です。ViewModelとはViewModelを使えば、ActivityやFragmentのライフサイクル全体でデータを保持できます。ActivityやFragmentは、ユーザーの操作でCreatedとDestroyedを繰り返す生存期間の短いオブジェクトです。ViewModelクラスを作成するには、ViewModel()を継承します。{}ViewModelは、ViewModelインスタンスが破棄されるときに呼ばれるonClearedというメソッドを持っています。ViewModelProvider(this).get(ChronoViewModel::class.java)このように取得することになります。以前は、ViewModelProvider.of(activity)のように取得していましたが、lifecycleのバージョン2.2.0-alpha03からDeprecatedになっていました。Delegationを使うにはfragment-ktxの1.1.0以上を使う必要があります。"androidx.fragment:fragment-ktx:1.1.0"https://developer.android.com/jetpack/androidx/releases/fragment#1.1.0ViewModelの生存期間ViewModelの生存期間はActivityやFragmentよりも長いです。下図はActivityを起動して、画面を回転させ、バックボタンでアプリを終了したときの状態遷移です。onDestroy画面回転時に、ActivityはonDestroyされていますが、ViewModelはonClearedが呼ばれていません。ViewModelの仕組みここで、ViewModelがどのような仕組みで保持されているか画面回転時にもonDestroyが呼ばれているにも関わらずonClearedは呼ばれず、アプリ終了時にのみonClearedが呼ばれている仕組みをみてみます。Activityこのような継承関係になっています。ActivityThreadクラスにあるstaticなsCurrentActivityThread変数がActivityが破棄されても同じインスタンスが取得できる大元のインスタンスだと思います。https://android.googlesource.com/platform/frameworks/base.git/+/refs/heads/android10-release/core/java/android/app/ActivityThread.java#351アプリ起動時onCreate時にActivityThreadのmActivitiesにActivityClientRecord(Activity含む)を保持します。onSaveInstanceStateで最後の構成状態lastNonConfigurationInstancesを取得して保持し、onDestroyになります。https://android.googlesource.com/platform/frameworks/base.git/+/refs/heads/android10-release/core/java/android/app/ActivityThread.java#4913再生成(onCreate)onCreate時にActivityThreadのmActiviesの保持しておいたActivityClientRecordインスタンスを取得して、そこにlastNonConfigurationInstancesという最後の構成状態を取得し、ActivityのmLastNonConfigurationInstancesにセットします。https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/activity/activity/src/main/java/androidx/activity/ComponentActivity.java#187これはmLastNonConfigurationInstancesがnullでなければmLastNonConfigurationInstances.activity(これがViewModelの実態)を返すようになっています。https://android.googlesource.com/platform/frameworks/base.git/+/refs/heads/android10-release/core/java/android/app/Activity.java#2876自分のActivityでviewModelをインスタンスにアクセスするとき、ComponentActivity#getViewModelStore()が呼ばれ、ViewModelはキャッシュされているものを返す仕組みになっています。画面回転時にもonDestroyが呼ばれているにも関わらずonClearedは呼ばれず、アプリ終了時にのみonClearedが呼ばれている仕組み画面回転時に、Activityのメンバ変数mChangingConfigurationsにtrueがセットされます。isChangingConfigurations()で画面回転中か確認し、true(回転中)なら何もぜず、false(回転中ではないonDestory=Activity終了)なら、onClearedメソッドを呼ぶという仕組みになっています。https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/activity/activity/src/main/java/androidx/activity/ComponentActivity.java#118画面回転でもタイマーリセットされないように変更するViewModelはActivityより生存期間が長いことがわかりましたので、ViewModelに保持したいタイマーの時間を保持します。null}Activity側ではonCreate時に、startTimeがnullなら時間を新規にセットして、nullじゃない(回転時)なら、ViewModelのstartTimeをセットすればよいです。Activity側のコードはStep2のコードをKotlinで書いただけなので割愛。いう意味ではViewModelもそうなのですが、ポイントはViewModelの方なので。LiveData次はTimerクラスを使って、UIを1秒間隔で「~秒経過」と表示するということを考えます。collectedメモリリークを避けるために、ViewModelにAactivityの参照を持たないようにしてください。画面回転などのコンフィグ変更すると、GCすべきActivityの参照が破棄されないかもしれません。書いてるんですが、画面回転ではメモリリークしませんでした。画面回転させたところ、LeakCanaryは検知しませんでした。)しかし、アプリ終了でリークしました。152まぁメモリリークはするので、ViewModelでActivityやViewの参照はするべきではないと理解。ホントはViewModelにViewを渡して、次のような感じでViewModelでViewを更新できたら良かったんでしょうけど、上記の通りメモリリークするので、Activity側でUIを更新しないといけないので、それをどうするかっていうのがstep3のLiveDataを使いましょうというお話。}}本題に戻ります!UIを1秒間隔で更新したいということでしたね。LiveDataに値を設定できるMutableLiveDataとそれをLiveDataとして返す関数を作成します。elapsedTimeそして、1秒毎に更新したいので、Timerクラスを使って実装します。}}ここまで来ると、なんとなくnewValueをMutableLiveDataのelapsedTimeにセットするんだなって思うと思うので、セットします。newValueすると、setValueはバックグラウンドスレッドで呼び出すことができないエラーが出ます。java.util.TimerThread.run(Timer.java:512)じゃぁどうするかとというと、postValueを使います。setValueはメインスレッド以外禁止にしているかというと、そもそもViewModelにViewを持たせて、ViewModelでUIを更新したかったけど、リークするのでできないから、LiveDataを使いましょうという話だったので、LiveDataの役割はViewを更新することでした。あるならば、Viewの更新はメインスレッドでしかできないので、setValueするときはメインスレッド以外で呼ぶのを禁止しましょう、ってなったんだと思います。(あ、UI側でonChangedメソッドを実装することになるので、setValueはそれを呼び出したスレッドでonChangedメソッドが呼ばれます。メインのHandlerのpostするのは面倒ですしね。postValueはなぜメインスレッド以外で呼べるかというと、名前からもわかるように次のように実装されているからです。}};ぜんぶpostValue使ったら、使う側でスレッド意識しなくていいから、ぜんぶpostValueじゃダメなんですかね?という疑問が最近あるのですが。ViewModel側でMutableLiveDataにpostValue()するところまで来ました。UIを更新していませんので、次にActivityをみていきます。Activityで、ViewModelのelapsedTime(MutableLiveData)を使って、viewModel.elapsedTime.valueでpostValueされたデータは取得できます。今回は毎秒ごとに更新したいので、そのためにはLiveDataを監視するという実装が必要になります。具体的には次のようになります。}})viewModel.getElapsedTime()でLiveDataを取得して、それに対してobserveメソッドで監視します。ViewModelのMutableLiveDataがpostValueされたら、onChangedメソッドが実行され、TextViewを更新するという流れなります。余計なViewが入っていますが、実行することこうなります。補足が必要で、まずviewModel.getElapsedTime()は、MutableLiveDataではなくLiveDataを返します。これは、Activity側では更新する必要がないため、不必要な更新ができてしまうことさけるためにこのようにしています。次に、observeメソッドの第一引数に渡しているthisは、LifecycleOwnerを渡しています。1.1.0でDeprecatedになっていました。LifecycleOwnerはViewModelとLiveDetaのライフサイクルをバインドするためのもので、ChronoActivityの親クラスのComponentActivityが実装しているインターフェースです。LifecycleOwner注意)Activityがアクティブな状態でないと、UIは更新されないことに注意してください。仕組み的にはアクティブになっていない場合は、onChangedが呼ばれ無いだけです。(!observer.mActive)で判断しています。mData);}ちなみに、ActivityがアクティブでないときもonChangedを呼びたい場合は、observeForeverというメソッドが用意されてるので、そちらを使うと良さそうです。observeの実装を説明のためにObserverのインターフェース実装のような形で書きましたが、次のようにlambdaを使って書くと少しスッキリします。"${it}秒経過"})これでstep3は終了です。LocationManagerを使うと思います。このようなAPIは、使うために初期化したりsubscribeしたりし、使わなくなったらストップしたり、unsubscribeします。mLocationManager.removeUpdates(mListener);}AndroidのライブラリやAPIはこのような処理をすることが多く、ActivityやFragmentで処理を書くと煩雑になってしまい、読みにくくなってしまいます。このようなことを防ぐためにLifecycleを使うと便利ですよ、という話になります。簡単に言うと、ActivityやFragment以外のクラスでライフサイクルの変更を検知することができます。このコードラボでは、BoundLocationListenerクラスがライフサイクルの変更を検知できるようになる予定なのですが、どのような仕組みかというと、まずBoundLocationListenerにActivityのlifecycleOwnerインスタンスを渡して、Lifecycleを次のように監視します。(lifecycleOwnerはComponentActivityが実装しているインターフェースでしたね)lifecycleOwner.lifecycle.addObserver(this)このときのthisはLifecycleObserverインターフェースで、BoundLocationListenerが実装する必要があります。(といっても特に実装するものはありません)次に、ライフサイクルが変更したときに指定したメソッドをコールするには、次のように@OnLifecycleEventアノテーションを付けます。ちなみに、軽く内部実装みたら、リフレクション使ってますね。そりゃそうかという感じですが。ここで少し気になるのが、initでaddObserver(this)としているから、デストラクタとかでremoveObserver(this)としなくていいのかということです。調べたところ、removeObserverはしなくて良いそうです。https://github.com/googlecodelabs/android-lifecycles/issues/5内部実装をみると、onDestroy時にremoveObserverが呼ばれていました。次はViewModelがFramgment間や、FragmentとActivity間でシェアできるよというお話。1つのActivityに2つのシークバーを持つFragmentが上下に並んでいて、ViewModelを使うと、片方のシークバーを移動したときにもう片方のシークバーも同期的に動くようにできるらしい。ViewModelインスタンスを取得する際、FragmentのLifecycleOwnerを渡すのではなく、Fragmentが属しているActivityのLifecycleOwnerを渡してあげれば良いようです。いままでは次のようにViewModelインスタンスを取得していた。viewModels<ChronoViewModel>()これは、実装を見ればわかりますが、Fragment自身のLifecycleOwnerを渡していました。{this}のところ。it})次のような動画のように、片方動かすと片方も動くようになります。まとめViewModelはActivityやFragmentより、生存期間が長いので、画面回転などのActivity再生成でも回転前と後で値を保持できる。LiveDataを使って、ViewModelでの更新をActivityやFragmentに通知できる。LifecycleはActivityやFragment以外のクラスでライフサイクル管理ができるので、ActivityやFragment生成時にsubscribeし、破棄時にunsubscribeするようなコンポーネントで使うとスッキリできる。ViewModelはFragment間やFragmentとActivity間で共有できる。