ライフサイクル対応コンポーネントによるライフサイクルへの対応  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 は、コンポーネント(アクティビティやフラグメントなど)のライフサイクルの状態に関する情報を保持するクラスです。このクラスを使用すると、他のオブジェクトでその状態を監視できます。

Lifecycle は、次の 2 つの主要な列挙型を使用して、関連するコンポーネントのライフサイクル ステータスを追跡します。

イベント
フレームワークと Lifecycle クラスからディスパッチされるライフサイクル イベント。これらのイベントは、アクティビティとフラグメントのコールバック イベントにマッピングされます。
状態
Lifecycle オブジェクトによって追跡されるコンポーネントの現在の状態。
ライフサイクルの状態の図
図 1. Android アクティビティ ライフサイクルを構成する状態とイベント

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

クラスでコンポーネントのライフサイクル ステータスを監視するには、アノテーションをそのメソッドに追加します。さらに、次の例に示すように、Lifecycle クラスの addObserver() メソッドを呼び出してオブザーバーのインスタンスを渡すことで、オブザーバーを追加できます。

Kotlin

    class MyObserver : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(MyObserver())
    

Java

    public class MyObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        public void connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        public void disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
    

上の例では、myLifecycleOwner オブジェクトに LifecycleOwner インターフェースが実装されています。次のセクションでこのインターフェースについて説明します。

LifecycleOwner

LifecycleOwner は、クラスに Lifecycle が含まれていることを示すシングル メソッド インターフェースです。このインターフェースの単一のメソッドである getLifecycle() をクラスで実装する必要があります。アプリのプロセス全体のライフサイクルを管理する場合は、ProcessLifecycleOwner をご覧ください。

このインターフェースでは、Lifecycle の所有権を個々のクラス(FragmentAppCompatActivity など)から分離して、それらのクラスと連携するコンポーネントを作成することができます。LifecycleOwner インターフェースはすべてのカスタム アプリクラスで実装できます。

LifecycleObserver を実装しているコンポーネントは、LifecycleOwner を実装しているコンポーネントとシームレスに連携します。これは、オブザーバーが監視用に登録可能なライフサイクルをオーナーが提供できるためです。

位置情報の追跡の例では、MyLocationListener クラスで LifecycleObserver を実装し、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
    ) {

        private var enabled = false

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun start() {
            if (enabled) {
                // connect
            }
        }

        fun enable() {
            enabled = true
            if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                // connect if not connected
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun stop() {
            // disconnect if connected
        }
    }
    

Java

    class MyLocationListener implements LifecycleObserver {
        private boolean enabled = false;
        public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
           ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void start() {
            if (enabled) {
               // connect
            }
        }

        public void enable() {
            enabled = true;
            if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
                // connect if not connected
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void stop() {
            // disconnect if connected
        }
    }
    

この実装では、LocationListener クラスが完全にライフサイクル対応になっています。LocationListener を別のアクティビティやフラグメントから使用する必要がある場合は、その初期化のみが必要になります。セットアップとティアダウンの処理はすべて、クラス自体で管理されます。

Android のライフサイクルと連動する必要があるクラスをライブラリで提供する場合は、ライフサイクル対応コンポーネントを使用することをおすすめします。これらのコンポーネントはライブラリ クライアントで簡単に統合できます。クライアント側でライフサイクルを手動で管理する必要はありません。

カスタムの LifecycleOwner の実装

Support Library 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 以下で提供されている回避策を実装する必要があります。

その他のリソース

ライフサイクル対応コンポーネントを使用したライフサイクルの処理について詳しくは、以下のリソースをご覧ください。

サンプル

コードラボ

ブログ