-
Notifications
You must be signed in to change notification settings - Fork 354
2.02. Activity と Fragment
GitHub Pagesへ移行しましたmixi-inc.github.ioへお願いします。
wikiの方はしばらくしたら消していきます
この章では、画面のライフサイクルの管理について解説します。
参考:Activities | Android Developers
参考:Tasks and Back Stack | Android Developers
- Android の画面の構成要素
- ライフサイクルの管理
- AndroidManifest での Activity の宣言
- [Activity とレイアウトの管理](#Activity とレイアウトの管理)
- [レイアウトからの View の取得](#レイアウトからの View の取得)
- [Fragment の組み込み](#Fragment の組み込み)
- レイアウトに記述する
- [FragmentManager と FragmentTransaction で管理する](#FragmentManager と FragmentTransaction で管理する)
- [Fragment の作成の注意点](#Fragment の作成の注意点)
- タスクとバックスタック
Android の画面を作る上で重要なコンポーネントとして、下記のようなものがあります。
- Activity
- MVC の Controller に相当するものです。 画面のライフサイクルや、UI イベントの管理は Activity が受け持っています。
- Fragment
-
再利用可能な UI コンポーネントのまとまりを管理します。Activity に組み込んで使用します。
こちらも、MVC の Controller に相当する責務を持っています。
様々なデバイスをサポートする上でも重要なコンポーネントとなります。Fragment も Activity と同様ライフサイクルを持っていますが、Activity に組み込んで使う為、Activity のライフサイクルと連動するようになっています。
- Layout・Widget
- 2.01. アプリのレイアウト作成で解説しました。
Activity にはライフサイクルが存在します。 主には、画面が呼び出されてから、必要なくなってメモリから追い出される(破棄される)までの一連の流れをライフサイクルとして扱います。 以下に上げるライフサイクルの各状態は、ライフサイクルコールバックとして、Activity クラスに同じ名前で定義されています。
- 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 にもライフサイクルが存在します。
基本的には、Activity のライフサイクルと同じ状態を持っていますが、いくつか Fragment 特有の状態も持っています。
以下に上げるライフサイクルの各状態は、ライフサイクルコールバックとして、Fragment クラスに同じ名前で定義されています。
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 は XML で記述されています。
Activity は、アプリケーションを構成するコンポーネントですので、 <application>
要素の下にぶら下げる形で宣言します。
<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 の拡大・縮小についての振る舞いを決める |
アプリを起動する時、ランチャーは、当該アプリに対して、暗黙的 Intent を発行します(Intent は、Android の各種コンポーネントを結びつけるためのメッセージオブジェクトです。新しい 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>
要素の子要素に宣言しておきます。
Activity#onCreate(Bundle)
の中で、Activity#setContentView(int)
を呼び出します。
呼び出すメソッドの引数に渡す int 型整数は、レイアウトファイルの id です。
レイアウトファイルの id は、R クラスで管理されており、レイアウトファイルを作成すると自動で生成されます。
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// activity_main.xml をこの Activity のレイアウトとして管理する場合
setContentView(R.layout.activity_main);
}
}
View にandroid:id
が割当てられていれば、この id を元に、View の取得をすることが出来ます。
id を元に View を取得するメソッドに、Activity#findViewById(int)
があります。
<TextView
android:id="@+id/MyTextView"
android:layout_width="wrap_content/>
android:layout_height="wrap_content"/>
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// findViewById の引数に、View に割り当てた id を指定する
// View オブジェクトが返ってくるので、適宜キャストする
TextView view = (TextView) findViewById(R.id.MyTextView);
}
}
2 種類の組み込み方があります。
Activity で使うレイアウトの XML の中で、<fragment>
タグを利用して記述する方法です。
もっとも単純な Fragment の使い方です。
レイアウトで使用する各種属性の他に、android:id
属性と、android:name
属性を必ず指定します。
android:name
属性は、Fragment のクラス名を FQDN で記述します。
<!-- Activity 用のレイアウトでの Fragment の宣言。andorid:id によって、状態管理を行なっている -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- 何かしらの他の Viewたち... -->
<!-- fragment の宣言。android:name に指定する Fragmentの名前は FQDN で書く。 -->
<fragment
android:id="@+id/MyFragment"
android:name="jp.mixi.sample.fragment.MyListFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
レイアウトに定義された Fragment を Activity で取り出すには、FragmentManager を利用します。
public class MyActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// FragmentManager も、android.support.v4.app.FragmentManager を利用する
FragmentManager manager = getSupportFragmentManager();
// レイアウトから Fragment を見つけ出してインスタンスを得る
Fragment fragment = manager.findFragmentById(R.id.MyFragment);
}
}
画面(Activity)の中で、動的に Fragment を切り替える場合を考えると、レイアウトに書いてしまうと実現が難しくなります。
そこで、Activity の中で動的に Fragment を切り替える仕組みも用意されています。
動的に Fragment を切り替える場合は、Fragment を保持するためだけの Layout を用意しておく必要があります。
<!-- Activity 用のレイアウトでの Fragment の宣言。andorid:id によって、状態管理を行なっている -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- 何かしらの他の Viewたち... -->
<!-- Fragment を動的に入れ替えるための入れ物用のレイアウト -->
<FrameLayout
android:id="@+id/FragmentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
FragmentManager で、FragmentTransaction を開始し、この Transaction の中で、動的に Fragment の切り替えを行います。
public class MyActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager manager = getSupportFragmentManager();
// FragmentTransaction を開始
FragmentTransaction transaction = manager.beginTransaction();
// FragmentContainer のレイアウトに、MyFragment を割当てる
transaction.add(R.id.FragmentContainer, MyFragment.createInstance());
// FragmentContainer のレイアウトの中身を、MyFragment に置き換える
transaction.replace(R.id.FragmentContainer, MyFragment.createInstance());
// Fragment を削除する
transaction.remove(mFragment);
// 変更を確定して FragmentTransaction を終える
transaction.commit();
}
}
Fragment のクラスを作成するときには、以下の点に注意してください。
- 空のコンストラクタを定義する
-
何もしなくても、空のコンストラクタが必要です。これは、Android のフレームワークが、リフレクションによって、Fragment のデフォルトコンストラクタを呼び出すためです。
public class MyFragment extends Fragment { public MyFragment() {} }
- Fragment のインスタンス化にはコンストラクタを使わない
-
Fragment を動的に管理する際に必要なノウハウです。
Fragment のインスタンス化の際、Fragment に対して、引数を何かしら渡したい場合がありますが、このためにコンストラクタや setter メソッドを使ってはいけません。
これは、Android のフレームワークが、Fragment の状態の復旧時に、デフォルトコンストラクタを呼び出すためです。つまり、setter メソッドを定義しても、フレームワークは全くその存在を気にすることが出来ない為、setter メソッドも意味を成さなくなります。代わりに、Fragment の初期化のために、専用の static メソッドを用意し、引数を渡す場合は、`Fragment#setArguments(Bundle)`を利用して、Bundle に引数として渡したいデータを詰め込みます。
public class MyFragment extends Fragment { public MyFragment() {}
// 初期化専用のメソッド。Fragment に対する初期化用の引数はここで管理する public static Fragment createInstance(int hoge) { Fragment fragment = new MyFragment(); // Fragment に渡す引数を詰めこむオブジェクト Bundle args = new Bundle(); args.putInt("hoge", hoge); // 詰め込んだオブジェクトを Fragment に渡す fragment.setArguments(args); // 新しいインスタンスを返す return fragment; }
}
Android には、タスクとバックスタックという考え方があります。 この考え方が取り扱うのは、複数の Activity の管理です。 同一アプリ内で扱う複数の Activity だけでなく、他のアプリとの連携も、タスクとバックスタックの扱う範囲です。
タスクとは、ユーザがアプリを利用して何らかの操作を行う単位です。このことから、Linux などで用いられるタスクとは異なる意味を持っています。
1つのタスクには、起動した Activity のまとまりをスタックで持つバックスタックが割当てられます。
バックスタックとは、Activity の呼び出し順を管理するスタックです。 アプリを起動すると、最初にランチャーから呼び出される Activity がバックスタックに積まれます(push)。
その Activity から新しい別の Activity を呼び出すと、その Activity がバックスタックに積まれます(push)。 新しく起動した Activity で、端末のバックキー(戻るボタン)を押すなどして前の Activity に戻ると、バックスタックに積まれていた Activity は破棄されます(pop)。
すべての Activity がバックスタックから居なくなると、アプリケーションは終了することとなります。
あるタスクのバックスタックに Activity が積まれている状態で、ユーザがホームキーで端末のホーム画面に戻った場合、タスクはバックグラウンドで待機状態となります。
この時、別のアプリを起動すると、新たな別のタスクが定義され、新しいタスクのバックスタックに起動したアプリの Activity が積まれます。 つまり、新しいタスクはフォアグラウンドで実行中の状態となります。
ここで、ユーザが再びホームキーでホーム画面に戻り、最初に利用していたアプリを起動してバックグラウンドに居たタスクをフォアグラウンドへ遷移させると、もう一方のフォアグラウンドに居たタスクがバックグラウンドへ移ります。
これが Android のマルチタスクです。
マルチタスクで多数のタスクをバックグラウンドへ移すと、その分だけメモリを消費します。メモリ残量が少なくなると、システムは、バックグラウンドに居るタスクのなかから、優先順位を見てタスクの持つ Activity の破棄を試みます。
Activity が破棄されても、破棄される前の状態に復元できるよう、Activity のライフサイクルにしたがって実装をしておくべきです。
Activity には、以下の二つのコールバックメソッドが用意されており、これらを活用して、状態の保存と復旧を行います。
- onSaveInstanceState
-
Activity が破棄される前に、状態を保存するために呼ばれるコールバックメソッドです。
引数に Bundle オブジェクト(Android 独自のコレクション・フレームワーク)が渡されるので、このオブジェクトに状態を保存します。
- onRestoreInstanceState
-
Activity が再度フォアグラウンドに遷移する際、状態を復旧するために呼ばれるコールバックメソッドです。
引数に Bundle オブジェクトが渡されます。この Bundle オブジェクトには、onSaveInstanceState() メソッドで保存した状態が入っていますので、その状態を取り出して復旧します。
Activity の onCreate() メソッドの引数にある Bundle オブジェクトも同じ役割を持っています。
同様、Fragment にも onSaveInstanceState() メソッドが定義されており、ここで状態の保存をすることができます。
一方、状態の復旧には、onCreate() や onCreateView()、onActivityCreated() メソッドの引数にある Bundle オブジェクトを利用します。
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 と、それに対応するレイアウトを作成してください。
- (実習) 新しく作った Activity を、アプリ起動時に表示するよう変更してください。
- (実習) 新しい Fragment と、それに対応するレイアウトを作成してください。
- (実習) 新しく作った Fragment を、既存の Activity に組み込んでください。
- (課題)
Toast#makeText(Context, int, int)
メソッドを使って、Activity のライフサイクルのコールバックメソッドが、どのような順番で実行されるかを画面に表示し、列挙してください(ControllerLifecycleAssignment1 プロジェクトを使う)。 - (課題) Activity が 2 つ用意されています。起動後の Activity から呼び出された Activity で、状態管理を適切に行なってください(ControllerLifecycleAssignment2 プロジェクトを使う)。
- (課題)
ControllerLifecycleAssignment3
のactivity_main.xml
から、Fragment に切り出すものを適宜選択し、Fragment 化してください。 - (課題) 課題 7 で切り出した Fragment を用いて、新しい画面を作成してください(課題 7 と同じプロジェクトの中で実施する)。
- (課題) Activity で発生しているメモリリークを特定し、解消してください(ControllerLifecycleAssignment4 プロジェクトを使う)。
Portions of this page are reproduced from work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.