Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

2.02. Activity と Fragment

KeithYokoma edited this page Apr 23, 2013 · 23 revisions

この章では、画面のライフサイクルの管理について解説します。

参考:Activities | Android Developers

参考:Tasks and Back Stack | Android Developers

目次

Android の画面の構成要素

Android の画面を作る上で重要なコンポーネントとして、下記のようなものがあります。

Activity
MVC の Controller に相当するものです。 画面のライフサイクルや、UI イベントの管理は Activity が受け持っています。
Fragment

再利用可能な UI コンポーネントのまとまりを管理します。Activity に組み込んで使用します。
こちらも、MVC の Controller に相当する責務を持っています。
様々なデバイスをサポートする上でも重要なコンポーネントとなります。

Fragment も Activity と同様ライフサイクルを持っていますが、Activity に組み込んで使う為、Activity のライフサイクルと連動するようになっています。

Layout・Widget
2.01. アプリのレイアウト作成で解説しました。

ライフサイクルの管理

Activity

Activity にはライフサイクルが存在します。 主には、画面が呼び出されてから、必要なくなってメモリから追い出される(破棄される)までの一連の流れをライフサイクルとして扱います。 以下に上げるライフサイクルの各状態は、ライフサイクルコールバックとして、Activity クラスに同じ名前で定義されています。

Activity Lifecycle

onCreate

Activity が一番最初にとる状態です。

Activity が起動し、画面を構成するまでの仕事をここで行います。
XML で定義したレイアウトを読み込んだり、View コンポーネントを取り出したりする処理を実行します。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // レイアウトを指定して、Activity がコントロールする View として扱うようにする
        setContentView(R.layout.activity_main);
    }
}
onStart(onRestart)
Activity の画面が構築され、ユーザに見える状態です。実際にユーザが UI に触れてインタラクションを実施出来るようになるまでの仕事をここで行います。
onResume
Activity の画面が構築され、ユーザが UI に触れてインタラクションを実施できる状態です。
onPause

ユーザが Activity を離れようとしている状態です。永続化するべき情報はこのタイミングで永続化を実施します。

この状態になった Activity へユーザが戻ると、onResume の状態へと遷移します。 よって、この状態になっても、必ずこの後の状態へ順に遷移して、Activity がメモリから破棄されるとは限りません。

onStop

Activity がユーザから見えなくなった状態です。

この状態になった Activity にユーザが戻ろうとした場合、onRestart の状態から onStart の状態へと遷移します。

onDestroy

Activity がシステムによって、メモリから追い出される直前の状態です。

この時点で、Activity に対するすべての参照を切っておかないと、メモリリークを起こします。
一方、システムの要求によって、アプリのプロセスごと kill された場合には、この状態になることなくアプリケーションが終了します。

Fragment

Fragment にもライフサイクルが存在します。

基本的には、Activity のライフサイクルと同じ状態を持っていますが、いくつか Fragment 特有の状態も持っています。
以下に上げるライフサイクルの各状態は、ライフサイクルコールバックとして、Fragment クラスに同じ名前で定義されています。

Fragment Lifecycle

Fragment は、Honeycomb で導入された新しいコンポーネントです。

Eclipse では、android.app.Fragment と android.support.v4.app.Fragment の 2 つがコード補完によって表示されますが、2.x 系で Fragment を使用する場合は、後者のパッケージのものを利用します。

また同時に、2.x 系で Fragment を組み込んだ Activity を作成する場合は、android.app.Activity ではなく、android.support.v4.app.FragmentActivity を継承するようにします。

onAttach

Fragment が Activity に組み込まれた状態です。
この時点で、Fragment が Activity に対して何らかのコールバックを提供する場合、Activity が必要なインタフェースを備えているかどうかチェックしておきます。

public class MainFragment extends Fragment {
    private FragmentCallbacks mCallback;
	
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            mCallback = (FragmentCallbacks) activity;
        } catch (ClassCastException e) {
            // Fragment が組み込まれる先の Activity に対して、FragmentCallbacks インタフェースの実装を要求する為
            // キャストに失敗した場合は、実行時例外としてプログラムのミスであることを示す 
            throw new IllegalStateException("activity should implement FragmentCallbacks", e);
        }
    }

    public static interface FragmentCallbacks {
        public void onHogehoge();
    }
}
onCreate

Fragment を構築する状態です。

Fragment がユーザに見える状態になるまで保持しておくべきコンポーネントの初期化を行います。

onCreateView

Fragment が持つ View を構築する状態です。

XML からレイアウトを取り出し、この Fragment の View として扱うための処理を行いますが、View を持たない Fragment を作ることも可能です。

public class MainFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // LayoutInflater を利用して、レイアウトをリソースとして読み込む
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        return view;
    }
}
public class MainFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // View を持たない Fragment は、ここで null を返す
        return null;
    }
}
onActivityCreated
Activity の onCreate の状態の処理が終わったことを示す状態です。
onStart
Fragment の UI が構築され、ユーザに見える状態です。
onResume
Fragment の UI が構築され、ユーザとのインタラクションが出来るようになった状態です。
onPause

