構成の変更に対処する

アプリの実行中に、デバイス構成の一部が変更される場合があります。変更の可能性がある構成の一部を以下に示します。

  • アプリの表示サイズ
  • 画面の向き
  • フォントのサイズと太さ
  • 言語 / 地域
  • ダークモードとライトモード
  • キーボードを使用できるかどうか

このような構成変更のほとんどは、なんらかのユーザー操作を原因として発生します。たとえば、デバイスの回転や折りたたみを行うと、アプリが使用できる画面内のスペースが変わります。同様に、フォントサイズ、言語、テーマなどのデバイス構成を変更すると、Configuration オブジェクト内の該当する値が変更されます。

このようなパラメータには通常、アプリケーションの UI に大幅な変更を加えることを要求します。そのため、Android プラットフォームには、そのような変更に対応するための専用のメカニズムがあります。このメカニズムは Activity の再作成です。

アクティビティの再作成

構成の変更があると、システムが Activity を再作成します。これを行うために、システムが onDestroy を呼び出し、既存の Activity インスタンスを破棄します。その後、onCreate を使用して新しいインスタンスが作成され、この新しい Activity インスタンスは、更新された新しい構成で初期化されます。またこれは、新しい構成で UI が再作成されることも意味します。

通常、Activity はコンポーザブルのホストとして機能します。Activity が再作成されると、Compose は新しい構成値を使用して UI も再作成します。

この再作成動作が、新しいデバイス構成と一致する代替リソースを使用してアプリを自動的に再読み込みすることで、アプリが新しい構成に適応するようにします。

再作成の例

文字列リソースを使用して静的タイトルを表示するコンポーザブルについて考えてみます。

// In the res/values/strings.xml file
// <string name="compose">Jetpack Compose</string>

// In your Compose code
Text(
    text = stringResource(R.string.compose)
)

Activity が作成されると、Text コンポーザブルは現在の構成(言語など)を読み取り、適切な文字列リソースを解決します。

言語が変更されると、システムがアクティビティを再作成します。この場合、Compose は UI を再作成します。stringResource は現在の構成から読み取るため、タイトルは自動的に正しいローカライズされた値に更新されます。

この再作成により、Activity 内のフィールドとして保持されている状態もすべて消去されます。

構成変更の前後で UI の状態を保存するには、推奨される状態管理パターンを使用します。データとビジネス ロジックには ViewModel を使用し、UI レベルの状態には rememberSaveable を使用します。これらのメカニズムにより、新しい構成を反映するように UI が更新される間、状態は Activity の再作成後も維持されます。

Compose での状態の保存について詳しくは、Compose で UI の状態を保存するをご覧ください。

ユーザーの想定

アプリのユーザーは、状態が保持されることを想定しています。ユーザーがフォームに入力中で、マルチウィンドウ モードで別のアプリを開いて情報を参照する場合、クリアされた状態のフォームやアプリ内のまったく別の場所に戻されてしまうと、ユーザー エクスペリエンスの低下につながります。デベロッパーは、構成の変更やアクティビティの再作成を通じて、一貫したユーザー エクスペリエンスを提供する必要があります。

アプリ内で状態が保持されているかどうかを確認するには、アプリがフォアグラウンドとバックグラウンドの両方で動作しているときに、構成を変更するアクションを実行できます。そのようなアクションには以下があります。

  • デバイスを回転させる
  • マルチ ウィンドウ モードを開始する
  • マルチウィンドウ モードまたはフリーフォーム ウィンドウでアプリケーションのサイズ変更を行う
  • 画面が複数ある折りたたみ式デバイスを折りたたむ
  • ダークモードとライトモードなど、システムのテーマを変更する
  • フォントサイズを変更する
  • システムやアプリの言語を変更する
  • ハードウェア キーボードの接続または接続解除を行う
  • ホルダーとの接続または接続解除を行う

Activity の再作成で関連する状態を維持するための方法はいくつかあります。どちらを使用するかは、保持する状態の種類によって異なります。

  • 複雑なデータや大規模データのプロセス終了を処理する永続ローカル ストレージ。永続ローカル ストレージには、データベースや DataStore があります。
  • ユーザーがアプリを積極的に使用している間、メモリ内の UI 関連の状態を処理する ViewModel インスタンスなどの保持オブジェクト
  • rememberSaveable を使用して、構成の変更やシステムによって開始されたプロセスの終了をまたいで一時的な UI の状態を保持します。これは、ユーザー入力、スクロール位置、ナビゲーションに依存するが、ViewModel には属さない状態に適しています。

各 API の詳細や API を適切に使用するタイミングについては、UI の状態を保存するをご覧ください。

アクティビティの再作成を制限する

特定の構成の変更が発生したときに、アクティビティの自動再作成が発生しないようにできます。最新の Compose 専用アプリでは、UI はどちらの方法でも再コンポーズされますが、構成の変更を直接処理することをおすすめします。

デフォルトでは、構成の変更があると、システムは UI やアクティビティから派生したオブジェクトなど、Activity を破棄して再作成します。アクティビティ自体が構成の変更を処理することを宣言すると、システムはこれを防ぎます。代わりに、Configuration オブジェクトのみが更新され、Compose が新しい値で UI を再コンポーズします。

Compose で構成の変更を直接処理することには、次のような利点があります。

  • パフォーマンスの向上: UI の再コンポーズは、特に小さな変更の場合、アクティビティの再作成サイクル全体よりもコストが低くなります。
  • スムーズなアニメーション: アクティビティの再起動を回避することで、デバイスの回転時のスムーズなレイアウト遷移など、構成変更全体で連続したアニメーションを実行できます。
  • 状態の保持: Activity インスタンスを保持すると、画面の回転などのイベント中に UI の状態が一時的に失われるリスクが軽減されます。システムによって開始されたプロセスの終了に対する状態の保存は、引き続き処理する必要があります。

