注意
Handling Lifecyclesの和訳になります。
わかりづらい表現を意訳したり、回りくどいところを端折ったりしています。
和訳に自信がないところもあるため、間違いを見つけた場合は指摘してください。
ライフサイクルのハンドリング
android.arch.lifecycleパッケージはActivity、Fragmentのライフサイクルに対応したコンポーネントを構築するためのクラスとインターフェースを提供します。
注:android.arch.lifecycleの導入についてはadding components to your projectを参照してください。
Android Framework で定義されているほとんどのアプリコンポーネントにはライフサイクルがあります。
これらのライフサイクルはOSまたは、実行中のフレームワークコードによって管理されています。
Androidはどのように動作するのかを把握し、アプリはそれを考慮することが肝になります。
そうしないとメモリリークやアプリのクラッシュが発生する可能性があります。
画面上にデバイスのロケーションを表示するActivityがあるとします。
一般的な実装では以下のようになります。
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;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
public void onStart() {
super.onStart();
myLocationListener.start();
}
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
このサンプルは一見良いように見えますが、実際のアプリでは多くの箇所から呼び出しますし、onStart()
とonStop()
にはもっと多くの処理が描かれます。
onStart()
でいくつかの構成のチェックを行なった後に、ロケーションの観測を開始するようにした場合、もしかしたら、Activityが非活性状態になった後に構成のチェックが終わってロケーションの観測が開始される可能性があります。
つまり、myLocationListener.stop()
が呼び出された後にmyLocationListener.start()
が呼び出されることになります。
この場合、ロケーションサービスに接続され続けてしまいます。
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, location -> {
// update UI
});
}
public void onStart() {
super.onStart();
Util.checkUserStatus(result -> {
// what if this callback is invoked AFTER activity is stopped?
if (result) {
myLocationListener.start();
}
});
}
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
android.arch.lifecycleパッケージは柔軟的で、独立した方法でこの問題に対応するクラスとインターフェースを提供します。
Lifecycle
Lifecycleは、Activity、Fragmentなどのコンポーネントのライフサイクルに関する情報を保持し、他のオブジェクトがこの状態を観察できるようにするクラスです。
Lifecycleでは2つの主要な列挙型を使用して、関連するコンポーネントのライフサイクルステータスを追跡します。
Event
Android フレームワークおよびLifecycleクラスから送られるライフサイクルイベント。
これらのイベントは、Activity、Fragmentのコールバックイベントに対応します。
State
Lifecycleオブジェクトによってトラッキングされたコンポーネントの現在の状態。
State
をグラフのノード、Event
をこれらのノード間の端線とします。
クラスのメソッドにアノテーションを追加することで、コンポーネントのライフサイクルステータスを監視することができます。
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
}
}
aLifecycleOwner.getLifecycle().addObserver(new MyObserver());
LifecycleOwner
LifecycleOwnerはクラスにLifecycleがあることを示すメソッドを1つ持つインターフェースです。
getLifecycle()をクラスに実装する必要があります。
このクラスはLifecycleをもつクラス(Activity、Fragmentなど)を抽象化して、別のコンポーネントでもライフサイクルに対応した処理を書くことを可能にします。
注:Architecture ComponentsはまだAlpha版のため、FragmentクラスとAppCompatActivityクラスには実装していません。(不安定なAPIに依存しないように)
Lifecycleが安定するまでは、便宜上、LifecycleActivityおよびLifecycleFragmentクラスが用意されています。
Lifecycleがリリースされた後に、サポートライブラリのActivityとFragmentにLifecycleOwnerが実装されます。
LifecycleActivityクラスおよびLifecycleFragmentクラスはその時点で廃止する予定です。
また、カスタムActivity、FragmentにLifecycleOwnerを実装するも参照してください。
下記の例ではLifecycleObserverを実装したMyLocationListener
クラスをonCreate()
でLifecycleを渡して初期化します。
これによってMyLocationListener
は完全に独立させることができるので、必要に応じで実装を排除することができます。
class MyActivity extends LifecycleActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}
既に周知のとおり、ActivityのonSaveInstanceState()
よりもあとにFragmentの操作を行うとアプリはクラッシュします。
そのため、onSaveInstanceState()
よりも後にFragmentを操作するコールバックが呼び出さないように考慮する必要があります。
これを簡単にするために、Lifecycleでは他のオブジェクトからコンポーネント(Activity、Fragment)の現在の状態を照会することができます。
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.getState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
この実装でLocationListener
クラスは完全にライフサイクルに対応しました。
Activityに管理されることなく、自身の初期化と破棄処理を行うことができます。
他のActivity、FragmentからLocationListener
が必要な場合には、LocationListener
の初期化をするだけです。
Lifecycleで動作するコンポーネントを"ライフサイクル対応コンポーネント"と呼びます。
Androidライフサイクルによって動作するクラスを提供するライブラリにはライフサイクル対応コンポーネントであることが推奨されます。
ライフサイクル対応コンポーネントにすることでアプリ側でライフサイクルの管理行わなくてもよくなるので、導入が簡単になります。
LiveDataはライフサイクル対応コンポーネントの一例です。
ViewModelと一緒にLiveDataを使用するとAndroidのライフサイクルを考慮しながらUIにデータをセットすることができます。
Lifecyclesのベストプラクティス
・UIコントローラ(ActivityやFragment)はできるだけ少ない実装にしてください。
データを取得はViewModelに任せて、Viewの更新はLiveDataを観察(observe)するようにします。
・データが更新された時にViewを更新したり、ユーザアクションをViewModelに通知することを担当するデータ駆動型のUIコントローラを作成するようにしてください。
・ViewModelクラスにデータロジックを書きます。
ViewModelはUIコントローラとアプリの外部とを接続するコネクタとして機能します。
ただし、ViewModelはネットワークなどからデータの取得は行いません。代わりにViewModelは適切なコンポーネントを呼び出してデータの取得を行い、結果をUIコントローラに返します。
・データバインディングを使用すると、ViewとUIコントローラの間をクリーンなインターフェースで保つことができます。
データバインディングによって、Viewへの値の代入やViewの更新ロジックをレイアウトファイルに直接書けるので、Activity、Fragmentに書くコードを少なくすることができます。
もし、Javaクラス内でこれを行いたい場合はButter Knifeのようなライブラリを使用することでボイラーコードを避けて抽象度を高めることができます。
・複雑なUIの場合は、UIの変更を処理するためのPresenterクラスを作成することを検討してください。
これはやりすぎかもしれませんが、テストを簡単にするという利点もあります。
・ViewModelでViewまたはActivity Contextを参照しないようにしてください。
ViewModelはActivityよりも生存期間が長いため、メモリリークを引き起こします。
Appendix
カスタムActivity、FragmentにLifecycleOwnerを実装する
独自のActivty、FragmentにLifecycleRegistryOwnerを実装することでLifecycleOwnerに変換することができます。(LifecycleActivityクラスおよびLifecycleFragmentクラスを継承する代わりに)
public class MyFragment extends Fragment implements LifecycleRegistryOwner {
LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}
LifecycleOwnerを作成するカスタムクラスがある場合には、LifecycleRegistryを使用して、イベントをクラスに通知する必要があります。
Activity、FragmentにLifecycleRegistryOwnerを実装することによって自動的にライフサイクル対応コンポーネントにイベントを通知することができます。