構成の変更に対処する

一部のデバイス構成(画面の向き、キーボードの利用状態など)は、実行時や、ユーザーがマルチ ウィンドウ モードを有効にしたときに変更される可能性があります。このような変更が生じると、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""screenLayout""keyboardHidden" です。

  • "orientation" 値は、画面の向きが変わったときに再起動するのを防ぎます。
  • "screenSize" 値も向きが変わったときの再起動を防ぎますが、Android 3.2(API レベル 13)以降でのみ使用できます。
  • "screenLayout" 値は、折りたたみ式スマートフォンやコンバーチブル型 Chromebook などのデバイスによってトリガーされる変更の検出に必要です。
  • "keyboardHidden" 値は、キーボードの利用状態が変わったときに再起動するのを防ぎます。

向きの変更をアプリ内で手動で処理するには、android:configChanges 属性で "orientation""screenSize""screenLayout" の値を宣言する必要があります。属性で複数の構成値を宣言するには、値をパイプ文字 | で区切ります。

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

<activity android:name=".MyActivity"
          android:configChanges="orientation|screenSize|screenLayout|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 オブジェクトが更新されれば、ImageView インスタンスを setImageResource() によって再設定することで、新しい構成に適切に対応するリソースが使用されます(アプリリソースの概要を参照)。

Configuration 項目の値は、Configuration クラスの特定の定数に一致する整数です。各項目で使用する定数については、Configuration リファレンスの該当する項目をご覧ください。

注: 構成の変更を処理するようにアクティビティを宣言した場合には、代替となる要素を再設定する必要があります。画面の向きの変更を処理するようにアクティビティを宣言し、縦向きと横向きの画像を切り替える場合は、onConfigurationChanged() で各リソースを各要素に再割り当てする必要があります。

この構成変更に基づいてアプリを更新する必要がない場合は、onConfigurationChanged() を実装しないようにすることも可能です。その場合は、構成の変更前に使用したすべてのリソースが引き続き使用され、アクティビティの再起動だけが回避されます。

ただしこの手法は、通常のアクティビティのライフサイクル中に状態を保持することを回避するものではありません。アプリは、シャットダウンして再起動しても、以前の状態が保持できていなければなりません。構成の変更を防ぐことができないと、アプリが再起動される場合があります。ユーザーがアプリから離れ、アプリがバックグラウンドに移動した場合、システムによってアプリが破棄されることがあります。