ライフサイクル対応コンポーネントによるライフサイクルへの対応   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 オブジェクトによって追跡されるコンポーネントの現在の状態。
ライフサイクルの状態の図
図 1. Android アクティビティ ライフサイクルを構成する状態とイベント

状態はグラフのノード、イベントはそれらのノード間のエッジとそれぞれ考えることができます。

クラスでコンポーネントのライフサイクル ステータスを監視するには、DefaultLifecycleObserver を実装し、onCreateonStart などの対応するメソッドをオーバーライドします。そして、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 の所有権を個々のクラス(FragmentAppCompatActivity)から分離して、それらのクラスと連携するコンポーネントを作成できます。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 コンポーネントを簡単にテストできるようになります。
  • ViewModelView または Activity のコンテキストを参照しないでください。ViewModel の生存期間がアクティビティよりも長くなると(設定の変更が行われた場合)、アクティビティがリークし、ガベージ コレクタによって適切に廃棄されなくなります。
  • Kotlin コルーチンを使用して、長時間実行されるタスクと非同期に実行できるその他のオペレーションを管理します。

ライフサイクル対応コンポーネントのユースケース

ライフサイクル対応コンポーネントを使用すると、さまざまなケースにおいてライフサイクルを極めて簡単に管理できるようになります。以下に例を示します。

  • 大まかな現在地情報と詳細な現在地情報の切り替え。ライフサイクル対応コンポーネントを使用すると、現在地情報アプリが表示されている間は詳細な現在地情報を有効にし、そのアプリがバックグラウンドで実行されているときには大まかな現在地情報に切り替えることができます。ライフサイクル対応コンポーネントである LiveData を使用すると、ユーザーが移動したときにアプリで UI を自動更新できます。
  • 動画のバッファリングの停止と開始。ライフサイクル対応コンポーネントを使用すると、可能な限り早く動画のバッファリングを開始できます。ただし、アプリが完全に起動するまで再生が延期されます。また、ライフサイクル対応コンポーネントを使用することで、アプリが破棄されたときにバッファリングを終了することもできます。
  • ネットワーク接続の開始と停止。ライフサイクル対応コンポーネントを使用すると、アプリがフォアグラウンドで実行されている間、ネットワーク データのリアルタイム更新(ストリーミング)を有効にすることができます。また、アプリがバックグラウンドに移動されたときには、リアルタイム更新を自動的に一時停止できます。
  • アニメーション ドローアブルの一時停止と再開。ライフサイクル対応コンポーネントを使用すると、アプリがバックグラウンドで実行されているときはアニメーション ドローアブルを一時停止し、アプリがフォアグラウンドで実行されるようになったらドローアブルを再開することができます。

停止イベントへの対処

LifecycleAppCompatActivity または Fragment に属している場合、AppCompatActivity または FragmentonSaveInstanceState() が呼び出されると、Lifecycle の状態が CREATED に変化して ON_STOP イベントがディスパッチされます。

Fragment または AppCompatActivity の状態が onSaveInstanceState() を介して保存された場合、ON_START が呼び出されるまでその UI は変更不可と見なされます。状態が保存された後に UI を変更しようとすると、アプリのナビゲーションの状態に不整合が生じる可能性があります。状態が保存された後にアプリで FragmentTransaction を実行した場合に FragmentManager が例外をスローするのはこのためです。詳しくは、commit() をご覧ください。

LiveData は、オブザーバーに関連付けられた Lifecycle の状態が STARTED 以上でなければオブザーバーを呼び出さないようにして、こうしたエッジケースを追加設定なしで防止しています。バックグラウンドでは、オブザーバーを呼び出すことを決定する前に isAtLeast() が呼び出されます。

残念ながら、AppCompatActivityonStop() メソッドは onSaveInstanceState() の後に呼び出されるため、UI の状態の変更は許可されず、Lifecycle の状態もまだ CREATED の状態に遷移していないというギャップが残ります。

この問題が発生しないように、バージョン beta2 以下の Lifecycle クラスでは、イベントをディスパッチせずに状態を CREATED としてマークします。これにより、onStop() がシステムによって呼び出されるまでイベントはディスパッチされなくなりますが、現在の状態をチェックするすべてのコードで実際の値を取得できるようになります。

ただし、このソリューションには次の 2 つの大きな問題があります。

  • API レベルが 23 以下の場合、アクティビティの状態の保存は、部分的に別のアクティビティの対象であっても、実際には Android システムが行います。つまり、Android システムは onSaveInstanceState() を呼び出しますが、必ずしも onStop() を呼び出すわけではありません。このため、UI の状態が変更不可であっても、ライフサイクルがアクティブであるとオブザーバーが判断するインターバルが長くなる可能性があります。
  • LiveData クラスに同様の動作を公開するクラスは、Lifecycle バージョン beta 2 以下で提供されている回避策を実装する必要があります。

参考情報

ライフサイクル対応コンポーネントによるライフサイクルへの対応について詳しくは、以下の参考情報をご覧ください。

サンプル

  • Sunflower - アーキテクチャ コンポーネントを使用したおすすめの方法を示すデモアプリ

Codelab

ブログ