アクティビティのライフサイクル

アクティビティのライフサイクルを理解することは非常に重要。理解すれば、よりスムーズなアプリを書けるようになります。

また、アプリのリソースを適切に管理することにも役立ちます。

より良いユーザー体験を提供するためにも欠かせません。

Android のアクティビティは重ねて表示することができます。

新しいアクティビティを起動するたびに、元のアクティビティの上に重なります。

そしてバックキーを押すと、現在のアクティビティが破棄され、一つ前のアクティビティが再び表示されます。

実は Android は「タスク(Task)」を使って管理しています。

タスクとは、スタックに格納されたアクティビティの集合体のことです。

このスタックは「バックスタック(Back Stack)」と呼ばれます。

スタックは後入れ先出し(LIFO)のデータ構造です。

デフォルトでは、新しいアクティビティを起動すると、それはスタックにプッシュされ、最上位に置かれます。

バックキーを押すか finish() を実行すると、スタックの最上位にあるアクティビティが破棄されます。そして一つ前にスタックに入っていたアクティビティが再び最上位に戻ります。システムは常にスタックの最上位にあるアクティビティをユーザーに表示します。

アクティビティの状態

アクティビティはライフサイクルの中で、最大で4つの状態のいずれかになります。

1. 実行中状態(Running)

スタックの最上位にある状態です。システムはユーザー体験を損なうため、これを回収したがりません。

2. 一時停止状態(Paused)

スタックの最上位にはありませんが、まだ表示されています。例えば現在のアクティビティの上にダイアログアクティビティが表示され、画面の一部だけを占める場合(モーダルウィンドウのようなもの)です。システムはこれも回収したがりません。突然消えるとユーザーに違和感を与えるからです。

3. 停止状態(Stopped)

アクティビティが最上位になく、かつ完全に見えなくなった場合に停止状態になります。システムは状態やメンバ変数を保持しますが、これは確実ではありません。他でメモリが必要になれば、停止状態のアクティビティも回収対象になります。

4. 破棄状態(Destroyed)

アクティビティがバックスタックから取り除かれると破棄状態になります。システムはメモリを確保するため、これらを最優先で回収します。

アクティビティの生存期間と7つのコールバックメソッド

Activity クラスには、ライフサイクルのあらゆる段階をカバーする7つのコールバックメソッドが定義されています。

つまり、これらの7つのコールバック関数はライフサイクルの特定のタイミングで呼び出され、その中のコードが実行されます。

例えば、アクティビティが最初に作成されるときに onCreate がトリガーされます。

以下、それぞれのメソッドを紹介します。

onCreate:

アクティビティが最初に作成されるときに呼び出されます。レイアウトの読み込みやボタンへのイベント紐付けなど、初期化処理を行います。

onStart:

アクティビティが不可視から可視になったときにトリガーされます。

onResume:

アクティビティがユーザーと対話する準備ができたときに呼び出されます。この時点でアクティビティはバックスタックの最上位にあり、実行中状態です(ユーザーに表示されています)。

onPause:

システムが別のアクティビティを起動または再開しようとするときに呼び出されます。CPU を消費するリソースの解放や重要なデータの保存を行うべきですが、処理は高速に終わらせる必要があります。さもないと新しいアクティビティの表示が遅れます。(現在のアクティビティが隠れようとしており、次のアクティビティに移行する直前の状態です)

onStop:

アクティビティが完全に見えなくなったときに呼び出されます。onPause との主な違いは、新しく起動したアクティビティがダイアログ形式の場合、onPause は実行されますが onStop は実行されない点です。(アクティビティが隠れ、新しいアクティビティが表示されます)

onDestroy:

アクティビティが破棄される直前に呼び出されます。その後、アクティビティは破棄状態になります。

onRestart:

停止状態のアクティビティが実行中状態に戻る直前に呼び出されます。つまり、アクティビティが再起動されるときです。

3種類の生存期間

上記の7つのメソッドのうち、onRestart を除く6つは2つずつ対になっています。そのためアクティビティの生存期間は3つに分類できます。

1. 完全生存期間

onCreate から onDestroy までの間を完全生存期間と呼びます。

2. 可視生存期間

onStart から onStop までの期間を可視生存期間と呼びます。この間、アクティビティは常にユーザーに見えています。これらのメソッドを使ってリソースを適切に管理できます。例えば onStart でリソースを読み込み、onStop で解放することで、停止状態のアクティビティがリソースを消費し続けるのを防げます。

3. フォアグラウンド生存期間

onResume から onPause までの期間をフォアグラウンド生存期間と呼びます。アクティビティは実行中状態にあり、ユーザーと対話できます。私たちが普段最も目にし、触れることになるのはこの状態のアクティビティです。

メインアクティビティを開く → 通常アクティビティに進む → メインアクティビティに戻る → ホーム画面に戻る

05-12 15:32:43.899 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onCreate:
05-12 15:32:43.926 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onStart:
05-12 15:32:43.930 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onResume:
05-12 15:33:00.973 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onPause:
05-12 15:33:01.078 4316-4316/com.foxdevelop.www.lifedemo E/通常アクティビティログ: onCreate:
05-12 15:33:01.079 4316-4316/com.foxdevelop.www.lifedemo E/通常アクティビティログ: onStart:
05-12 15:33:01.081 4316-4316/com.foxdevelop.www.lifedemo E/通常アクティビティログ: onResume:
05-12 15:33:02.969 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onStop:
05-12 15:34:01.747 4316-4316/com.foxdevelop.www.lifedemo E/通常アクティビティログ: onPause:
05-12 15:34:01.779 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onRestart:
05-12 15:34:01.779 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onStart:
05-12 15:34:01.780 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onResume:
05-12 15:34:02.749 4316-4316/com.foxdevelop.www.lifedemo E/通常アクティビティログ: onStop:
05-12 15:34:02.750 4316-4316/com.foxdevelop.www.lifedemo E/通常アクティビティログ: onDestroy:
05-12 15:35:26.827 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onPause:
05-12 15:35:28.534 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onStop:
05-12 15:34:01.779 4316-4316/com.foxdevelop.www.lifedemo E/メインアクティビティログ: onDestroy:

実践プロジェクト

新しいプロジェクトを作成してライフサイクルを体験してみましょう

アクティビティ名は LifeDemo とします。

EmptyActivity をメインアクティビティとして追加します。

さらに NormalActivity と DialogActivity の2つの通常アクティビティを追加します。

その後、それぞれのレイアウトを修正します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="通常アクティビティ" />

</LinearLayout>Code language: HTML, XML (xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ダイアログアクティビティ" />

</LinearLayout>Code language: HTML, XML (xml)

レイアウトは表示内容が違うだけで、基本的に同じです。

DialogActivity をダイアログ形式のアクティビティにする必要があります。

AndroidManifest.xml を編集します:

<activity android:name=".NormalActivity" />
<activity android:name=".DialogActivity" android:theme="@style/Base.Theme.AppCompat.Dialog"/>
</application>Code language: HTML, XML (xml)

これで DialogActivity がダイアログテーマになります。

メインアクティビティのレイアウト

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="通常アクティビティを開く" />
    <Button
        android:id="@+id/buttondialog"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ダイアログアクティビティを開く" />
</LinearLayout>Code language: HTML, XML (xml)

メインアクティビティの2つのボタンにクリックイベントを追加

package com.foxdevelop.www.lifedemo;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button b = (Button)findViewById(R.id.button);
        b.setOnClickListener(
                new View.OnClickListener(){
                    @Override
                    public void onClick(View view){
                        Intent it = new Intent(MainActivity.this,NormalActivity.class);
                        startActivity(it);
                    }
                }
        );
        Button b2 = (Button)findViewById(R.id.buttondialog);
        b2.setOnClickListener(
                new View.OnClickListener(){
                    @Override
                    public void onClick(View view){
                        Intent it = new Intent(MainActivity.this,DialogActivity.class);
                        startActivity(it);
                    }
                }
        );
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e(TAG, "onStart: ");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG, "onResume: ");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG, "onPause: ");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e(TAG, "onStop: ");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: ");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.e(TAG, "onRestart: ");
    }
}Code language: JavaScript (javascript)

1. アプリを実行

メインアクティビティが作成されると、onCreate、onStart、onResume が順番に実行されます。

「通常アクティビティを開く」ボタンをタップすると、onPause と onStop がトリガーされます。

NormalActivity が MainActivity を完全に覆い隠すため、両方のメソッドが呼び出されます。

バックキーを押して戻ります。

先ほどの MainActivity は停止状態にあったため、onRestart がトリガーされ、その後 onStart と onResume が順に実行されます。

ただし onCreate は実行されません。再作成されていないからです。このとき NormalActivity 側では onDestroy がトリガーされているはずです。

次に「ダイアログを開く」ボタンをタップします。

このとき、onPause だけがトリガーされます。

onStop はトリガーされていないことがわかります。

それは DialogActivity が MainActivity を完全に隠していないためです。

同様に、この状態でバックキーを押すと onResume だけが実行されます。停止していないため onStart は呼び出されません。

このように、onPause、onStop、onDestroy がトリガーされるのが確認できます。

Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.Code language: CSS (css)

メインアクティビティ作成 → ダイアログアクティビティを開く → メインアクティビティに戻る → ホーム画面に戻る

05-13 02:10:51.635 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onCreate: // 新しいアクティビティ作成
05-13 02:10:51.673 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onStart: // 不可視から可視に
05-13 02:10:51.701 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onResume: // アクティビティが表示された
05-13 02:12:01.993 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onPause: // 現在のアクティビティから他へ移動
05-13 02:12:02.115 4206-4206/com.foxdevelop.www.lifedemo E/ダイアログアクティビティ: onCreate:
05-13 02:12:02.116 4206-4206/com.foxdevelop.www.lifedemo E/ダイアログアクティビティ: onStart:
05-13 02:12:02.143 4206-4206/com.foxdevelop.www.lifedemo E/ダイアログアクティビティ: onResume:
05-13 02:14:26.078 4206-4206/com.foxdevelop.www.lifedemo E/ダイアログアクティビティ: onPause: // バックキーでメインアクティビティに戻る
05-13 02:14:26.367 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onResume: // 停止していなかったので直接表示される
05-13 02:14:26.803 4206-4206/com.foxdevelop.www.lifedemo E/ダイアログアクティビティ: onStop: // ダイアログアクティビティが見えなくなった
05-13 02:14:26.803 4206-4206/com.foxdevelop.www.lifedemo E/ダイアログアクティビティ: onDestroy: // バックまたはfinish()で破棄される
05-13 02:16:23.959 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onPause:
05-13 02:16:25.606 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onStop:
05-13 02:16:25.609 4206-4206/com.foxdevelop.www.lifedemo E/メインアクティビティ: onDestroy: // 破棄前には必ずpauseとstopが発生するCode language: JavaScript (javascript)

アクティビティのライフサイクル

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です