ユーザが別の画面への遷移をしようとして Fragment から離れていこうとした状態です。

Activity と同じく、この時点で、永続化するべき情報を保存するようにしておきます。
ただし、この状態になったからといって、必ずしもこの後の状態へ遷移し、Fragment がメモリから破棄されるわけではありません。

onStop
Fragment がユーザに見えない状態です。
onDestroyView

Fragment が扱う View などのコンポーネントに紐付いた各種リソースを開放するための状態です。

ここで Fragment への参照が View やコンポーネントに残っていると、メモリリークを起こします。
ユーザのナビゲーションや Fragment の操作等で、再びレイアウトにアタッチされる場合、onCreateView の状態へ戻ります。

onDestroy
Fragment が完全にメモリから破棄される直前の状態です。
onDetach
Fragment が Activity から切り離される状態です。

AndroidManifest での Activity の宣言

AndroidManifest は XML で記述されています。 Activity は、アプリケーションを構成するコンポーネントですので、 <application>要素の下にぶら下げる形で宣言します。

<activity>要素

<activity>要素には、どの Activity かを示したり、Activity のラベルを示したりする属性を付与できます。 以下に、代表的な属性を示します。

<activity
  android:name="string"
  android:label="string"
  android:exported=["true" | "false"]
  android:launchMode=["multiple" | "singleTop" | "singleTask" | "singleInstance"]
  android:configChanges=["mcc", "mnc", "locale", "touchscreen", "keyboard", "keyboardHidden", "navigation", "screenLayout", "fontScale", "uiMode", "orientation", "screenSize", "smallestScreenSize"]  android:theme="resource or theme"
  android:uiOptions=["none" | "splitActionBarWhenNarrow"]
  android:windowSoftInputMode=["stateUnspecified", "stateUnchanged", "stateHidden", "stateAlwaysHidden", "stateVisible", "stateAlwaysVisible", "adjustUnspecified", "adjustResize", "adjustPan"]>
</activity>
要素 意味
android:name Activity のクラス名。FQDN か、<application>要素で宣言したパッケージ名から後ろのパス表現で記述する
android:label Activity のラベル。ユーザに見えるものなので、分かりやすいものにしておく
android:exported 他のアプリからも呼び出すことができるようにするかどうか。デフォルトではfalseになるが、例外的に、<intent-filter>要素を子要素に持つ<activity>は、この属性がデフォルトでtrueになることに注意。特に理由がない限りは、falseを明示すること
android:configChanges Activity が自身で制御するコンフィギュレーション変化の一覧。複数指定が可能で、パイプで繋ぐ
android:theme Activity に適用するテーマ
android:uiOptions Activity に適用する、UI にまつわる設定。現状は ActionBar の設定のみが存在している
android:windowSoftInputMode ソフトウェアキーボードと Activity の表示に関する調整要素。ソフトウェアキーボードを自動で表示するかどうかや、Activity の拡大・縮小についての振る舞いを決める

ランチャーから起動する Activity の宣言

アプリを起動する時、ランチャーは、当該アプリに対して、暗黙的 Intent を発行します。 この暗黙的 Intent を受け付けることの出来る Activity の宣言の例を以下に示します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.mixi.sample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <!-- Activity に関する宣言 -->
        <!-- 新しい Activity を作ったら、必ずこの宣言を追加する -->
        <!-- android:name には、FQDN か、jp.mixi.sampleの続きからのパスを入れる -->
        <activity
            android:name="jp.mixi.sample.MainActivity"
            android:label="@string/app_name" >
            <!-- ランチャーから呼び出す対象とするための宣言 -->
            <!-- ランチャーから呼び出さない Activity であれば、この宣言は不要 -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

<activity>要素の子要素として、<intent-filter>要素を宣言します。 この<intent-filter>要素に更に子要素として、Intent に付与される Action と Category を指定します。

ランチャーからの呼び出しでは、android.intent.action.MAIN というアクションと、android.intent.category.LAUNCHER というカテゴリが付与されますので、これを<intent-filter>要素の子要素に宣言しておきます。

タスクとバックスタック

Android には、タスクとバックスタックという考え方があります。 この考え方が取り扱うのは、複数の Activity の管理です。 同一アプリ内で扱う複数の Activity だけでなく、他のアプリとの連携も、タスクとバックスタックの扱う範囲です。

タスク

タスクとは、ユーザがアプリを利用して何らかの操作を行う単位です。このことから、Linux などで用いられるタスクとは異なる意味を持っています。

1つのタスクには、起動した Activity のまとまりをスタックで持つバックスタックが割当てられます。

バックスタック

バックスタックとは、Activity の呼び出し順を管理するスタックです。 アプリを起動すると、最初にランチャーから呼び出される Activity がバックスタックに積まれます(push)。

