1. ようこそ
はじめに
この演習では、アクティビティのライフサイクルについて詳しく説明します。ライフサイクルとは、アクティビティが作成されて破棄され、システムがリソースを再利用するまで(すなわちアクティビティの存続期間中)に取り得る一連の状態のことです。ユーザーがアプリのアクティビティ間やアプリの内外を移動すると、アクティビティは、そのライフサイクルのさまざまな状態に遷移します。
アクティビティのライフサイクルの各ステージには、対応するコールバック メソッド(onCreate()
、onStart()
、onPause()
など)があります。アクティビティの状態が変更されると、関連するコールバック メソッドが呼び出されます。このうち、メソッド onCreate()
についてはすでに説明しました。Activity
クラスのライフサイクル コールバック メソッドのいずれかをオーバーライドすることで、ユーザーまたはシステムのアクションに応じてアクティビティのデフォルトの動作を変更できます。
アクティビティの状態も、ユーザーがデバイスを縦向きから横向きに回転させたときなど、デバイス設定変更に応じて変化することがあります。このような設定変更が発生すると、アクティビティは破棄され、デフォルトの状態で再作成されます。そのため、ユーザーがアクティビティに入力した情報が失われる可能性があります。ユーザーの混乱を避けるため、予期せぬデータ損失を防ぐためのアプリ開発は重要です。この演習では、後ほど設定変更を試し、デバイス設定変更やその他のアクティビティのライフサイクル イベントに応じてアクティビティの状態を維持する方法について学びます。
この演習では、TwoActivities アプリにロギング ステートメントを追加し、アプリの使用に伴うアクティビティのライフサイクルの変化を確認します。その後、これらの変更と連動して、このような状況下でユーザー入力を処理する方法を探ります。
前提となる知識
次の知識は必須です。
- Android Studio でアプリ プロジェクトを作成、実行する。
- アプリにログ ステートメントを追加し、[Logcat] ペインでログを表示する。
Activity
とIntent
について理解したうえで使用し、問題なく操作する。
学習内容
Activity
のライフサイクルの仕組み。Activity
が開始、一時停止、停止、および破棄されるタイミング。Activity
の変更に関連するライフサイクル コールバック メソッドについて。Activity
のライフサイクル イベントにつながる可能性のあるアクション(設定変更など)の効果。- ライフサイクル イベント全体で
Activity
の状態を保持する方法。
演習内容
- 以前に演習した TwoActivities アプリにコードを追加して、ロギング ステートメントを含めるためのさまざまな
Activity
ライフサイクル コールバックを実装する。 - アプリの実行中およびアプリ内での各
Activity
の操作中に、状態がどのように変化するかを確認する。 - ユーザーの動作やデバイス設定変更に応じて予期せず再作成された
Activity
インスタンスの状態を保持できるよう、アプリを変更する。
2. アプリの概要
この演習では、TwoActivities アプリに追加します。アプリの外観と動作は、前回の Codelab とほぼ同じです。これには 2 つの Activity
の実装が含まれ、ユーザーはそれらの間で送信できるようになります。今回の演習でアプリに変更を加えても、表示されるユーザーの動作には影響しません。
3.タスク 1: TwoActivities にライフサイクル コールバックを追加する
このタスクでは、Activity
ライフサイクル コールバック メソッドをすべて実装し、それらのメソッドが呼び出されたときにメッセージを logcat に出力します。これらのログメッセージを見ると、Activity
のライフサイクルの状態がいつ変化したか、その変化が実行時のアプリにどのような影響を与えるかが確認できます。
1.1(任意)TwoActivities プロジェクトをコピーする
この演習のタスクでは、前回の演習で作成した既存の TwoActivities プロジェクトを変更します。以前の TwoActivities プロジェクトをそのまま保持する場合は、付録: ユーティリティの手順に沿ってプロジェクトのコピーを作成します。
1.2 MainActivity にコールバックを実装する
- Android Studio で TwoActivities プロジェクトを開き、[Project] > [Android] ペインで MainActivity を開きます。
onCreate()
メソッドに、次のログ ステートメントを追加します。
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
- ステートメントを使用して、イベントのログに
onStart()
コールバックのオーバーライドを追加します。
@Override
public void onStart(){
super.onStart();
Log.d(LOG_TAG, "onStart");
}
ショートカットを作成するには、Android Studio で [Code] > [Override Methods] を選択します。ダイアログが開き、クラスでオーバーライドできるすべてのメソッドが表示されます。リストから 1 つ以上のコールバック メソッドを選択すると、それらのメソッド用の完全なテンプレート(スーパークラスへの必要な呼び出しを含む)が挿入されます。
onStart()
メソッドをテンプレートとして使用し、onPause()
、onRestart()
、onResume()
、onStop()
、onDestroy()
のライフサイクル コールバックを実装します。
すべてのコールバック メソッドのシグネチャは同じです(名前を除く)。onStart()
をコピー、貼り付けして他のコールバック メソッドを作成するには、必ずスーパークラスで適切なメソッドを呼び出すように内容を更新し、正しいメソッドを記録してください。
- アプリを実行します。
- Android Studio の下部にある [Logcat] タブをクリックして、[Logcat] ペインを表示します。開始時に
Activity
が遷移した 3 つのライフサイクル状態を示す 3 つのログメッセージが表示されるはずです。
D/MainActivity: ------- D/MainActivity: onCreate D/MainActivity: onStart D/MainActivity: onResume
1.3 SecondActivity にライフサイクル コールバックを実装する
MainActivity
のライフサイクル コールバック メソッドを実装したところで、SecondActivity
についても同様に行います。
- SecondActivity を開きます。
- クラスの先頭に、
LOG_TAG
変数の定数を追加します。
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
- ライフサイクル コールバックとログ ステートメントを 2 つ目の
Activity
に追加します。(MainActivity
のコールバック メソッドをコピーして貼り付けます)。 finish()
メソッドの直前にあるreturnReply()
メソッドに、ログ ステートメントを追加します。
Log.d(LOG_TAG, "End SecondActivity");
1.4 アプリの実行中にログを確認する
- アプリを実行します。
- Android Studio の下部にある [Logcat] タブをクリックして、[Logcat] ペインを表示します。
- 検索ボックスに「Activity」と入力します。Android logcat は非常に長く、雑然としたものになる場合があります。各クラスの
LOG_TAG
変数には、MainActivity
またはSecondActivity
という単語が含まれているため、このキーワードを使用すると、目的の項目だけが表示されるようにログをフィルタできます。
アプリを使用してテストし、さまざまなアクションに応じて発生するライフサイクル イベントに注意してください。特に、以下のことを試してみてください。
- アプリを通常どおりに使用します(メッセージを送信し、別のメッセージで返信します)。
- 戻るボタンを使用して、2 つ目の
Activity
からメインのActivity
に戻ります。 - アプリバーの上矢印を使用して、2 つ目の
Activity
からメインのActivity
に戻ります。 - アプリのメインと 2 つ目の
Activity
の両方で、デバイスを異なるタイミングで回転させ、ログと画面の内容を確認します。 - 概要ボタン(ホームの右側にある四角いボタン)を押して、アプリを閉じます([X] をタップします)。
- ホーム画面に戻り、アプリを再起動します。
ヒント: エミュレータでアプリを実行している場合は、Control+F11
または Control+Function+F11
で回転をシミュレートできます。
タスク 1 の解答コード
次のコード スニペットは、最初のタスクの解答コードを示しています。
MainActivity
次のコード スニペットは、MainActivity
に追加したコードを示していますが、クラス全体ではありません。
onCreate()
メソッド:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Log the start of the onCreate() method.
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
}
その他のライフサイクル メソッド:
@Override
protected void onStart() {
super.onStart();
Log.d(LOG_TAG, "onStart");
}
@Override
protected void onPause() {
super.onPause();
Log.d(LOG_TAG, "onPause");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(LOG_TAG, "onRestart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(LOG_TAG, "onResume");
}
@Override
protected void onStop() {
super.onStop();
Log.d(LOG_TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
}
SecondActivity
次のコード スニペットは、SecondActivity
に追加したコードを示していますが、クラス全体ではありません。
SecondActivity
クラスの先頭で、変数に次の定数を追加します。
private static final String LOG_TAG = SecondActivity.class.getSimpleName();
returnReply()
メソッド:
public void returnReply(View view) {
String reply = mReply.getText().toString();
Intent replyIntent = new Intent();
replyIntent.putExtra(EXTRA_REPLY, reply);
setResult(RESULT_OK, replyIntent);
Log.d(LOG_TAG, "End SecondActivity");
finish();
}
その他のライフサイクル メソッド:
上記の MainActivity
と同じです。
4. タスク 2: Activity インスタンスの状態を保存して復元する
システム リソースやユーザーの動作によっては、想定よりもはるかに頻繁に、アプリの各 Activity
が破棄および再構築される場合があります。
最後のセクションでデバイスまたはエミュレータを回転したとき、この動作に気づいたかもしれません。デバイスを回転することは、デバイス設定変更の一例です。回転は最も一般的な例ですが、あらゆる設定変更によって現在の Activity
は破棄され、新規同様に再作成されます。コード内でこの動作を考慮していない場合、設定変更の発生時に Activity
レイアウトがデフォルトの外観と初期値に戻され、ユーザーがアプリの場所、データ、または進行中の状態情報を失う可能性があります。
各 Activity
の状態は、Activity
インスタンスの状態と呼ばれる Bundle
オブジェクト内に Key-Value ペアのセットとして保存されます。デフォルトの状態情報は、Activity
が停止する直前に、システムによりインスタンスの状態 Bundle
に保存され、その Bundle
が新しい Activity
インスタンスに渡されて復元されます。
予期せず破棄されて再作成される場合、Activity
内のデータが失われないためには、onSaveInstanceState()
メソッドを実装する必要があります。Activity
が破棄されて再作成される可能性がある場合、システムにより Activity
(onPause()
~onStop()
)にこのメソッドが呼び出されます。
インスタンス状態で保存するデータは、現在のアプリ セッションの間、この特定の Activity
のインスタンスだけに固有のものです。新しいアプリ セッションを停止して再起動すると、Activity
インスタンスの状態は失われ、Activity
はデフォルトの外観に戻ります。アプリ セッション間でユーザーデータを保存する必要がある場合は、共有設定またはデータベースを使用します。どちらについても後に学習します。
2.1 onSaveInstanceState() を使用してアクティビティ インスタンスの状態を保存する
デバイスを回転させても、2 番目の Activity
の状態にはまったく影響しないことに気付いたかもしれません。これは、2 番目の Activity
レイアウトと状態が、レイアウトとそれをアクティブ化した Intent
から生成されるためです。Activity
が再作成された場合でも Intent
は保持され、2 番目の Activity
で onCreate()
メソッドが呼び出されるたびに、その Intent
内のデータが引き続き使用されます。
さらに各 Activity
では、デバイスが回転した場合でも、メッセージや返信の EditText
要素に入力したテキストは保持されます。これは、レイアウト内の一部の View
要素については、その状態情報が構成変更後も自動的に保存されるためで、EditText
の現在の値もそうしたケースの一つです。
したがって、注目すべき Activity
の状態は、返信ヘッダーの TextView
要素とメイン Activity
の返信テキストのみです。デフォルトでは、TextView
要素はいずれも表示されません。これらは、2 番目の Activity
からメインの Activity
にメッセージを返信した後にのみ表示されます。
このタスクでは、onSaveInstanceState()
を使用して、これら 2 つの TextView
要素のインスタンス状態を保持するコードを追加します。
- MainActivity を開きます。
onSaveInstanceState()
のスケルトン実装をActivity
に追加するか、[Code] > [Override Methods] を使用してスケルトン オーバーライドを挿入します。
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
- ヘッダーが現在表示されているかどうかを確認し、表示されている場合は、
putBoolean()
メソッドと"reply_visible"
鍵を使用してその表示状態をBundle
状態にします。
if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
outState.putBoolean("reply_visible", true);
}
返信のヘッダーとテキストは、2 番目の Activity
からの返信があるまで非表示になります。ヘッダーが表示されている場合は、保存する必要のある応答データがあります。この公開状態だけに注目してください。ヘッダーの実際のテキストは変更されないため、保存する必要はありません。
- 同じチェック内で、返信テキストを
Bundle
に追加します。
outState.putString("reply_text",mReplyTextView.getText().toString());
ヘッダーが表示されている場合、返信メッセージ自体も表示されていると考えられます。返信メッセージの現在の表示状態をテストや保存する必要はありません。メッセージの実際のテキストのみが、"reply_text"
鍵を使用して Bundle
状態に移行します。
Activity
の作成後に変更される可能性がある View
要素の状態だけを保存します。アプリの他の View
要素(EditText
、Button
)は、デフォルトのレイアウトからいつでも再作成できます。
EditText
の内容など、一部の View
要素の状態はシステムによって保存されます。
2.2 onCreate() で Activity インスタンスの状態を復元する
Activity
インスタンスの状態を保存した後、Activity
が再作成されたときにも復元する必要があります。これは onCreate()
で行うか、Activity
の作成後に onStart()
の後で呼び出される onRestoreInstanceState()
コールバックを実装して行うことができます。
ほとんどの場合、Activity
の状態を復元するのに適した場所は onCreate()
です。これにより、その状態を含む UI が可能な限り早く利用できるようになります。すべての初期化が完了後に onRestoreInstanceState()
でそれを実行する、またはデフォルト実装の使用の有無をサブクラスが決定できるようにすると、便利な場合があります。
onCreate()
メソッドで、View
変数がfindViewById()
で初期化された後、savedInstanceState
が null でないことを確認するテストを追加します。
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
// Restore the state.
if (savedInstanceState != null) {
}
Activity
が作成されると、システムは Bundle
状態を唯一の引数として onCreate()
に渡します。初めて onCreate()
が呼び出されてアプリが起動したとき、Bundle
は null
です。このアプリの初回起動時には既存の状態はありません。後続の onCreate()
への呼び出しでは、onSaveInstanceState()
に保存したデータが Bundle に入力されます。
- そのチェックの内部で
"reply_visible"
鍵を使用して、Bundle
から現在の公開設定(true または false)を取得します。
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
}
- 前の行の下に isVisible 変数のテストを追加します。
if (isVisible) {
}
Bundle
状態に reply_visible
鍵がある(そのため isVisible
が true
である)場合、状態を復元する必要があります。
isVisible
テスト内でヘッダーを表示します。
mReplyHeadTextView.setVisibility(View.VISIBLE);
"reply_text"
鍵を使用してBundle
からテキスト返信メッセージを受け取り、その返信をTextView
に設定して文字列を表示します。
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
- 返信の
TextView
も表示されるようにします。
mReplyTextView.setVisibility(View.VISIBLE);
- アプリを実行します。デバイスまたはエミュレータを回転させて、
Activity
が再作成された後に返信メッセージ(存在する場合)が画面に残るようにします。
タスク 2 の解答コード
次のコード スニペットは、このタスクの解答コードを示しています。
MainActivity
次のコード スニペットは、MainActivity
に追加したコードを示していますが、クラス全体ではありません。
onSaveInstanceState()
メソッド:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// If the heading is visible, message needs to be saved.
// Otherwise we're still using default layout.
if (mReplyHeadTextView.getVisibility() == View.VISIBLE) {
outState.putBoolean("reply_visible", true);
outState.putString("reply_text",
mReplyTextView.getText().toString());
}
}
onCreate()
メソッド:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(LOG_TAG, "-------");
Log.d(LOG_TAG, "onCreate");
// Initialize all the view variables.
mMessageEditText = findViewById(R.id.editText_main);
mReplyHeadTextView = findViewById(R.id.text_header_reply);
mReplyTextView = findViewById(R.id.text_message_reply);
// Restore the saved state.
// See onSaveInstanceState() for what gets saved.
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
// Show both the header and the message views. If isVisible is
// false or missing from the bundle, use the default layout.
if (isVisible) {
mReplyHeadTextView.setVisibility(View.VISIBLE);
mReplyTextView.setText(savedInstanceState
.getString("reply_text"));
mReplyTextView.setVisibility(View.VISIBLE);
}
}
}
プロジェクト全体:
Android Studio プロジェクト: TwoActivitiesLifecycle
5. コーディングの課題
課題: ユーザーが作成するリスト用のメイン アクティビティと、一般的なショッピング アイテム リスト用の 2 つ目のアクティビティを備えたシンプルなショッピング リスト アプリを作成する。
- メイン アクティビティには作成するリストが含まれ、このリストは 10 個の空の
TextView
要素で構成される必要があります。 - メイン アクティビティのアイテムを追加ボタンをクリックすると、一般的なショッピング アイテム(チーズ、米、りんごなど)のリストを含む 2 つ目のアクティビティが開始されます。
Button
要素を使用してアイテムを表示します。 - アイテムを選択すると、ユーザーはメイン アクティビティに戻り、空の
TextView
が更新されて選択したアイテムが含まれます。
Intent
を使用して、Activity
間で情報を受け渡しします。ユーザーがデバイスを回転させたときに、ショッピング リストの現在の状態が保存されているようにします。
6. まとめ
- アクティビティのライフサイクルは
Activity
が最初に作成されたときに始まり、それが移行して、そのActivity
リソースが Android システムによって再利用されたときに終了するまでの一連の状態です。 - ユーザーが、
Activity
間やアプリの内外を移動すると、各Activity
がActivity
ライフサイクルの状態間を移行します。 Activity
ライフサイクルの各状態には、Activity
クラスでオーバーライド可能な、対応するコールバック メソッドがあります。- ライフサイクル メソッドは、
onCreate()
、onStart()
、onPause()
、onRestart()
、onResume()
、onStop()
、onDestroy()
です。 - ライフサイクル コールバック メソッドをオーバーライドすると、
Activity
がその状態に遷移したときに発生する動作を追加できます。 - [Code] > [Override] を使用すると、Android Studio でクラスにスケルトン オーバーライド メソッドを追加できます。
- 回転などのデバイス構成の変更を加えると、新規の場合と同様に
Activity
が破棄され再作成されます。 - 構成変更時、
Activity
の状態の一部(EditText
要素の現在の値など)は保持されます。他のすべてのデータについては、自分で明示的に保存する必要があります。 Activity
インスタンスの状態をonSaveInstanceState()
メソッドに保存します。- インスタンスの状態データは、シンプルな Key-Value ペアとして
Bundle
に保存されます。Bundle
メソッドを使用してデータを入れ、Bundle
からデータを取得します。 onCreate()
(推奨)か、またはonRestoreInstanceState()
でインスタンスの状態を復元します。
7. 関連概念
関連概念のドキュメントについては、「2.2: アクティビティのライフサイクルと状態」をご覧ください。
8. 詳細
Android Studio のドキュメント:
Android デベロッパー ドキュメント:
9. 宿題
このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。
- 必要に応じて宿題を与える
- 宿題の提出方法を生徒に伝える
- 宿題を採点する
インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。
この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。
アプリをビルドして実行する
- カウンタ
TextView
、カウンタをインクリメントするButton
、およびEditText
を保持するレイアウトでアプリを作成します。以下のスクリーンショットは一例です。レイアウトを正確にコピーする必要はありません。 - カウンタをインクリメントする
Button
のクリック ハンドラを追加します。 - アプリを実行し、カウンタをインクリメントします。
EditText
にテキストを入力します。 - デバイスを回転させます。カウンタはリセットされますが、
EditText
はリセットされません。 onSaveInstanceState()
を実装して、アプリの現在の状態を保存します。onCreate()
を更新して、アプリの状態を復元します。- デバイスを回転させたときに、アプリの状態が保持されるようにしてください。
以下の質問に回答してください
問題 1
onSaveInstanceState()
を実装する前に宿題のアプリを実行すると、デバイスを回転させた場合にどうなりますか?1 つ選択してください。
EditText
に入力したテキストは削除されるが、カウンタは保持される。- カウンタが 0 にリセットされ、
EditText
に入力したテキストは削除される。 - カウンタは 0 にリセットされるが、
EditText
の内容は保持される。 - カウンタも
EditText
の内容も保持される。
問題 2
デバイス構成の変更(回転など)が発生したとき、どのような Activity
ライフサイクル メソッドが呼び出されますか?1 つ選択してください。
- Android は
onStop()
を呼び出してActivity
を直ちにシャットダウンする。コードでActivity
を再起動する必要がある。 - Android は
onPause()
、onStop()
、onDestroy()
を呼び出してActivity
をシャットダウンする。コードでActivity
を再起動する必要がある。 - Android は
onPause()
、onStop()
、onDestroy()
を呼び出してActivity
をシャットダウンする。その後、再起動してonCreate()
、onStart()
、onResume()
を呼び出す。 - Android は直ちに
onResume()
を呼び出す。
問題 3
Activity
のライフサイクルでは、どのタイミングで onSaveInstanceState()
が呼び出されますか?1 つ選択してください。
onSaveInstanceState()
は、onStop()
メソッドの前に呼び出される。onSaveInstanceState()
は、onResume()
メソッドの前に呼び出される。onSaveInstanceState()
は、onCreate()
メソッドの前に呼び出される。onSaveInstanceState()
は、onDestroy()
メソッドの前に呼び出される。
質問 4
Activity
が終了または破棄される前に、データを保存するのに最適な Activity
ライフサイクル メソッドは次のうちどれですか?1 つ選択してください。
onPause()
またはonStop()
onResume()
またはonCreate()
onDestroy()
onStart()
またはonRestart()
採点のためアプリを送信する
採点者のガイダンス
アプリに以下の機能があることを確認します。
- カウンタ、そのカウンタをインクリメントする
Button
、EditText
が表示される。 Button
をクリックすると、カウンタが 1 ずつ増える。- デバイスを回転させると、カウンタと
EditText
の状態がともに保持される。 MainActivity.java
の実装で、onSaveInstanceState()
メソッドを使用してカウンタ値を保存する。onCreate()
の実装で、outState
Bundle
が存在するかどうかをテストする。Bundle
が存在する場合、カウンタ値は復元され、TextView
に保存される。
10. 次の Codelab
Android デベロッパー向け基礎(V2)コースで、次の実践的な Codelab を確認するには、Android デベロッパー向け基礎(V2)の Codelab をご覧ください。
概念の章、アプリ、スライドへのリンクを含むコースの概要については、Android デベロッパーの基礎(バージョン 2)をご覧ください。