はじめに
改修案件にアサインされたものの、秘伝のソースの闇に触れてしまい、ものすごくイラモヤする日々を送る今日この頃。
このローテンションで、思っていることを書いてみようと思い立った。
つまり、うっぷん溜まってた。
やりたいこと
- アクティビティとフラグメントについて語りたい
- これを読んでる人を初心者にしたい
- 続きものを書いてみたかった(これ含めて全2回の予定)
書きながら思ったけど、ずぶの初心者向けというよりは、
Android を理解することをあきらめた Android エンジニア向けかもしれない。
ライフサイクルを理解することをあきらめろ
たぶん大多数の人が「いや、それが Android 的に大事なんじゃねーの!?」と思うだろう。思うはずだ。
全くもってその通りである。
まぁ、もちろん大事なんだけど、ぶっちゃけ今まで色々 Android エンジニアという人種に会ってきた中で、
この人はちゃんと理解してる人だなーなんて思ったのは、実は2割くらいである。
それも1年とか2年とかの経験年数じゃなくてもそうだったりするので、
__初心者がそんなもの理解できるわけがない。__素直にあきらめよう。
じゃあ、何がわかってればいいのかというと__「どこで何をやればいいのか」。__これに尽きる。
ということで、アクティビティとフラグメントのライフサイクルについて、それぞれ見てみよう。
アクティビティのライフサイクル
まず、アクティビティの主なライフサイクルメソッドは以下の通りである。
- onCreate
- onStart
- onResume
- onPause
- onStop
- onDestroy
とりあえず、これだけ覚えてれば初心者としては困らないはずだ。
もちろん、他にも onRestart とか onSaveInstanceState とか重要なものはあるんだけれども、
そんなの必要になったら覚えればいいと思う。
知識なんてものは、往々にして後から付いてくるものである。
で、この6つのライフサイクルメソッドは、それぞれ対になっている。
- onCreate - onDestroy
- onStart - onStop
- onResume - onPause
onCreate - onDestroy は、それぞれ画面を生成とき、画面を破棄するときに呼ばれる。
onStart - onStop は、それぞれ画面に対する処理を開始するとき、画面に対する処理を停止するときに呼ばれる。
onResume - onPause は、それぞれ画面を表示する準備をするとき、画面をバックグラウンドに回そうとするときに呼ばれる。
対になっているということは、そこに書く処理も対になってないといけないということである。
つまり、
onCreate で new したクラスは onDestroy で null とか詰めて破棄しないといけないし、
onStart で設定した値は onStop でリセットしないといけないし、
onResume でキックした処理は onPause でキャンセルしないといけないわけである。
なんだそんなこと当たり前だと思うだろうか?
その当たり前を意外とやってない奴らがかなり闊歩しているのですよ。いやホントに。
正直、これを意識しているだけで簡単に落ちるアプリはできなくなる。
予期せぬエラー?それはお前の書き方が悪いんじゃ!!ヽ(`Д´#)ノ
さて、対がわかったところで、本題である。
「どこで何をやればいいのか」
onCreate でやるべき処理は「画面を構成するのに必要なクラスやビューの生成&取得」である。
例えば、その画面が「画面を表示している秒数をカウントして、ボタンを押したらカウントをログに出す画面」だったとして、
onCreate でやるべき処理は「対象のボタンを findViewById すること」と「カウンタークラスを new すること」である。
対して、onDestroy では「対象のボタンの参照変数に null を詰めること」と
「カウンタークラスの参照変数に null を詰めること」をしてやれば良い。
onStart でやるべき処理は「画面を構成するのに必要なクラスやビューの初期化」である。
上の例で言えば、「対象のボタンに setOnClickListener すること(onClick の中にはもちろんログに出力する処理が書いてある)」と
「カウンタークラスの初期化メソッド(カウンター変数やタイマーを new するような)を呼ぶこと」である。
対して、onStop では「対象のボタンの setOnClickListener に null を設定」と
「カウンタークラスの破棄メソッド(カウンター変数やタイマーを null にするような)を呼ぶこと」をしてやれば良い。
onResume でやるべき処理は「画面を表示するための処理のキック」である。
上の例で言えば、「カウンタークラスのカウンター開始処理(指定時間ごとにカウンター変数に+1する処理)を呼び出すこと」である。
対して、onPause では「カウンタークラスのカウンター停止処理(タイマーを停止するような)を呼び出すこと」をしてやれば良い。
onCreate と onStart の処理はよく一緒にされる場合が多い。
まぁ、一緒にしても多くの場合は問題が起きないと思うが、__各対は周期が違う__ということは頭の片隅に置いておいた方が良い。
onStop されるまで onResume - onPause 組は複数回呼ばれることがあり得るし、
onDestroy されるまでは onStart - onStop 組が複数回呼ばれることもあり得る。
とはいえ、この辺を深く理解しようとすると間違いなく疲れて挫折するので、
- ライフサイクルメソッドは対になっている
- 面倒くさがらずにそれぞれのライフサイクルメソッドに必要な処理を分けて書く(特に生成と初期化)
- 各対は周期が違う
ということを意識しておけば、とりあえずやっていけるはずだと思う。
みんながんばれ。
フラグメントのライフサイクル
アクティビティはせいぜい6つのメソッドを覚えてればいいけど、フラグメントになるとそれが急に増える。
- onAttach
- onCreate
- onCreateView
- onActivityCreated
- onStart
- onResume
- onPause
- onStop
- onDestroyView
- onDestroy
- onDettach
総勢11個のライフサイクルメソッドがお出ましである。
もちろん、これらも最小限覚えておけばいいメソッドたちである。
それぞれの対は、こんな感じである。
- onAttach - onDettach
- onCreate - onDestroy
- onCreateView - onDestroyView
- onStart - onStop
- onResume - onPause
onActivityCreated に関しては、実は対がない。
何にでも例外があるということで、このメソッドに関しては
「アクティビティの onCreate が呼ばれた後に呼ばれるもの」くらいの認識で良い。
onAttach - onDettach は、それぞれアクティビティと紐づけられたとき、アクティビティとの紐づけを解除されたときに呼ばれる。
onCreateView - onDestroyView は、それぞれフラグメントのビューを生成したいとき、フラグメントのビューを破棄したいときに呼ばれる。
onCreate - onDestroy なんかのアクティビティと同じ名前のものは、それぞれアクティビティのライフサイクルの項で書いた通りに呼ばれる。
フラグメントのライフサイクルメソッドの__「どこで何をやればいいのか」__は、実は意外と厄介だ。
特に onCreate と onCreateView と onActivityCreated の使い分けなんかは、
それはもう初心者じゃなくてもなってない奴らが多い。
とりあえず、さらっとやっていこう。
onAttach でやるべき処理は「コールバック(アクティビティ)参照の取得」である。
フラグメントは setRetainInstance フラグを設定しない限り、アクセッサで値を渡すことはできない。
(フラグについては理解しなくていい、重要なのは通常はアクセッサを使えないということだけだ)
なので、アクティビティにコールバックを実装して、その参照をフラグメントが持っていないと、
フラグメントからアクティビティに値を渡せないのである。
onAttach は引数でフラグメントと紐付いているアクティティを渡してくるので、
それに対して「コールバックは実装されているよな?」というのを確認しつつ、参照を保持すればいいのである。
よく見るこういう感じのコードである。
public class SampleFragment extends Fragment {
/**
* コールバックインターフェース
*/
public interface OnSampleFragmentListener {
void onSample(final float sampleValue);
}
// コールバック参照
private OnSampleFragmentListener mListener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
// コールバック参照の取得
try {
mListener = (OnSampleFragmentListener) context;
} catch (ClassCastException e) {
// 実装されていなかったら開発者のミスなので、問答無用の例外スロー
throw new ClassCastException(context.toString() + " must implement OnSampleFragmentListener");
}
}
}
対して、onDettach では「コールバック(アクティビティ)参照の破棄」をしてやれば良い。
つまり、上のコードの例に合わせると「mListener = null;」をすれば良い。
onCreate でやるべき処理は「ロジッククラスの生成」である。
アクティビティのライフサイクルの項で出した
「画面を表示している秒数をカウントして、ボタンを押したらカウントをログに出す画面」を例に考えてみよう。
ここでやるべきは「カウンタークラスを new すること」。ただそれだけである。
アクティビティと違って、ここではビューについての処理は行わない。
それは後述の onCreateView の役目である。
対して、onDestroy では同じく「カウンタークラスの参照変数に null を詰めること」のみをしてやれば良い。
onCreateView でやるべき処理は「ビューの生成&初期化」である。
例に沿って考えると、ここでは「フラグメントのビューを生成すること」、
「生成したビューに対して findViewById し、対象のボタンの参照を保持すること」、
「対象のボタンに setOnClickListener すること」の3つである。
フラグメントではビューにはビュー用のライフサイクルが用意されているので、
ビューに対しての処理はそこに寄せた方が理解しやすくなる。
対して、onDestroyView では「対象のボタンの setOnClickListener に null を設定」、
「対象のボタンの参照を破棄すること」をしてやれば良い。
onActivityCreated でやるべき処理はかなり特殊で、「アクティビティの準備が整ってからキックしたい処理を呼ぶ」ことである。
例えば、継続して取得する位置情報処理のキックとか、バックグラウンドで動きっぱなしにしておくサービスの開始とか。
まぁ、正直そういうのはフラグメントでやらないでほしいのだが…。
(その理由は長くなるので、次回の「住み分け編」で触れることにする)
このライフサイクルメソッドには対がないのだが、要はタイミング的にアクティビティの onCreate と対になっていれば良いので、
onDestroy の方に onActivityCreated でキックしたり呼び出した処理に対するキャンセルだとか破棄だとかを呼び出せば良い。
onStart でやるべき処理は「ロジッククラスの初期化」である。
「画面を表示している秒数をカウントして、ボタンを押したらカウントをログに出す画面」の例に沿って書くと、
「カウンタークラスの初期化メソッド(カウンター変数やタイマーを new するような)を呼ぶこと」のみである。
対して、onStop でも「カウンタークラスの破棄メソッド(カウンター変数やタイマーを null にするような)を呼ぶこと」のみをしてやれば良い。
onResume - onPause はに関しては、アクティビティのライフサイクルの項で書いたのと同じように
「画面を表示するための処理のキック - 画面を表示するための処理のキャンセル」を書けば良い。
フラグメントはアクティビティに比べてライフサイクルのメソッドが増えた分、
__「どこで何をやればいいのか」__は本当に悩むと思う。
特にこの中だと、onActivityCreated についてはとても頭を悩ませると思うので、
最悪見なかったことにしても問題ない。
次回予告
なぜか急に始まった Android ライフサイクル談義!
とりあえず処理を書く場所は分かったけど、その前に話すことがあるんじゃないか!?
次回
「あの日挫折した Android 初心者へ戻るためのショートカット 〜住み分け編〜」
アクティビティとフラグメントって何が違うの!?
そもそも、アクティビティって何?フラグメントって何??
フラグメントが太るのは君のせい!
※ 内容は考えてあるけど、いつ書くかは未定です
※ 予告なく内容の変更はしませんが、テンションはその時の状況で変わります
まとめ
- ライフサイクルに対する理解をあきらめても Android は終了しないよ!
- メソッドはそれぞれ対になってるよ!書く処理も対になっていないと死ぬよ!!(アプリが)
- ちょっとうっぷん晴れたよ!
参考
特になし