The Android Developer Challenge is back! Submit your idea before December 2.

構成の変更を処理する

端末の構成の中には、実行の際に変化するものがあります(画面の向き、キーボードの可用性、ユーザーがマルチウィンドウ モードを有効にしたときなど)。そのような変更が発生すると、Android は実行中の Activity を再起動します(onDestroy() が呼び出され、その後に onCreate() が呼び出されます)。再起動の動作は、新しい端末構成に一致する代替リソースを使用してアプリを自動的に再読み込みすることで、アプリを新しい構成に適応させることを目的としています。

再起動を適切に処理するには、アクティビティが以前の状態を復元することが重要です。onSaveInstanceState() オブジェクト、ViewModel オブジェクト、永続ストレージの組み合わせを使用して、構成変更全体のアクティビティの UI の状態を保存して復元できます。アクティビティの状態を保存する方法の詳細については、UI の状態を保存するをご覧ください。

アプリ自体がそのままの状態で再起動することをテストするには、アプリでさまざまなタスクを実行中に、構成の変更(画面の向きの変更など)を呼び出す必要があります。構成の変更を処理したり、ユーザーに電話がかかってきて、アプリのプロセスが破棄されるほど時間が経過してからアプリに戻ったりするような場合には、ユーザーデータや状態を失うことなくいつでもアプリを再起動できるようにする必要があります。アクティビティの状態を復元する方法については、アクティビティのライフサイクルをご覧ください。

ただし、場合によっては、アプリを再起動すると、大量のデータの復元にコストがかかり、操作性が悪くなることがあります。そのような場合、次の 2 つの方法で処理できます。

  1. 構成の変更中にオブジェクトを保持する

    構成の変更時にアクティビティを再起動できますが、ステートフル オブジェクトがアクティビティの新しいインスタンスに移動します。

  2. 構成の変更を自分で処理する

    構成変更の処理は思っている以上に複雑なため、自分で構成変更を処理することはお勧めできません。ただし、推奨オプション(onSaveInstanceState()、ViewModel、永続ストレージ)を使用して UI の状態を保存できない場合は、代わりに特定の構成変更中にシステムがアクティビティを再起動しないようにすることができます。必要に応じて手動でアクティビティを更新できるように、構成が変更された場合には、アプリはコールバックを受け取ります。

構成の変更中にオブジェクトを保持する

アクティビティの再起動で大量のデータの復元、ネットワーク接続の再構築、他の負荷のかかる操作の実行が必要になる場合、構成変更のために完全な再起動を実行すると、操作性が悪くなってしまうことがあります。さらに、システムの onSaveInstanceState() コールバックによって保存される Bundle を使用して、アクティビティの状態を完全に復元できない場合もあります。大きなオブジェクト(ビットマップなど)を扱うためのものではなく、メインスレッドで内部のデータをシリアル化してから逆シリアル化を行うため、多くのメモリを消費することになり、構成の変更に時間がかかってしまいます。そのような場合は、ViewModel オブジェクトを使用することで、アクティビティの再初期化の負担を軽減できます。構成変更全体で ViewModel が保存されます。再度クエリを実行する必要がなく、UI データを保存するのに最適な場所です。アプリで ViewModel を使用する方法の詳細については、ViewModel ガイドをご覧ください。

構成の変更を自分で処理する

アプリに特定の構成変更の際にリソースを更新する必要がなく、パフォーマンスの制限によりアクティビティの再起動を回避する必要がある場合は、構成の変更をアクティビティ自身が処理することを宣言します。そうすることで、システムによってアクティビティが再起動されなくなります。

注意:構成の変更を自分で処理すると、変更がシステムによって自動的に適用されることがないため、代替リソースの使用が非常に難しくなります。この手法は、構成の変更による再起動を回避する際の最後の手段として検討する手法です。ほとんどの場合、アプリでの使用は推奨されません。

アクティビティで構成の変更を処理することを宣言するには、マニフェスト ファイルの該当する <activity> 要素を編集し、処理する構成を表す値を使用して android:configChanges 属性を追加します。使用可能な値は、android:configChanges 属性のドキュメントに一覧が記載されています。一般的によく使用される値は、"orientation""screenSize""keyboardHidden" です。"orientation" の値は、画面の向きを変更した場合の再起動を回避します。Android 3.2(API レベル 13)以降では、"screenSize" の値も画面の向きを変更した場合の再起動を回避します。アプリの構成変更を手動で処理する場合は、android:configChanges 属性で "orientation""screenSize" の両方の値を宣言する必要があります。"keyboardHidden" の値は、キーボードの可用性を変更した場合の再起動を回避します。パイプ記号の | 文字を使用して区切ることで、属性内に複数の構成値を宣言できます。

たとえば、次のマニフェスト コードでは、画面の向きとキーボードの可用性の変更の両方を処理するアクティビティを宣言しています。

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

これで、これらのいずれかの構成が変更された場合でも、MyActivity が再起動することはなくなりました。代わりに、MyActivityonConfigurationChanged() の呼び出しを受け取ります。このメソッドには、新しい端末構成を指定する Configuration オブジェクトが渡されます。Configuration のフィールドを読み込むことで新しい構成を判断し、インターフェースに使用するリソースを更新して適切に変更できます。このメソッドが呼び出されると、アクティビティの Resources オブジェクトが更新され、新しい構成に基づいたリソースを返します。それにより、システムがアクティビティを再起動しなくても、UI の要素を簡単にリセットできます。

たとえば、次の onConfigurationChanged() の実装により現在の端末の画面の向きがチェックされます。

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks the orientation of the screen
    if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show()
    } else if (newConfig.orientation === Configuration.ORIENTATION_PORTRAIT) {
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Configuration オブジェクトは、変更された構成だけでなく、現在のすべての構成を表します。ほとんどの場合、構成の変更内容を正確に把握する必要はなく、代替の構成を提供するすべてのリソースを処理中の構成に割り当て直します。たとえば、現在は Resources オブジェクトが更新されたため、任意の ImageViewsetImageResource() でリセットでき、新しい構成には最適なリソースが使用されます(リソースの提供をご覧ください)。

Configuration フィールドの値は、Configuration クラスの特定の定数に一致する整数であることにご注意ください。それぞれのフィールドで使用する定数に関するドキュメントについては、Configuration リファレンスの該当するフィールドをご覧ください。

メモ:構成の変更をアクティビティで処理することを宣言すると、代替を提供するすべての要素をリセットする操作が必要になります。画面の向きの変更を処理するアクティビティを宣言しており、縦向きと横向きで切り替わる画像がある場合、onConfigurationChanged() の際にそれぞれの要素に各リソースを割り当て直す必要があります。

これらの構成の変更に基づいてアプリを更新する必要がない場合は、代わりに onConfigurationChanged() を実装できません。このような場合、構成の変更前に使用していたすべてのリソースがそのまま使用され、アクティビティの再起動を回避した状態となります。ただし、アプリはいつでもシャットダウンして以前のままの状態で再起動できる状態にしておく必要があるため、通常のアクティビティのライフサイクルでは、状態を保持しない手段としてこの手法を使用しないようにします。それは、アプリの再起動を回避できない他の構成の変更があるだけでなく、ユーザーがアプリを離れ、ユーザーがアプリに戻る前に破棄されてしまうようなイベントを処理する必要があるためです。