ライフサイクル対応コンポーネントによるライフサイクルへの対応 Android Jetpack の一部。
ライフサイクル対応コンポーネントは、別のコンポーネント(アクティビティやフラグメントなど)のライフサイクル ステータスの変化に対応してアクションを実行します。このコンポーネントを使用することで、適切に整理された、メンテナンスが容易な軽量のコードを作成できます。
一般的なパターンでは、従属関係にあるコンポーネントのアクションをアクティビティとフラグメントのライフサイクル メソッドに実装します。ただし、このパターンではコードをうまく整理できず、エラーの発生が増加します。ライフサイクル対応コンポーネントを使用すると、従属関係にあるコンポーネントのコードをライフサイクル メソッドから移動してコンポーネント自体に組み込むことができます。
androidx.lifecycle
パッケージには、ライフサイクル対応コンポーネントを作成するためのクラスとインターフェースが用意されています。ライフサイクル対応コンポーネントは、アクティビティやフラグメントの現在のライフサイクルの状態に基づいて動作を自動的に調整できるコンポーネントです。
Android フレームワークで定義されているアプリ コンポーネントは、その大部分にライフサイクルがアタッチされています。ライフサイクルは、オペレーティング システム、またはプロセス内で実行されるフレームワーク コードによって管理されます。ライフサイクルは Android の動作にとって非常に重要であり、アプリはライフサイクルに従う必要があります。そうしないと、メモリリークやアプリのクラッシュが発生する可能性があります。
画面に端末の位置情報を表示するアクティビティについて考えてみましょう。一般的な実装は次のようになります。
Kotlin
internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } } class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } }
Java
class MyLocationListener { public MyLocationListener(Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; @Override public void onCreate(...) { myLocationListener = new MyLocationListener(this, (location) -> { // update UI }); } @Override public void onStart() { super.onStart(); myLocationListener.start(); // manage other components that need to respond // to the activity lifecycle } @Override public void onStop() { super.onStop(); myLocationListener.stop(); // manage other components that need to respond // to the activity lifecycle } }
このサンプルは問題ないように見えますが、実際のアプリでは、ライフサイクルの現在の状態に対応して UI や他のコンポーネントを管理するための呼び出しの回数が非常に多くなります。複数のコンポーネントを管理する場合、ライフサイクル メソッド(onStart()
や onStop()
など)のコードがかなりの量になるため、メンテナンスが困難になります。
さらに、コンポーネントが開始する前にアクティビティやフラグメントが停止する可能性も否めません。これは、長時間実行オペレーション(onStart()
での設定チェックなど)を実行する必要がある場合に特に当てはまります。これにより onStart()
メソッドの前に onStop()
メソッドが終了するという競合状態が発生すると、コンポーネントが必要以上に長い間稼働し続けることになります。
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start() } } } public override fun onStop() { super.onStop() myLocationListener.stop() } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, location -> { // update UI }); } @Override public void onStart() { super.onStart(); Util.checkUserStatus(result -> { // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start(); } }); } @Override public void onStop() { super.onStop(); myLocationListener.stop(); } }
androidx.lifecycle
パッケージに含まれているクラスとインターフェースを使用することで、上記の問題を回復性のある分離された方法で解決できます。
ライフサイクル
Lifecycle
は、コンポーネント(アクティビティやフラグメントなど)のライフサイクルの状態に関する情報を保持するクラスです。このクラスを使用すると、他のオブジェクトでその状態を監視できます。
Lifecycle
は、次の 2 つの主要な列挙型を使用して、関連するコンポーネントのライフサイクル ステータスを追跡します。
- イベント
- フレームワークと
Lifecycle
クラスからディスパッチされるライフサイクル イベント。これらのイベントは、アクティビティとフラグメントのコールバック イベントにマッピングされます。 - 状態
Lifecycle
オブジェクトによって追跡されるコンポーネントの現在の状態。
状態はグラフのノード、イベントはそれらのノード間のエッジとそれぞれ考えることができます。
クラスでコンポーネントのライフサイクル ステータスを監視するには、DefaultLifecycleObserver
を実装し、onCreate
、onStart
などの対応するメソッドをオーバーライドします。そして、Lifecycle
クラスの addObserver()
メソッドを呼び出して、オブザーバーのインスタンスを渡すことで、オブザーバーを追加します。次の例のようになります。
Kotlin
class MyObserver : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { connect() } override fun onPause(owner: LifecycleOwner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(MyObserver())
Java
public class MyObserver implements DefaultLifecycleObserver { @Override public void onResume(LifecycleOwner owner) { connect() } @Override public void onPause(LifecycleOwner owner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
上の例では、myLifecycleOwner
オブジェクトに LifecycleOwner
インターフェースが実装されます。次のセクションでこのインターフェースについて説明します。
LifecycleOwner
LifecycleOwner
は、クラスに Lifecycle
が含まれていることを示すシングル メソッド インターフェースです。このインターフェースの単一のメソッドである getLifecycle()
をクラスで実装する必要があります。アプリのプロセス全体のライフサイクルを管理する場合は、ProcessLifecycleOwner
をご覧ください。
このインターフェースでは、Lifecycle
の所有権を個々のクラス(Fragment
や AppCompatActivity
)から分離して、それらのクラスと連携するコンポーネントを作成できます。LifecycleOwner
インターフェースはすべてのカスタム アプリクラスで実装できます。
DefaultLifecycleObserver
を実装するコンポーネントは、LifecycleOwner
を実装するコンポーネントとシームレスに連携します。これは、オブザーバーが監視用に登録可能なライフサイクルをオーナーが提供できるためです。
位置情報追跡の例では、MyLocationListener
クラスで DefaultLifecycleObserver
を実装し、onCreate()
メソッドでアクティビティの Lifecycle
を使用して初期化できます。これにより、MyLocationListener
クラスは自立できます。つまり、ライフサイクル ステータスの変化に対応するロジックは、アクティビティでなく MyLocationListener
で宣言されます。個々のコンポーネントに固有のロジックを持たせることで、アクティビティやフラグメントのロジックを簡単に管理できます。
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this, lifecycle) { location -> // update UI } Util.checkUserStatus { result -> if (result) { myLocationListener.enable() } } } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
一般的なユースケースでは、Lifecycle
の現在の状態が良好でない場合、特定のコールバックを呼び出さないようにします。たとえば、アクティビティの状態が保存された後にコールバックによってフラグメント トランザクションを実行するとクラッシュがトリガーされるため、そのコールバックは呼び出さないようにします。
このユースケースを簡単にするには、Lifecycle
クラスを使用して、他のオブジェクトから現在の状態を照会できるようにします。
Kotlin
internal class MyLocationListener( private val context: Context, private val lifecycle: Lifecycle, private val callback: (Location) -> Unit ): DefaultLifecycleObserver { private var enabled = false override fun onStart(owner: LifecycleOwner) { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } override fun onStop(owner: LifecycleOwner) { // disconnect if connected } }
Java
class MyLocationListener implements DefaultLifecycleObserver { private boolean enabled = false; public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... } @Override public void onStart(LifecycleOwner owner) { if (enabled) { // connect } } public void enable() { enabled = true; if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected } } @Override public void onStop(LifecycleOwner owner) { // disconnect if connected } }
この実装では、LocationListener
クラスが完全にライフサイクル対応になります。別のアクティビティやフラグメントから LocationListener
を使用する場合に必要なのは、初期化のみです。セットアップとティアダウンの処理はすべて、クラス自体で管理されます。
Android のライフサイクルと連動する必要があるクラスをライブラリで提供する場合は、ライフサイクル対応コンポーネントを使用することをおすすめします。これらのコンポーネントはライブラリ クライアントで簡単に統合できます。クライアント側でライフサイクルを手動で管理する必要はありません。
カスタムの LifecycleOwner の実装
サポート ライブラリ 26.1.0 以降のフラグメントとアクティビティには、すでに LifecycleOwner
インターフェースが実装されています。
カスタムクラスを LifecycleOwner
にするには、LifecycleRegistry クラスを使用します。ただし、次のサンプルコードに示すように、イベントをそのクラスに転送する必要があります。
Kotlin
class MyActivity : Activity(), LifecycleOwner { private lateinit var lifecycleRegistry: LifecycleRegistry override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.markState(Lifecycle.State.CREATED) } public override fun onStart() { super.onStart() lifecycleRegistry.markState(Lifecycle.State.STARTED) } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class MyActivity extends Activity implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } @Override public void onStart() { super.onStart(); lifecycleRegistry.markState(Lifecycle.State.STARTED); } @NonNull @Override public Lifecycle getLifecycle() { return lifecycleRegistry; } }
ライフサイクル対応コンポーネントに関するおすすめの方法
- UI コントローラ(アクティビティとフラグメント)はできる限りシンプルにします。データの取得には UI コントローラ自体でなく
ViewModel
を使い、LiveData
オブジェクトを監視して変更をビューに反映させます。 - データの変更時に UI コントローラがビューを更新するデータ主導型の UI を作成するか、ユーザー アクションを
ViewModel
に通知するようにします。 - データのロジックを
ViewModel
クラスに実装します。ViewModel
は、UI コントローラとその他のアプリ間のコネクタとして機能する必要があります。ただし、ネットワークなどからのデータの取得はViewModel
の責任ではありません。代わりに、ViewModel
で適切なコンポーネントを呼び出し、そのコンポーネントを使ってデータを取得して UI コントローラに返します。 - データ バインディングを使用して、ビューと UI コントローラ間のクリーンなインターフェースを維持します。こうすることで、ビューをより宣言的にすることができるうえ、アクティビティとフラグメントに記述する必要がある更新コードを最小限に抑えることができます。Java プログラミング言語でこの処理を行う場合は、Butter Knife などのライブラリを使用してボイラープレート コードを排除し、適切に抽象化を行います。
- UI が複雑な場合は、UI の変更に対処するためにプレゼンター クラスを作成することを検討してください。この作業には手間がかかるかもしれませんが、UI コンポーネントを簡単にテストできるようになります。
ViewModel
でView
またはActivity
のコンテキストを参照しないでください。ViewModel
の生存期間がアクティビティよりも長くなると(設定の変更が行われた場合)、アクティビティがリークし、ガベージ コレクタによって適切に廃棄されなくなります。- Kotlin コルーチンを使用して、長時間実行されるタスクと非同期に実行できるその他のオペレーションを管理します。
ライフサイクル対応コンポーネントのユースケース
ライフサイクル対応コンポーネントを使用すると、さまざまなケースにおいてライフサイクルを極めて簡単に管理できるようになります。以下に例を示します。
- 大まかな現在地情報と詳細な現在地情報の切り替え。ライフサイクル対応コンポーネントを使用すると、現在地情報アプリが表示されている間は詳細な現在地情報を有効にし、そのアプリがバックグラウンドで実行されているときには大まかな現在地情報に切り替えることができます。ライフサイクル対応コンポーネントである
LiveData
を使用すると、ユーザーが移動したときにアプリで UI を自動更新できます。 - 動画のバッファリングの停止と開始。ライフサイクル対応コンポーネントを使用すると、可能な限り早く動画のバッファリングを開始できます。ただし、アプリが完全に起動するまで再生が延期されます。また、ライフサイクル対応コンポーネントを使用することで、アプリが破棄されたときにバッファリングを終了することもできます。
- ネットワーク接続の開始と停止。ライフサイクル対応コンポーネントを使用すると、アプリがフォアグラウンドで実行されている間、ネットワーク データのリアルタイム更新(ストリーミング)を有効にすることができます。また、アプリがバックグラウンドに移動されたときには、リアルタイム更新を自動的に一時停止できます。
- アニメーション ドローアブルの一時停止と再開。ライフサイクル対応コンポーネントを使用すると、アプリがバックグラウンドで実行されているときはアニメーション ドローアブルを一時停止し、アプリがフォアグラウンドで実行されるようになったらドローアブルを再開することができます。
停止イベントへの対処
Lifecycle
が AppCompatActivity
または Fragment
に属している場合、AppCompatActivity
または Fragment
の onSaveInstanceState()
が呼び出されると、Lifecycle
の状態が CREATED
に変化して ON_STOP
イベントがディスパッチされます。
Fragment
または AppCompatActivity
の状態が onSaveInstanceState()
を介して保存された場合、ON_START
が呼び出されるまでその UI は変更不可と見なされます。状態が保存された後に UI を変更しようとすると、アプリのナビゲーションの状態に不整合が生じる可能性があります。状態が保存された後にアプリで FragmentTransaction
を実行した場合に FragmentManager
が例外をスローするのはこのためです。詳しくは、commit()
をご覧ください。
LiveData
は、オブザーバーに関連付けられた Lifecycle
の状態が STARTED
以上でなければオブザーバーを呼び出さないようにして、こうしたエッジケースを追加設定なしで防止しています。バックグラウンドでは、オブザーバーを呼び出すことを決定する前に isAtLeast()
が呼び出されます。
残念ながら、AppCompatActivity
の onStop()
メソッドは onSaveInstanceState()
の後に呼び出されるため、UI の状態の変更は許可されず、Lifecycle
の状態もまだ CREATED
の状態に遷移していないというギャップが残ります。
この問題が発生しないように、バージョン beta2
以下の Lifecycle
クラスでは、イベントをディスパッチせずに状態を CREATED
としてマークします。これにより、onStop()
がシステムによって呼び出されるまでイベントはディスパッチされなくなりますが、現在の状態をチェックするすべてのコードで実際の値を取得できるようになります。
ただし、このソリューションには次の 2 つの大きな問題があります。
- API レベルが 23 以下の場合、アクティビティの状態の保存は、部分的に別のアクティビティの対象であっても、実際には Android システムが行います。つまり、Android システムは
onSaveInstanceState()
を呼び出しますが、必ずしもonStop()
を呼び出すわけではありません。このため、UI の状態が変更不可であっても、ライフサイクルがアクティブであるとオブザーバーが判断するインターバルが長くなる可能性があります。 LiveData
クラスに同様の動作を公開するクラスは、Lifecycle
バージョンbeta 2
以下で提供されている回避策を実装する必要があります。
参考情報
ライフサイクル対応コンポーネントによるライフサイクルへの対応について詳しくは、以下の参考情報をご覧ください。
サンプル
- Sunflower - アーキテクチャ コンポーネントを使用したおすすめの方法を示すデモアプリ
Codelab
ブログ
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- LiveData の概要
- ライフサイクル対応コンポーネントで Kotlin コルーチンを使用する
- ViewModel の保存済み状態のモジュール