1. ようこそ
この実践的な Codelab は、「Android デベロッパーの基礎(バージョン 2)」コースの「ユニット 1: スタートガイド」の一部です。次の Codelab を順番に進めることで、このコースを最大限に活用できます。
- コースに含まれる Codelab の完全なリストについては、Android デベロッパー向け基礎(V2)の Codelab をご覧ください。
- コンセプトの全章、アプリ、スライドへのリンクなど、コースの詳細については、Android デベロッパーの基礎(バージョン 2)をご覧ください。
はじめに
この演習では、アクティビティのライフサイクルについて詳しく説明します。ライフサイクルとは、アクティビティが作成されて破棄され、システムがリソースを再利用するまで(すなわちアクティビティの存続期間中)に取り得る一連の状態のことです。ユーザーがアプリのアクティビティ間やアプリの内外を移動すると、アクティビティは、そのライフサイクルのさまざまな状態に遷移します。
アクティビティのライフサイクルの各ステージには、対応するコールバック メソッド(onCreate()、onStart()、onPause() など)があります。アクティビティの状態が変更されると、関連するコールバック メソッドが呼び出されます。これらのメソッドの 1 つである onCreate() についてはすでに説明しました。Activity クラスのライフサイクル コールバック メソッドのいずれかをオーバーライドすることで、ユーザーまたはシステムのアクションに応じてアクティビティのデフォルトの動作を変更できます。
アクティビティの状態も、ユーザーがデバイスを縦向きから横向きに回転させたときなど、デバイス設定変更に応じて変化することがあります。このような設定変更が発生すると、アクティビティは破棄され、デフォルトの状態で再作成されます。そのため、ユーザーがアクティビティに入力した情報が失われる可能性があります。ユーザーの混乱を避けるため、予期せぬデータ損失を防ぐためのアプリ開発は重要です。この演習では、後ほど設定変更を試し、デバイス設定変更やその他のアクティビティのライフサイクル イベントに応じてアクティビティの状態を維持する方法について学びます。
この演習では、TwoActivities アプリにロギング ステートメントを追加し、アプリの使用に伴うアクティビティのライフサイクルの変化を確認します。その後、これらの変更と連動して、このような状況下でユーザー入力を処理する方法を探ります。
前提条件
次のことを行える必要があります。
- Android Studio でアプリ プロジェクトを作成して実行します。
- アプリにログ ステートメントを追加し、[Logcat] ペインでログを表示する。
- アクティビティとインテントを理解して操作し、問題なく操作できるようにする。
学習内容
- アクティビティのライフサイクルの仕組み。
- アクティビティが開始、一時停止、停止、および破棄されるとき。
- アクティビティの変更に関連するライフサイクル コールバック メソッドについて。
- アクティビティのライフサイクル イベントにつながる可能性のあるアクション(設定変更など)の影響。
- ライフサイクル イベント全体でアクティビティの状態を保持する方法。
演習内容
- 以前の演習の TwoActivities アプリにコードを追加して、ロギング ステートメントを含めるためのさまざまな Activity ライフサイクル コールバックを実装します。
- アプリを実行しているときや、アプリ内の各アクティビティを操作したときに、状態の変化を確認します。
- ユーザー行動やデバイス上の構成変更に応じて予期せず再作成された Activity のインスタンス状態を保持するように、アプリを変更する。
2. アプリの概要
この演習では、TwoActivities アプリに追加します。アプリの外観と動作は、前回の Codelab とほぼ同じです。これには 2 つの Activity の実装が含まれ、ユーザーはそれらの間でアクティビティを送信できるようになります。今回の演習でアプリに変更を加えても、表示されるユーザーの動作には影響しません。
3. 3. タスク 1: TwoActivities にライフサイクル コールバックを追加する
このタスクでは、Activity ライフサイクル コールバック メソッドをすべて実装し、これらのメソッドが呼び出されたときにメッセージを logcat に出力します。これらのログメッセージを見ると、アクティビティのライフサイクルの状態がいつ変化したか、その変化が実行中にアプリにどのように影響するかがわかります。
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] ペインを表示します。アクティビティが開始時に遷移した 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 つ目のアクティビティに追加します。(MainActivity からコールバック メソッドをコピーして貼り付けてください)。
- finish() メソッドの直前の returnReply() メソッドにログ ステートメントを追加します。
Log.d(LOG_TAG, "End SecondActivity");
1.4 アプリの実行中にログを確認する**
- アプリを実行します。
- Android Studio の下部にある [Logcat] タブをクリックして、[Logcat] ペインを表示します。
- 検索ボックスに「アクティビティ」と入力します。Android logcat は非常に長く、雑然としたものになる場合があります。各クラスの LOG_TAG 変数には MainActivity または SecondActivity という単語が含まれているため、このキーワードを使用すると、関心のあることだけを対象としてログをフィルタできます。
アプリを使用してテストし、さまざまなアクションに応じて発生するライフサイクル イベントに注意してください。特に、以下のことを試してみてください。
- アプリを通常どおりに使用します(メッセージを送信し、別のメッセージで返信します)。
- [戻る] ボタンを使用して、2 つ目のアクティビティからメインのアクティビティに戻ります。
- アプリバーの上矢印を使用して、2 つ目のアクティビティからメイン アクティビティに戻ります。
- アプリのメイン アクティビティと 2 つ目のアクティビティの両方で、デバイスを異なるタイミングで回転させ、* ログと画面で何が起こるかを観察します。
- 概要ボタン(ホームの右側にある四角いボタン)を押して、アプリを閉じます([X] をタップします)。
- ホーム画面に戻り、アプリを再起動します。
ヒント: エミュレータでアプリを実行している場合は、Ctrl+F11 キーまたは Ctrl+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. 4. タスク 2: Activity インスタンスの状態を保存して復元する
システム リソースやユーザーの行動によっては、想定よりもはるかに頻繁に、アプリの各 Activity が破棄および再構築される場合があります。
最後のセクションでデバイスまたはエミュレータを回転したとき、この動作に気づいたかもしれません。デバイスを回転させることは、デバイス構成変更の一例です。回転は最も一般的な方法ですが、すべての構成変更により現在のアクティビティが破棄され、新規の場合と同様に再作成されます。コード内でこの動作を考慮していない場合、構成の変更が発生したときに、アクティビティ レイアウトがデフォルトの外観と初期値に戻り、ユーザーがアプリでの場所、データ、または進行状況を失う可能性があります。
各アクティビティの状態は、Activity インスタンス状態と呼ばれる Bundle オブジェクト内に Key-Value ペアのセットとして保存されます。アクティビティが停止する直前にシステムは、デフォルトの状態情報をインスタンスの状態 Bundle に保存し、その Bundle を新しい Activity インスタンスに渡して復元します。
Activity が予期せず破棄されて再作成されたときにデータが失われないようにするには、onSaveInstanceState() メソッドを実装する必要があります。アクティビティが破棄されて再作成される可能性がある場合、システムはアクティビティ(onPause() と onStop() の間)でこのメソッドを呼び出します。
インスタンス状態で保存するデータは、現在のアプリ セッションの間、この特定のアクティビティのこのインスタンスだけに固有のものです。新しいアプリ セッションを停止して再起動すると、Activity インスタンスの状態は失われ、Activity はデフォルトの外観に戻ります。アプリ セッション間でユーザーデータを保存する必要がある場合は、共有設定またはデータベースを使用します。どちらについても後に学習します。
2.1 onSaveInstanceState() を使用してアクティビティ インスタンスの状態を保存する
デバイスを回転させても 2 つ目のアクティビティの状態にはまったく影響しないことに気付いたかもしれません。これは、2 つ目のアクティビティのレイアウトと状態が、レイアウトとそれをアクティブにしたインテントから生成されるためです。アクティビティが再作成されても、インテントは引き続き存在し、そのインテントのデータは、2 つ目のアクティビティの onCreate() メソッドが呼び出されるたびに使用されます。
また、各 Activity では、デバイスが回転しても、メッセージや返信の EditText 要素に入力したテキストは保持されます。これは、レイアウト内の一部のビュー要素の状態情報が、設定の変更後も自動的に保存されるためで、EditText の現在の値もそうしたケースの一つです。
したがって、注目すべき Activity 状態は、返信ヘッダーの TextView 要素とメイン アクティビティの返信テキストのみです。どちらの TextView 要素も、デフォルトでは非表示です。これらの要素が表示されるのは、2 番目の Activity からメイン Activity にメッセージを返信した場合のみです。
このタスクでは、onSaveInstanceState() を使用して、これら 2 つの TextView 要素のインスタンス状態を保持するコードを追加します。
- MainActivity を開きます。
- onSaveInstanceState() のこのスケルトン実装をアクティビティに追加するか、[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 からの返信が届くまで非表示になります。ヘッダーが表示されている場合は、保存する必要のある応答データがあります。この公開状態だけに注目してください。ヘッダーの実際のテキストは変更されないため、保存する必要はありません。
- 同じチェック内で、返信テキストをバンドルに追加します。
outState.putString("reply_text",mReplyTextView.getText().toString());
ヘッダーが表示されている場合、返信メッセージ自体も表示されていると考えられます。返信メッセージの現在の表示状態をテストや保存する必要はありません。メッセージの実際のテキストのみが、キー「reply_text」を持つ Bundle 状態になります。
アクティビティの作成後に変更される可能性のある View 要素の状態のみを保存します。アプリの他の View 要素(EditText、Button)は、デフォルトのレイアウトからいつでも再作成できます。
なお、EditText の内容など、一部のビュー要素の状態はシステムによって保存されます。
2.2 onCreate() で Activity インスタンスの状態を復元する
アクティビティ インスタンスの状態を保存した後、アクティビティが再作成されたときにも状態を復元する必要があります。これを行うには、onCreate() で行うか、アクティビティの作成後に onStart() の後に呼び出される onRestoreInstanceState() コールバックを実装します。
ほとんどの場合、Activity の状態を復元するのに適した場所は onCreate() です。これにより、状態を含む UI が可能な限り早く利用できるようになります。すべての初期化が完了した後に onRestoreInstanceState() でこれを行うか、デフォルトの実装を使用するかどうかをサブクラスが決定できるようにすると便利な場合があります。
- onCreate() メソッドで、ビュー変数を 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) {
}
アクティビティが作成されると、システムは Bundle の状態を唯一の引数として onCreate() に渡します。初めて onCreate() が呼び出されてアプリが起動したとき、Bundle は null です。アプリの初回起動時には既存の状態はありません。後続の onCreate() の呼び出しでは、onSaveInstanceState() に格納されたデータがバンドルに入力されます。
- このチェック内で、「reply_visible」キーを使用して、バンドルから現在の公開設定(true または false)を取得します。
if (savedInstanceState != null) {
boolean isVisible =
savedInstanceState.getBoolean("reply_visible");
}
- 前の行の下に isVisible 変数のテストを追加します。
if (isVisible) {
}
Bundle の状態に feedback_visible キーがある場合(したがって isvisible が true の場合)、状態を復元する必要があります。
- isvisible テスト内で、ヘッダーが表示されるようにします。
mReplyHeadTextView.setVisibility(View.VISIBLE);
- 「reply_text」キーを使用して Bundle からテキスト返信メッセージを取得し、その文字列を表示するように返信 TextView を設定します。
mReplyTextView.setText(savedInstanceState.getString("reply_text"));
- 返信の TextView も表示されるようにします。
mReplyTextView.setVisibility(View.VISIBLE);
- アプリを実行します。デバイスまたはエミュレータを回転させて、アクティビティが再作成された後も返信メッセージ(存在する場合)が画面に残ることを確認します。
タスク 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 つ目のアクティビティが開始されます。ボタン要素を使用してアイテムを表示します。
- アイテムを選択すると、ユーザーはメイン アクティビティに戻り、空の TextView が更新され、選択したアイテムが含まれます。
インテントを使用して、あるアクティビティから別のアクティビティに情報を渡します。ユーザーがデバイスを回転させたときに、ショッピング リストの現在の状態が保存されているようにします。
6. まとめ
- アクティビティのライフサイクルは、アクティビティが最初に作成された時点から始まり、Android システムがそのアクティビティのリソースを再利用したときに終了するまでの、遷移を遷移する一連の状態です。
- ユーザーが、あるアクティビティ間やアプリの内外を移動すると、各アクティビティはアクティビティのライフサイクルの状態間を移行します。
- アクティビティのライフサイクルの各状態には、Activity クラスでオーバーライドできる、対応するコールバック メソッドがあります。
- ライフサイクル メソッドは、onCreate()、onStart()、onPause()、onRestart()、onResume()、onStop()、onDestroy() です。
- ライフサイクル コールバック メソッドをオーバーライドすると、アクティビティがその状態に遷移したときに発生する動作を追加できます。
- Android Studio の [Code] > [Override] で、スケルトン オーバーライド メソッドをクラスに追加できます。
- 回転などのデバイス構成が変更されると、アクティビティが新規であるかのように破棄されて再作成されます。
- 構成変更時に、EditText 要素の現在の値など、Activity の状態の一部が保持されます。他のすべてのデータについては、自分で明示的に保存する必要があります。
- onSaveInstanceState() メソッドでアクティビティ インスタンスの状態を保存します。
- インスタンスの状態データは、シンプルな Key-Value ペアとして Bundle に保存されます。Bundle メソッドを使用して、データを Bundle に格納し、バンドルからデータを取得します。
- onCreate()(推奨)または onRestoreInstanceState() でインスタンスの状態を復元します。