その Activity から新しい別の Activity を呼び出すと、その Activity がバックスタックに積まれます(push)。 新しく起動した Activity で、端末のバックキー(戻るボタン)を押すなどして前の Activity に戻ると、バックスタックに積まれていた Activity は破棄されます(pop)。

すべての Activity がバックスタックから居なくなると、アプリケーションは終了することとなります。

Back Stack Visualization

マルチタスク

あるタスクのバックスタックに Activity が積まれている状態で、ユーザがホームキーで端末のホーム画面に戻った場合、タスクはバックグラウンドで待機状態となります。

この時、別のアプリを起動すると、新たな別のタスクが定義され、新しいタスクのバックスタックに起動したアプリの Activity が積まれます。 つまり、新しいタスクはフォアグラウンドで実行中の状態となります。

ここで、ユーザが再びホームキーでホーム画面に戻り、最初に利用していたアプリを起動してバックグラウンドに居たタスクをフォアグラウンドへ遷移させると、もう一方のフォアグラウンドに居たタスクがバックグラウンドへ移ります。

Multi-Task Visualization

これが Android のマルチタスクです。

マルチタスクで多数のタスクをバックグラウンドへ移すと、その分だけメモリを消費します。メモリ残量が少なくなると、システムは、バックグラウンドに居るタスクのなかから、優先順位を見てタスクの持つ Activity の破棄を試みます。

Activity が破棄されても、破棄される前の状態に復元できるよう、Activity のライフサイクルにしたがって実装をしておくべきです。

状態の保存と復旧

Activity には、以下の二つのコールバックメソッドが用意されており、これらを活用して、状態の保存と復旧を行います。

onSaveInstanceState
Activity が破棄される前に、状態を保存するために呼ばれるコールバックメソッドです。

引数に Bundle オブジェクト(Android 独自のコレクション・フレームワーク)が渡されるので、このオブジェクトに状態を保存します。

onRestoreInstanceState
Activity が再度フォアグラウンドに遷移する際、状態を復旧するために呼ばれるコールバックメソッドです。

引数に Bundle オブジェクトが渡されます。この Bundle オブジェクトには、onSaveInstanceState() メソッドで保存した状態が入っていますので、その状態を取り出して復旧します。

Activity の onCreate() メソッドの引数にある Bundle オブジェクトも同じ役割を持っています。

同様、Fragment にも onSaveInstanceState() メソッドが定義されており、ここで状態の保存をすることができます。

一方、状態の復旧には、onCreate() や onCreateView()、onActivityCreated() メソッドの引数にある Bundle オブジェクトを利用します。

Launch Mode

Activity のインスタンスをどのようにスタック上で管理するかを決めるモードです。
このモードの宣言は、AndroidManifest の <activity>要素で行います。

<activity
    ...
    android:launchMode="singleTop"/>
モード名 意味
standard デフォルトのモード。Activity の呼び出しごとに毎回インスタンスを生成する為、複数の同じActivityのインスタンスがスタック上に現れる。
singleTop Activity を呼び出した時、スタックの一番上にその Activity のインスタンスがあるときは、Activity#onNewIntent()を呼び出して、そこに新しい Intent を送りつける
singleTask 新しいタスクのルートとして Activity のインスタンスを生成するが、既にタスクのスタックにインスタンスがある場合は、Activity#onNewIntent()を呼び出して、新しいタスクのルートに据える
singleInstance タスクの中で、Activity のインスタンスは常に 1 つで、かつそのインスタンスが属するタスクの中では、他の Activity を起動させない

殆どの場合、standard モードないしは singleTop モードを利用することになります。
公式のリファレンスにも書かれている通り、ほとんどのアプリケーションにおいて、singleTaks や singleInstance は不適切なモードです。

実習・課題

Activity

  1. (実習) 新しい Activity と、それに対応するレイアウトを作成してください。
  2. (実習) 新しく作った Activity を、アプリ起動時に表示するよう変更してください。
  3. (実習) 新しい Fragment と、それに対応するレイアウトを作成してください。
  4. (実習) 新しく作った Fragment を、既存の Activity に組み込んでください。
  5. (課題) Toast#makeText(Context, int, int)メソッドを使って、Activity のライフサイクルのコールバックメソッドが、どのような順番で実行されるかを画面に表示し、列挙してください。
  6. (課題) Activity が 2 つ用意されています。起動後の Activity から呼び出された Activity で、状態管理を適切に行なってください。
  7. (課題) ControllerLifecycleAssignment3activity_main.xmlから、Fragment に切り出すものを適宜選択し、Fragment 化してください。
  8. (課題) 課題 7 で切り出した Fragment を用いて、新しい画面を作成してください(課題 7 と同じプロジェクトの中で実施する)。
  9. (課題) Activity で発生しているメモリリークを特定し、解消してください。

GitHub Pagesへ移行しましたmixi-inc.github.ioへお願いします。

Clone this wiki locally