特定の構成変更に関するアクティビティの再作成を無効にするには、AndroidManifest.xml ファイルの <activity> エントリで構成タイプを android:configChanges に追加します。有効な値は、android:configChanges 属性のドキュメントに記載されています。

次のマニフェスト コードは、画面の向きとキーボードの利用可能性が変更されたときに、MyActivityActivity 再作成を無効にします。

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

構成の変更に対応する

Jetpack Compose を使用すると、アプリで構成の変更に簡単に対応できるようになります。ただし、可能なすべての構成変更に対して Activity の再作成を無効にした場合、アプリは構成の変更を適切に処理する必要があります。

Configuration オブジェクトは、LocalConfiguration コンポジション ローカルの Compose UI 階層で使用できます。変更があるたびに、LocalConfiguration.current から読み取るコンポーズ可能な関数が再コンポーズされます。コンポジション ローカルの仕組みについては、CompositionLocal でローカルにスコープされたデータをご覧ください。

次の例では、コンポーザブルが特定の形式で日付を表示します。コンポーザブルは、LocalConfiguration.currentConfigurationCompat.getLocales を呼び出すことで、システムの言語 / 地域の構成の変更に対応します。

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

ロケールが変更されたときに Activity の再作成を回避するには、Compose コードをホストする Activity でロケール構成の変更を無効にする必要があります。これを行うには、android:configChangeslocale|layoutDirection に設定します。

構成の変更: 主なコンセプトとベスト プラクティス

構成を変更する場合、次の重要なコンセプトを理解しておく必要があります。

  • 構成: デバイス構成では、アプリの表示サイズ、ロケール、システムテーマなど、UI をどのようにユーザーに表示するかを定義します。Compose では、LocalConfiguration を使用して構成値にアクセスできます。
  • 構成の変更:ユーザー インタラクションにより構成が変更されます。たとえば、ユーザーがデバイス構成を変更したり、デバイスを操作するための物理的手法を変更したりする場合があります。構成の変更を防ぐ方法はありません。
  • Activity の再作成: 構成を変更すると、デフォルトで Activity が再作成されます。これは、新しい構成に応じてアプリの状態を再初期化するための組み込みメカニズムです。
  • Activity の破棄: Activity の再作成により、古い Activity インスタンスは破棄され、新しいインスタンスが作成されます。現在、古いインスタンスは使われていません。ライフサイクル スコープのオブジェクトへの参照を、意図したスコープを超えて保持しないようにします。
  • 状態:古い Activity インスタンスの状態は、新しい Activity インスタンスには存在しません。その 2 つは異なるオブジェクトのインスタンスだからです。状態をアクティビティに関連付けるのではなく、UI の状態を保存するで説明しているように、推奨される API を使用してアプリとユーザーの状態を保持します。
  • オプトアウト: ある種の構成変更に対するアクティビティの再作成を無効にするには、新しい構成に応じてアプリを適切に更新する必要があります。ほとんどの Compose アプリでは、この方法は推奨されません。

優れたユーザー エクスペリエンスを提供するために、次のベスト プラクティスを参考にしてください。

  • 頻繁な構成変更に備える: API レベル、フォーム ファクタ、UI ツールキットに関係なく、構成変更は滅多に行われないものであると思い込まないでください。ユーザーは構成を変更した際に、アプリも更新されて新たな構成で引き続き正常に動作するものと期待しています。
  • 状態を保持する: Activity の再作成時にユーザーの状態が失われないようにします。ViewModelrememberSaveable などの API を使用して、UI の状態を保存するで説明しているように状態を保持します。
  • 簡単な修正としてのオプトアウトを避ける:状態の喪失を避けるためのショートカットとして、Activity の再作成を無効にしないでください。アクティビティの再作成を無効にするには、変更を処理する promise を実行する必要があります。それでも依然として、他の構成変更、プロセスの終了、アプリの終了から生じる Activity の再作成により、状態が失われる可能性はあります。Activity の再作成を完全に無効にすることはできないのです。UI の状態を保存するで説明しているように、状態を保持します。
  • 構成の変更を回避しない: 構成の変更と Activity の再作成を回避する目的で、画面の向き、アスペクト比、サイズ変更の可能性を制限しないでください。これは、アプリを快適に使用したいと考えるユーザーに悪影響を及ぼします。

サイズベースの構成変更を処理する

サイズベースの構成変更は、いつでも発生する可能性があります。マルチウィンドウ モードに入れるような大画面のデバイスでアプリが動作している場合は、その傾向が強くなります。ユーザーは、そのような状況でもアプリが適切に動作することを期待しています。

一般的にサイズ変更には、大幅な変更と軽微な変更の 2 種類があります。大幅なサイズ変更とは、画面サイズ(幅、高さ、最小幅など)の違いにより、新しい構成に別の代替リソースセットが適用されるものです。これらのリソースには、アプリ自体が定義するものと、アプリのライブラリから発生したものが含まれています。

サイズベースの構成変更に関するアクティビティの再作成を制限する

サイズベースの構成変更に対して Activity の再作成を無効にすると、Activity は再作成されません。代わりに、Activity.onConfigurationChanged への呼び出しを受け取ります。LocalConfiguration.current を読み取るコンポーザブルは、新しいサイズを反映するために自動的に再コンポーズされます。

マニフェスト ファイルに android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" が含まれていると、サイズベースの構成変更に対する Activity の再作成が無効になります。

参考情報

構成変更の処理の詳細については、以下のリソースをご覧ください。

ドキュメント

コンテンツの閲覧