ユーザーがアプリの内外を移動してからアプリに戻ると、アプリ内の Activity インスタンスはライフサイクルのさまざまな状態の間を遷移します。Activity クラスには、状態が変化したときや、システムがアクティビティを作成、停止、再開しているとき、またはアクティビティが存在するプロセスを破棄しているときをアクティビティが認識できるようにするコールバックが備えられています。
ライフサイクル コールバック メソッド内で、ユーザーがアクティビティを離れてから戻った場合のアクティビティの動作を宣言できます。たとえば、ストリーミング動画プレーヤーを作成している場合は、ユーザーが別のアプリに切り替えたときに動画を一時停止し、ネットワーク接続を終了するようにできます。ユーザーが戻ってきた際に、ネットワークに再接続して、ユーザーが同じ場所から動画の再生を再開できるようにします。
各コールバックにより、特定の状態変化に対して適した作業を実行できます。適切な作業を適切なタイミングで行い、移行を正しく処理することで、アプリの堅牢性とパフォーマンスが向上します。たとえば、ライフサイクル コールバックを適切に実装することで、アプリで次の状況を回避できます。
- アプリの使用中にユーザーが電話を受信したり、別のアプリに切り替えたりするとクラッシュする。
- ユーザーが実際に使用していない場合に、貴重なシステム リソースが消費される。
- ユーザーがアプリを離れてから後で戻ると、ユーザーの進捗状況が失われる。
- 画面が横向きと縦向きの表示を切り替えている間に、クラッシュする、またはユーザーの進捗状況が失われる。
このドキュメントでは、アクティビティのライフサイクルについて詳しく説明します。最初にライフサイクル パラダイムについて説明します。次に、各コールバックに関して、実行時に内部で行われる処理と、その間に実装する必要がある対象について説明します。
続いて、アクティビティの状態とシステムの強制終了に対するプロセスの脆弱性との関係を簡単に紹介します。最後に、アクティビティの状態間の遷移に関連するトピックについて説明します。
おすすめの方法についてのガイダンスを含め、ライフサイクルの処理方法については、ライフサイクル対応コンポーネントによるライフサイクルの処理と UI の状態の保存をご覧ください。アクティビティをアーキテクチャ コンポーネントと組み合わせて使用することにより、製品版と同等の品質を備えた堅牢なアプリを設計する方法については、アプリのアーキテクチャ ガイドをご覧ください。
アクティビティのライフサイクルに関するコンセプト
アクティビティのライフサイクルにおけるステージ間を移動するために、Activity クラスには onCreate、onStart、onResume、onPause、onStop、onDestroy の 6 つのコールバックのコアセットがあります。アクティビティが新しい状態になると、これらの各コールバックが呼び出されます。
図 1 は、このパラダイムを視覚的に示しています。
ユーザーがアクティビティから離れる処理を開始すると、システムはアクティビティを解体するメソッドを呼び出します。場合によっては、アクティビティは部分的に解体され、メモリ内に残ります(ユーザーが別のアプリに切り替えた場合など)。このような場合、アクティビティはフォアグラウンドに戻ることができます。
ユーザーがアクティビティに戻ると、アクティビティはユーザーが中断したところから再開します。いくつかの例外はありますが、アプリはバックグラウンドでの実行中にアクティビティを開始できません。
システムがシステム内にあるアクティビティとともに特定のプロセスを強制終了する可能性は、その時点でのアクティビティの状態によって異なります。状態と退避に対する脆弱性の関係について詳しくは、アクティビティの状態とメモリからの退避のセクションをご覧ください。
アクティビティの複雑さにもよりますが、すべてのライフサイクル メソッドを実装する必要はないと考えられます。ただし、それぞれを理解して、ユーザーの期待に応える動作をするアプリを実装することが重要です。
ライフサイクル コールバック
このセクションでは、アクティビティのライフサイクルで使用されるコールバック メソッドの概念と実装について説明します。
一部のアクションは、アクティビティのライフサイクル メソッドに属します。ただし、依存コンポーネントのアクションを実装するコードは、アクティビティのライフサイクル メソッドではなく、コンポーネントに配置します。これを行うには、依存コンポーネントをライフサイクル対応にする必要があります。依存コンポーネントをライフサイクル対応にする方法については、ライフサイクル対応コンポーネントによるライフサイクルの処理をご覧ください。
onCreate
このコールバックを実装する必要があります。このコールバックは、システムが最初にアクティビティを作成したときに呼び出されます。アクティビティが作成されると、アクティビティは作成済みの状態になります。onCreate メソッドでは、アクティビティのライフサイクルの全期間で 1 回のみ実行する必要がある基本的なアプリの起動ロジックを実行します。
たとえば、onCreate の実装では、データをリストにバインドし、アクティビティを ViewModel に関連付け、一部のクラススコープ変数をインスタンス化します。このメソッドはパラメータ savedInstanceState を受け取ります。これは、アクティビティの以前に保存された状態を含む Bundle オブジェクトです。アクティビティがこれまで存在しなかった場合、Bundle オブジェクトの値は null になります。
ライフサイクル対応コンポーネントがアクティビティのライフサイクルに接続されている場合は、ON_CREATE イベントを受け取ります。@OnLifecycleEvent アノテーションが付けられたメソッドが呼び出されるため、ライフサイクル対応コンポーネントは、作成された状態に必要なセットアップ コードを実行できます。
次の onCreate メソッドの例は、ユーザー インターフェースの宣言(XML レイアウト ファイルで定義)、メンバー変数の定義、一部の UI の構成など、アクティビティの基本設定を示しています。この例では、XML レイアウト ファイルはファイルのリソース ID R.layout.main_activity を setContentView に渡します。
Kotlin
lateinit var textView: TextView
// Some transient state for the activity instance.
var gameState: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
// Call the superclass onCreate to complete the creation of
// the activity, like the view hierarchy.
super.onCreate(savedInstanceState)
// Recover the instance state.
gameState = savedInstanceState?.getString(GAME_STATE_KEY)
// Set the user interface layout for this activity.
// The layout is defined in the project res/layout/main_activity.xml file.
setContentView(R.layout.main_activity)
// Initialize member TextView so it is available later.
textView = findViewById(R.id.text_view)
}
// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}
// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
outState?.run {
putString(GAME_STATE_KEY, gameState)
putString(TEXT_VIEW_KEY, textView.text.toString())
}
// Call superclass to save any view hierarchy.
super.onSaveInstanceState(outState)
}
Java
TextView textView;
// Some transient state for the activity instance.
String gameState;
@Override
public void onCreate(Bundle savedInstanceState) {
// Call the superclass onCreate to complete the creation of
// the activity, like the view hierarchy.
super.onCreate(savedInstanceState);
// Recover the instance state.
if (savedInstanceState != null) {
gameState = savedInstanceState.getString(GAME_STATE_KEY);
}
// Set the user interface layout for this activity.
// The layout is defined in the project res/layout/main_activity.xml file.
setContentView(R.layout.main_activity);
// Initialize member TextView so it is available later.
textView = (TextView) findViewById(R.id.text_view);
}
// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}
// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(GAME_STATE_KEY, gameState);
outState.putString(TEXT_VIEW_KEY, textView.getText());
// Call superclass to save any view hierarchy.
super.onSaveInstanceState(outState);
}
XML ファイルを定義して setContentView に渡す代わりに、アクティビティ コード内に新しい View オブジェクトを作成し、新しい View オブジェクトを ViewGroup に挿入してビュー階層を作成することもできます。ルートの ViewGroup を setContentView に渡してそのレイアウトを使用します。ユーザー インターフェースの作成の詳細については、ユーザー インターフェースのドキュメントをご覧ください。
アクティビティは作成済みの状態にとどまりません。onCreate メソッドの実行が終了すると、アクティビティは開始状態になり、システムは直ちに onStart メソッドと onResume メソッドを呼び出します。
onStart
アクティビティが開始状態になると、システムは onStart を呼び出します。アプリがアクティビティをフォアグラウンドに移動して操作可能な状態にする準備を行うと、この呼び出しによってアクティビティがユーザーに表示されます。たとえば、このメソッドでは、UI を保持するコードが初期化されます。
アクティビティが開始状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントは、ON_START イベントを受け取ります。
onStart メソッドは直ちに完了し、作成済みの状態の場合と同様に、アクティビティは開始状態にとどまりません。このコールバックが終了すると、アクティビティは再開状態になり、システムは onResume メソッドを呼び出します。
onResume
アクティビティは再開状態になるとフォアグラウンドに移動し、システムは onResume コールバックを呼び出します。これは、アプリがユーザーと対話する状態です。フォーカスがアプリから他の対象に移動する状況が発生するまで、アプリはこの状態を保持します。このような状況には、デバイスで電話の着信があった場合、ユーザーが別のアクティビティに移動した場合、デバイスの画面がオフになった場合などがあります。
アクティビティが再開状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントは、ON_RESUME イベントを受け取ります。この時点で、ライフサイクル コンポーネントは、コンポーネントが表示されフォアグラウンドにある状態で実行する必要がある機能(カメラ プレビューの開始など)を有効にできます。
割り込みイベントが発生すると、アクティビティは一時停止状態になり、システムは onPause コールバックを呼び出します。
アクティビティが一時停止状態から再開状態に戻ると、システムは再び onResume メソッドを呼び出します。このため、onPause の実行中に解放したコンポーネントの初期化、およびアクティビティが再開状態になるたびに必要となるその他の初期化を実行するように、onResume を実装します。
次の例は、コンポーネントが ON_RESUME イベントを受け取ると、カメラにアクセスするライフサイクル対応コンポーネントを示しています。
Kotlin
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun initializeCamera() {
if (camera == null) {
getCamera()
}
}
...
}
Java
public class CameraComponent implements LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void initializeCamera() {
if (camera == null) {
getCamera();
}
}
...
}
上記のコードは、LifecycleObserver が ON_RESUME イベントを受け取るとカメラを初期化します。ただし、マルチウィンドウ モードでは、一時停止状態のアクティビティも完全に表示される場合があります。たとえば、アプリがマルチウィンドウ モードで、ユーザーがアクティビティが表示されていないウィンドウをタップすると、アクティビティは一時停止状態になります。
アプリが再開状態(フォアグラウンドで表示されアクティブな状態)になった場合にのみカメラをアクティブにするには、上記の ON_RESUME イベントの後にカメラを初期化します。アクティビティが一時停止状態であるものの表示されている場合(マルチウィンドウ モードになっている場合など)にカメラをアクティブな状態に保つには、ON_START イベントの後にカメラを初期化します。
ただし、アクティビティが一時停止状態であるときにカメラをアクティブにすると、マルチウィンドウ モードで使用している別の再開状態のアプリでカメラへのアクセスが拒否される場合があります。アクティビティが一時停止状態になっている間にカメラをアクティブな状態で維持することが必要な場合もありますが、実際にはこのようにすると全般的なユーザーの操作性が低下します。
そのため、マルチ ウィンドウ モードのコンテキストで共有システム リソースの制御を行うのに最も適切なライフサイクルの場所を慎重に検討してください。マルチウィンドウ モードのサポートの詳細については、マルチウィンドウ モードをサポートするをご覧ください。
初期化操作を実行するビルドアップ イベントの選択にかかわらず、対応するライフサイクル イベントを使用してリソースを解放してください。ON_START イベントの後になんらかのアイテムを初期化する場合は、ON_STOP イベントの後に解放または終了してください。ON_RESUME イベントの後に初期化する場合は、ON_PAUSE イベントの後に解放してください。
上記のコード スニペットは、ライフサイクル対応コンポーネントにカメラ初期化コードを配置します。代わりに、このコードを onStart や onStop などのアクティビティのライフサイクルのコールバックに直接挿入することもできますが、この方法はおすすめしません。このロジックを独立したライフサイクル対応コンポーネントに追加すると、コードを複製せずにコンポーネントを複数のアクティビティで再利用できます。ライフサイクル対応コンポーネントの作成方法については、ライフサイクル対応コンポーネントによるライフサイクルの処理(ビュー)をご覧ください。
onPause
ユーザーがアクティビティを離れることを最初に示すために、システムはこのメソッドを呼び出します(アクティビティが破棄されることを意味するとは限りません)。これは、アクティビティがフォアグラウンドにないことを示しますが、ユーザーがマルチウィンドウ モードの場合は、アクティビティが引き続き表示されます。アクティビティがこの状態になる理由はいくつかあります。
onResumeコールバックに関するセクションで説明されているように、アプリの実行に割り込むイベントが発生すると、現在のアクティビティが一時停止します。これは最も一般的なケースです。- マルチ ウィンドウ モードでは、一度に 1 つのアプリのみがフォーカスされ、システムはその他のアプリをすべて一時停止します。
- 新しい半透明のアクティビティ(ダイアログなど)が開くと、そのアクティビティがカバーするアクティビティは一時停止します。対象のアクティビティが引き続き部分的に表示されているものの、目的のコンテンツとして表示されていない場合は、一時停止状態が保持されます。
アクティビティが一時停止状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントは、ON_PAUSE イベントを受け取ります。この時点でライフサイクル コンポーネントは、コンポーネントがフォアグラウンドにないときに実行する必要のない機能を停止できます(カメラ プレビューの停止など)。
Activity が一時停止状態であり、間もなく再開する予定である場合は、onPause メソッドを使用して、続行できない、または適度に続行する可能性がある操作を一時停止または調整します。
また、onPause メソッドを使用して、システム リソース、(GPS などの)センサーの操作、またはアクティビティが一時停止されており、ユーザーが必要としない状態である間に電池寿命に影響を与えるリソースを解放することもできます。
ただし、onResume のセクションで説明したように、アプリがマルチウィンドウ モードの場合、一時停止状態のアクティビティが引き続き完全に表示されることがあります。マルチウィンドウ モードのサポートが強化されるように、onPause ではなく onStop を使用して、UI 関連のリソースと操作を完全に解放または調整することを検討してください。
ON_PAUSE イベントに対する LifecycleObserver のリアクションの例を以下に示します。これは、上記の ON_RESUME イベントの例に対応するものであり、ON_RESUME イベントを受け取った後に初期化されたカメラを解放します。
Kotlin
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun releaseCamera() {
camera?.release()
camera = null
}
...
}
Java
public class JavaCameraComponent implements LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void releaseCamera() {
if (camera != null) {
camera.release();
camera = null;
}
}
...
}
この例では、LifecycleObserver が ON_PAUSE イベントを受け取った後にカメラのリリースコードが配置されています。
onPause の実行は非常に短時間で終了し、保存操作を実行するのに十分な時間を確保できない場合があります。このため、アプリデータまたはユーザーデータの保存、ネットワーク呼び出し、データベース トランザクションの実行には onPause を使用しないでください。このような作業は、メソッドが完了する前に完了しない可能性があります。
代わりに、高負荷のシャットダウン操作は onStop の実行中に行ってください。onStop 中に実行する適切なオペレーションの詳細については、次のセクションをご覧ください。データの保存について詳しくは、状態の保存と復元のセクションをご覧ください。
onPause メソッドが完了しても、アクティビティが一時停止状態でなくなるわけではありません。むしろ、アクティビティが再開されるか、ユーザーに対して完全に非表示になるまで、アクティビティはこの状態のままになります。アクティビティが再開すると、システムは再び onResume コールバックを呼び出します。
アクティビティが一時停止状態から再開状態に戻ると、システムは Activity インスタンスをメモリ内に常駐させ、onResume を呼び出す際にこのインスタンスを再呼び出しします。このシナリオでは、再開状態に至ったいずれかのコールバック メソッドの間に作成されたコンポーネントを再初期化する必要はありません。アクティビティが完全に非表示になると、システムは onStop を呼び出します。
onStop
ユーザーに表示されなくなったアクティビティは、停止状態になり、システムは onStop コールバックを呼び出します。これは、新たに開始されたアクティビティが画面全体を占有する場合などに発生します。アクティビティの実行が終了し、間もなく消滅する場合にも、システムによって onStop が呼び出されます。
アクティビティが停止状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントは、ON_STOP イベントを受け取ります。この時点で、ライフサイクル コンポーネントは、コンポーネントが画面に表示されていない間に実行する必要のない機能を停止できます。
onStop メソッドでは、アプリがユーザーに表示されていない間は不要なリソースを解放または調整します。たとえば、アプリがアニメーションを一時停止する場合や、高精度の位置情報アップデートから低精度の位置情報アップデートに切り替える場合です。onPause の代わりに onStop を使用すると、ユーザーがマルチウィンドウ モードでアクティビティを表示している場合も、UI 関連の処理が続行されます。
また、CPU に比較的高い負荷をかけるシャットダウン操作を実行するには、onStop を使用します。たとえば、データベースに情報を保存する適切なタイミングが見つからない場合は、onStop でこの保存を実行できます。下書きのメモの内容を永続ストレージに保存する onStop の実装例を次に示します。
Kotlin
override fun onStop() {
// Call the superclass method first.
super.onStop()
// Save the note's current draft, because the activity is stopping
// and we want to be sure the current note progress isn't lost.
val values = ContentValues().apply {
put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
}
// Do this update in background on an AsyncQueryHandler or equivalent.
asyncQueryHandler.startUpdate(
token, // int token to correlate calls
null, // cookie, not used here
uri, // The URI for the note to update.
values, // The map of column names and new values to apply to them.
null, // No SELECT criteria are used.
null // No WHERE columns are used.
)
}
Java
@Override
protected void onStop() {
// Call the superclass method first.
super.onStop();
// Save the note's current draft, because the activity is stopping
// and we want to be sure the current note progress isn't lost.
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());
// Do this update in background on an AsyncQueryHandler or equivalent.
asyncQueryHandler.startUpdate (
mToken, // int token to correlate calls
null, // cookie, not used here
uri, // The URI for the note to update.
values, // The map of column names and new values to apply to them.
null, // No SELECT criteria are used.
null // No WHERE columns are used.
);
}
上記のコードサンプルでは SQLite が直接使用されています。ただし、SQLite に抽象化レイヤを提供する永続ライブラリである Room を使用することをおすすめします。Room を使用するメリットと、Room をアプリで実装する方法については、Room 永続ライブラリのガイドをご覧ください。
アクティビティが停止状態になると、Activity オブジェクトがメモリ内に常駐します。すべての状態とメンバー情報が保持されますが、ウィンドウ マネージャーには接続されません。アクティビティが再開すると、この情報を再呼び出しします。
再開状態に至ったいずれかのコールバック メソッドの間に作成されたコンポーネントを再初期化する必要はありません。また、システムはレイアウト内の各 View オブジェクトの現在の状態を追跡するため、ユーザーが EditText ウィジェットにテキストを入力すると、そのコンテンツが保持されることから、保存および復元する必要はありません。
停止状態のアクティビティは、その後ユーザーとやり取りするために戻るか、実行が終了して消滅します。アクティビティが戻ると、システムは onRestart を呼び出します。Activity の実行が終了すると、システムは onDestroy を呼び出します。
onDestroy
onDestroy はアクティビティが破棄される前に呼び出されます。このコールバックは、次のいずれかの理由でシステムによって呼び出されます。
- アクティビティが終了する(ユーザーがアクティビティを完全に閉じるか、アクティビティに対して
finishが呼び出さたことによる)。 - 構成の変更(デバイスの向きの変更やマルチウィンドウ モードへの切り替えなど)に伴いアクティビティが一時的に破棄される。
アクティビティが破棄状態に移行すると、アクティビティのライフサイクルに関連付けられているライフサイクル対応コンポーネントは ON_DESTROY イベントを受け取ります。この時点で、Activity が破棄される前にクリーンアップする必要があるデータをライフサイクル コンポーネントがクリーンアップできます。
破棄される理由を判別するロジックを Activity に挿入する代わりに、ViewModel オブジェクトを使用して Activity の関連ビューデータを格納します。構成の変更に伴い Activity が再作成される場合、ViewModel は保持され、次の Activity インスタンスに渡されることから、なんらかの処理を行う必要はありません。
Activity が再作成されない場合は、ViewModel で onCleared メソッドが呼び出されます。ここで、破棄する前にクリーンアップする必要があるデータをクリーンアップできます。これらの 2 つのシナリオは、isFinishing メソッドで区別できます。
アクティビティが終了する場合、onDestroy はアクティビティが受け取る最後のライフサイクル コールバックです。構成の変更の結果として onDestroy が呼び出される場合、システムは新しいアクティビティ インスタンスを即時に作成し、新しい構成の新しいインスタンスに対して onCreate を呼び出します。
onDestroy コールバックは、onStop などの以前のコールバックによって解放されていないすべてのリソースを解放します。
一時的な UI の状態の保存と復元
ユーザーが期待するのは、構成の変更後も、たとえば回転やマルチウィンドウ モードへの切り替えが行われてもアクティビティの UI の状態が変わらないことです。ただし、このような構成変更が行われると、システムはデフォルトでアクティビティを破棄し、アクティビティ インスタンスに保存されている UI の状態を消去します。
同様に、ユーザーは一時的に別のアプリに切り替えてから元のアプリに戻った場合にも、UI の状態が同じままであることを想定します。ただし、システムはユーザーが離れてアクティビティが停止している間に、アプリのプロセスを破棄する場合があります。
システムの制約が原因でアクティビティが破棄される場合は、ViewModel、onSaveInstanceState、およびローカル ストレージを組み合わせて使用して、ユーザーの一時的な UI の状態を保持します。ユーザーが想定する内容とシステムの動作の比較、およびシステムが開始したアクティビティとプロセスの終了時に複雑な UI の状態のデータを保持する最適な方法については、UI の状態を保存するをご覧ください。
このセクションでは、インスタンスの状態と onSaveInstance メソッド(アクティビティ自体に対するコールバック)の実装方法について概説します。UI データが軽量な場合は、onSaveInstance を単独で使用して、構成の変更とシステムが開始したプロセスの終了の両方に対して UI の状態を保持できます。ただし、onSaveInstance ではシリアル化/シリアル化解除のコストが発生するため、ほとんどの場合は、UI の状態を保存するで概説しているように、ViewModel と onSaveInstance の両方を使用します。
インスタンスの状態
通常のアプリの動作によって、アクティビティが破棄されるシナリオがいくつかあります。たとえば、ユーザーが [戻る] ボタンを押す、またはアクティビティが finish メソッドを呼び出すことによって自身の破棄を知らせる場合が考えられます。
ユーザーが [戻る] を押すか、アクティビティ自体が終了したためにアクティビティが破棄されると、システムとユーザー両方の Activity インスタンスのコンセプトは完全に失われます。これらのシナリオでは、ユーザーの想定とシステムの動作が一致しており、追加の作業は不要です。
ただし、システムの制約(構成の変更やメモリ負荷など)が原因でアクティビティが破棄された場合、実際の Activity インスタンスは失われますが、システムにはこのアクティビティが存在したことが記憶されます。ユーザーがアクティビティに戻ろうとすると、システムはアクティビティが破棄された時点でのアクティビティの状態を記述する一連の保存済みデータを使用して、アクティビティの新しいインスタンスを作成します。
システムが以前の状態を復元するために使用する保存済みデータは、インスタンス状態と呼ばれます。これは、Bundle オブジェクトに保存された Key-Value ペアのコレクションです。デフォルトでは、システムは Bundle インスタンス状態を使用して、アクティビティのレイアウトの各 View オブジェクトに関する情報(EditText ウィジェットに入力されたテキスト値など)を保存します。
そのため、アクティビティのインスタンスが破棄されてから再作成された場合、レイアウトの状態はコードを必要とすることなく以前の状態に復元されます。ただし、アクティビティには、アクティビティ内のユーザーの作業状況を追跡するメンバー変数など、復元する必要があると考えられる状態に関する情報が他にも含まれている場合があります。
Bundle オブジェクトは、メインスレッドでのシリアル化を必要とし、システムプロセス メモリを消費するため、ごく少量のデータを保存する場合を除いて、データの保存には適していません。保持するデータ量が非常に少ない場合以外は、UI の状態の保存で概説したように、永続ローカル ストレージ、onSaveInstanceState メソッド、ViewModel クラスを組み合わせた方法でデータを保持する必要があります。
onSaveInstanceState を使用してシンプルで軽量な UI の状態を保存する
アクティビティの停止が開始されると、システムは onSaveInstanceState メソッドを呼び出します。これにより、アクティビティは状態情報をインスタンス状態バンドルに保存できます。このメソッドのデフォルトの実装では、EditText ウィジェット内のテキストや ListView ウィジェットのスクロール位置など、アクティビティのビュー階層の状態に関する一時的な情報が保存されます。
アクティビティの追加のインスタンス状態情報を保存するには、onSaveInstanceState をオーバーライドし、アクティビティが予期せず破棄された際に保存された Bundle オブジェクトにキーと値のペアを追加します。onSaveInstanceState をオーバーライドする場合、デフォルトの実装でビュー階層の状態を保存するには、スーパークラス実装を呼び出す必要があります。これを次の例に示します。
Kotlin
override fun onSaveInstanceState(outState: Bundle?) {
// Save the user's current game state.
outState?.run {
putInt(STATE_SCORE, currentScore)
putInt(STATE_LEVEL, currentLevel)
}
// Always call the superclass so it can save the view hierarchy state.
super.onSaveInstanceState(outState)
}
companion object {
val STATE_SCORE = "playerScore"
val STATE_LEVEL = "playerLevel"
}
Java
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state.
savedInstanceState.putInt(STATE_SCORE, currentScore);
savedInstanceState.putInt(STATE_LEVEL, currentLevel);
// Always call the superclass so it can save the view hierarchy state.
super.onSaveInstanceState(savedInstanceState);
}
ユーザー設定やデータベースのデータなどの永続データを保存するには、アクティビティがフォアグラウンドにある適切な時点で保存してください。このような適切な時点がない場合は、onStop メソッドの実行中に永続データを保存してください。
保存済みのインスタンスの状態を使用してアクティビティの UI の状態を復元する
以前に破棄したアクティビティを再作成する場合は、システムがアクティビティに渡した Bundle から保存済みのインスタンスの状態を復元できます。コールバック メソッド onCreate と onRestoreInstanceState は両方とも、同じ Bundle を受け取り、これにはインスタンスの状態情報が含まれています。
onCreate メソッドは、システムがアクティビティの新しいインスタンスを作成している場合、または以前のインスタンスを再作成している場合のいずれであっても呼び出されるため、状態 Bundle を読み取る前にこれが null かどうかを確認する必要があります。null の場合は、破棄された以前のインスタンスが復元されるのではなく、アクティビティの新しいインスタンスが作成されます。
次のコード スニペットは、onCreate で一部の状態データを復元する方法を示しています。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // Always call the superclass first
// Check whether we're recreating a previously destroyed instance.
if (savedInstanceState != null) {
with(savedInstanceState) {
// Restore value of members from saved state.
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
} else {
// Probably initialize members with default values for a new instance.
}
// ...
}
Java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance.
if (savedInstanceState != null) {
// Restore value of members from saved state.
currentScore = savedInstanceState.getInt(STATE_SCORE);
currentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
// Probably initialize members with default values for a new instance.
}
// ...
}
onCreate の実行中に状態を復元する代わりに、onStart メソッドの後にシステムが呼び出す onRestoreInstanceState を実装することもできます。システムは、復元対象の保存済みの状態がある場合にのみ onRestoreInstanceState を呼び出すため、Bundle が null であるかどうかを確認する必要はありません。
Kotlin
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
// Always call the superclass so it can restore the view hierarchy.
super.onRestoreInstanceState(savedInstanceState)
// Restore state members from saved instance.
savedInstanceState?.run {
currentScore = getInt(STATE_SCORE)
currentLevel = getInt(STATE_LEVEL)
}
}
Java
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy.
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance.
currentScore = savedInstanceState.getInt(STATE_SCORE);
currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
アクティビティ間の移動
アプリの存続期間中に、アプリはアクティビティに入り、アクティビティから出る操作を何度も行う可能性があります。たとえば、ユーザーがデバイスの [戻る] ボタンをタップする、またはアクティビティが別のアクティビティを開始する場合が考えられます。
このセクションでは、アクティビティの適切な遷移を実装するうえで理解しておく必要があるトピックについて説明します。これらのトピックには、別のアクティビティからのアクティビティの開始、アクティビティの状態の保存、アクティビティの状態の復元などがあります。
アクティビティを別のアクティビティから開始する
特定の時点で、アクティビティが別のアクティビティを開始する必要が生じることは頻繁にあります。たとえば、アプリが現在の画面から新しい画面に移動する必要がある場合に、この動作が必要です。
実行中のアクティビティで、新たに開始しようとしているアクティビティから結果を戻す必要があるかどうかに応じて、startActivity メソッドまたは startActivityForResult メソッドのいずれかを使用して新しいアクティビティを開始します。いずれの場合も、Intent オブジェクトを渡します。
Intent オブジェクトは、開始するアクティビティを正確に指定するか、実行する操作のタイプを記述します。システムが適切なアクティビティを選択しますが、そのアクティビティが他のアプリのアクティビティである場合も考えられます。Intent オブジェクトには、開始したアクティビティで使用する少量のデータを含めることもできます。Intent クラスの詳細については、インテントとインテント フィルタをご覧ください。
startActivity
新規に開始されたアクティビティから結果を返す必要がない場合、現在のアクティビティは startActivity メソッドを呼び出して新しいアクティビティを開始できます。
独自のアプリで作業する場合には、既知のアクティビティを起動するだけで済むことが頻繁にあります。たとえば、次のコード スニペットは、SignInActivity という名前のアクティビティを起動する方法を示しています。
Kotlin
val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)
Java
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
アクティビティから取得したデータを使用して、アプリでメールやテキスト メッセージの送信、ステータスの更新などの操作を行うことが必要な場合もあります。アプリにそのような操作を実行できる独自のアクティビティがない場合は、代わりにデバイス上の他のアプリから提供された、該当の操作を実行可能なアクティビティを活用できます。
ここでインテントが非常に役立ちます。実行する操作を記述するインテントを作成し、システムが別のアプリから適切なアクティビティを起動します。インテントを処理できるアクティビティが複数ある場合は、ユーザーが使用するアクティビティを選択できます。たとえば、ユーザーがメール メッセージを送信できるようにするには、次のインテントを作成します。
Kotlin
val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)
Java
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
インテントに追加された EXTRA_EMAIL のエクストラは、メールの送信先となるメールアドレスの文字列配列です。メールアプリがこのインテントに応答すると、エクストラにある文字列配列を読み取り、メール作成フォームの「宛先」フィールドにアドレスを配置します。この場合、メールアプリのアクティビティが開始され、ユーザーが操作を完了するとアクティビティが再開されます。
startActivityForResult
アクティビティの終了時に、アクティビティから結果を戻すことが必要な場合があります。たとえば、ユーザーが連絡先リストから担当者を選択できるようにするアクティビティを開始できます。このアクティビティは終了時に、選択された担当者を返します。これを行うには、startActivityForResult(Intent, int) メソッドを呼び出します。このメソッドでは、整数パラメータにより呼び出しが識別されます。
この ID によって、同一のアクティビティからの複数の startActivityForResult(Intent, int) 呼び出しを区別できます。この ID はグローバル ID ではないため、他のアプリやアクティビティと競合するリスクはありません。結果は onActivityResult(int, int, Intent) メソッドを介して返されます。
子アクティビティが終了すると、setResult(int) を呼び出して親にデータを返すようにすることができます。子アクティビティは結果コードを提供する必要があります。結果コードには標準の結果コード RESULT_CANCELED, RESULT_OK、または RESULT_FIRST_USER から始まるカスタム値を使用できます。
また、子アクティビティはオプションとして、必要な追加データを含む Intent オブジェクトを返すこともできます。親アクティビティは、onActivityResult(int, int, Intent) メソッドとともに、親アクティビティが当初指定した整数の ID を使用して情報を受け取ります。
クラッシュなど、なんらかの原因で子アクティビティが失敗すると、親アクティビティはコード RESULT_CANCELED の結果を受け取ります。
Kotlin
class MyActivity : Activity() {
// ...
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
// When the user center presses, let them pick a contact.
startActivityForResult(
Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
PICK_CONTACT_REQUEST)
return true
}
return false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
when (requestCode) {
PICK_CONTACT_REQUEST ->
if (resultCode == RESULT_OK) {
// A contact was picked. Display it to the user.
startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
}
}
}
companion object {
internal val PICK_CONTACT_REQUEST = 0
}
}
Java
public class MyActivity extends Activity {
// ...
static final int PICK_CONTACT_REQUEST = 0;
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
// When the user center presses, let them pick a contact.
startActivityForResult(
new Intent(Intent.ACTION_PICK,
new Uri("content://contacts")),
PICK_CONTACT_REQUEST);
return true;
}
return false;
}
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == PICK_CONTACT_REQUEST) {
if (resultCode == RESULT_OK) {
// A contact was picked. Display it to the user.
startActivity(new Intent(Intent.ACTION_VIEW, data));
}
}
}
}
アクティビティを連携させる
あるアクティビティが別のアクティビティを開始すると、双方でライフサイクルの遷移が発生します。 最初のアクティビティが動作を停止して、一時停止状態または停止状態となり、もう 1 つのアクティビティが作成されます。これらのアクティビティでディスクなどに保存されているデータを共有している場合は、2 つ目のアクティビティが作成される前に最初のアクティビティが完全に停止することはない点を理解しておくことが重要です。むしろ、2 つ目のアクティビティを開始するプロセスは、最初のアクティビティを停止するプロセスと重複します。
ライフサイクル コールバックの順序は、特に 2 つのアクティビティが同じプロセス(つまり同じアプリ)にあり、一方がもう一方のアクティビティを開始する場合に厳密に定義されます。アクティビティ A がアクティビティ B を開始する場合の動作の順序は次のとおりです。
- アクティビティ A の
onPauseメソッドが実行されます。 - アクティビティ B の
onCreate、onStart、onResumeの各メソッドが順次実行されます。このとき、アクティビティ B にユーザー フォーカスがあります。 - アクティビティ A が画面に表示されなくなった場合は、
onStopメソッドが実行されます。
このライフサイクル コールバックの順序により、アクティビティ間の情報の遷移を管理できます。