ライフサイクル対応コンポーネントは、別のコンポーネント(アクティビティやフラグメントなど)のライフサイクル ステータスの変化に対応してアクションを実行します。このコンポーネントを使用することで、適切に整理された、メンテナンスが容易な軽量のコードを作成できます。
一般的なパターンでは、従属関係にあるコンポーネントのアクションをアクティビティとフラグメントのライフサイクル メソッドに実装します。ただし、このパターンではコードをうまく整理できず、エラーの発生が増加します。ライフサイクル対応コンポーネントを使用すると、従属関係にあるコンポーネントのコードをライフサイクル メソッドから移動してコンポーネント自体に組み込むことができます。
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 - アーキテクチャ コンポーネントを使用したおすすめの方法を示すデモアプリ