mcpField
M
maui-app-lifecycle

maui-app-lifecycle

github.com/dotnet開発5.8k3.4k 回インストール1 日前に更新
リポジトリ

maui-app-lifecycle について

.NET MAUI App Lifecycle は、.NET MAUI 開発者向けにウィンドウ状態の変化とクロスプラットフォームのライフサイクルイベントを解説するスキルです。ネイティブコールバックとの対応や、バックグラウンド移行・復帰時の状態保存方法を扱います。

主な機能

  1. 016 種類のウィンドウライフサイクルイベントの解説
  2. 02Android、iOS、Mac Catalyst、Windows のコールバック対応
  3. 03バックグラウンド時の状態保存と復元
  4. 04イベント登録とメソッドオーバーライドの例
  5. 05ConfigureLifecycleEvents の使い方
  6. 06マルチウィンドウ動作と一般的な注意点

活用シーン

  1. 01アプリのバックグラウンド移行時に下書きやフォームを保存
  2. 02復帰時に UI を更新、または状態を復元
  3. 03プラットフォーム固有のライフサイクルコールバックを利用
  4. 04フォーカス喪失とバックグラウンド移行を区別

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dotnet/skills --skill maui-app-lifecycle

インストール後、Claude Code で/maui-app-lifecycleを入力して読み込まれたか確認します。

同梱ファイルreferences/lifecycle-api.md

詳細

.NET MAUI アプリのライフサイクル

.NET MAUI でアプリの状態遷移を正しく処理します。このスキルでは、クロスプラットフォームの Window ライフサイクルイベントとプラットフォームネイティブの対応関係、およびバックグラウンド移行と再開のサイクルを通じて状態を保持するパターンについて説明します。

使用する場面

  • アプリがバックグラウンドに移行または再開するときに状態を保存、復元する
  • Window ライフサイクルイベント(Created、Activated、Deactivated、Stopped、Resumed、Destroying)をサブスクライブする
  • ConfigureLifecycleEvents を介してプラットフォームネイティブのライフサイクルコールバックにフックする
  • 初期化、後処理、更新のロジックを配置する場所を判断する
  • Deactivated と Stopped の違いを理解する

使用しない場面

  • ページレベルのナビゲーションイベント — 代わりに Shell ナビゲーションのガイダンスを使用します
  • 起動時のサービス登録 — 代わりに依存関係注入のガイダンスを使用します
  • ライフサイクルのコンテキスト外でプラットフォーム固有の API を呼び出す — 代わりにプラットフォーム呼び出しのガイダンスを使用します

入力

  • 対象となるライフサイクル遷移(例:「バックグラウンド移行時に下書きを保存する」「再開時にデータを更新する」)
  • 開発者が対象とするプラットフォーム(Android、iOS、Mac Catalyst、Windows)
  • アプリが複数ウィンドウを使用するかどうか(iPad、Mac Catalyst、デスクトップ Windows)

アプリの状態

.NET MAUI アプリは 4 つの状態を遷移します。

状態説明
未実行プロセスが存在しません
実行中フォアグラウンドで入力を受け付けています
非アクティブ表示されていますが、フォーカスを失っています(ダイアログ、画面分割、通知シェード)
停止完全にバックグラウンドに移行し、UI は表示されていません

一般的な流れ:未実行 → 実行中 → 非アクティブ → 停止 → 実行中(再開)または未実行(終了)。

Window ライフサイクルイベント

Microsoft.Maui.Controls.Window は 6 つのクロスプラットフォームイベントを公開します。

イベント発生するタイミング
Createdネイティブウィンドウが割り当てられたとき
Activatedウィンドウが入力フォーカスを受け取ったとき
Deactivatedウィンドウがフォーカスを失ったとき(表示されたままの場合があります)
Stoppedウィンドウが表示されなくなったとき
ResumedStopped の後、ウィンドウがフォアグラウンドに戻ったとき
Destroyingネイティブウィンドウが破棄されるとき
CreateWindow を介したサブスクライブ

App クラスで CreateWindow をオーバーライドし、イベントハンドラーを追加します。

public partial class App : Application
{
    protected override Window CreateWindow(IActivationState? activationState)
    {
        var window = base.CreateWindow(activationState);

        window.Created += (s, e) => Debug.WriteLine("Created");
        window.Activated += (s, e) => Debug.WriteLine("Activated");
        window.Deactivated += (s, e) => Debug.WriteLine("Deactivated");
        window.Stopped += (s, e) => Debug.WriteLine("Stopped");
        window.Resumed += (s, e) => Debug.WriteLine("Resumed");
        window.Destroying += (s, e) => Debug.WriteLine("Destroying");

        return window;
    }
}
カスタム Window サブクラスを介したサブスクライブ

Window のサブクラスを作成し、仮想メソッドをオーバーライドします。

public class AppWindow : Window
{
    public AppWindow(Page page) : base(page) { }

    protected override void OnActivated() { /* refresh UI */ }
    protected override void OnStopped() { /* save state */ }
    protected override void OnResumed() { /* restore state */ }
    protected override void OnDestroying() { /* cleanup */ }
}

CreateWindow から返します。

protected override Window CreateWindow(IActivationState? activationState)
    => new AppWindow(new AppShell());

ワークフロー:バックグラウンド移行時に状態を保存し、復元する

  1. 一時的な状態を特定する — 下書きテキスト、スクロール位置、フォーム入力、タイマーの値などです。
  2. OnStopped で保存する — 小さな値には Preferences を、大きな状態にはファイルへのシリアル化を使用します。
  3. OnResumed で復元する — 保存した値を読み込み、ビューモデルに適用します。
  4. Android では OnDestroying でも保存する — 戻るボタンを押すと、Stopped が発生しない場合があります。
  5. ハンドラーの処理を短く保つ — Android での ANR や iOS でのウォッチドッグによる強制終了を避けるため、1~2 秒以内に完了させます。
protected override void OnStopped()
{
    base.OnStopped();
    Preferences.Set("draft_text", _viewModel.DraftText);
    Preferences.Set("scroll_y", _viewModel.ScrollY);
}

protected override void OnResumed()
{
    base.OnResumed();
    _viewModel.DraftText = Preferences.Get("draft_text", string.Empty);
    _viewModel.ScrollY = Preferences.Get("scroll_y", 0.0);
}

protected override void OnDestroying()
{
    base.OnDestroying();
    // Android back-button can skip Stopped
    Preferences.Set("draft_text", _viewModel.DraftText);
}

プラットフォームのライフサイクル対応関係

Android
Window イベントAndroid コールバック
CreatedOnCreate
ActivatedOnResume
DeactivatedOnPause
StoppedOnStop
ResumedOnRestart → OnStart → OnResume
DestroyingOnDestroy
iOS / Mac Catalyst
Window イベントUIKit コールバックAddiOS ビルダーメソッド
CreatedWillFinishLaunching / SceneWillConnect.WillFinishLaunching() / .SceneWillConnect()
ActivatedDidBecomeActive.OnActivated()
DeactivatedWillResignActive.OnResignActivation()
StoppedDidEnterBackground.DidEnterBackground()
ResumedWillEnterForeground.WillEnterForeground()
DestroyingWillTerminate.WillTerminate()

⚠️ UIKit のセレクター名と AddiOS ビルダーメソッド名は、アクティブ化に関して異なります。 .DidBecomeActive() または .WillResignActive() というビルダーメソッドはありません。.OnActivated() と .OnResignActivation() を使用してください。そうしないと、コードはコンパイルできません。

Windows(WinUI)
ウィンドウイベントWinUI コールバック
作成OnLaunched
アクティブ化Activated(フォアグラウンド)
非アクティブ化Activated(バックグラウンド)
停止VisibilityChanged(false)
再開VisibilityChanged(true)
破棄Closed

ネイティブのライフサイクルを直接フックする

Window イベントで提供されるもの以外にプラットフォーム固有のコールバックが必要な場合は、MauiProgram.cs で ConfigureLifecycleEvents を使用します。

builder.ConfigureLifecycleEvents(events =>
{
#if ANDROID
    events.AddAndroid(android => android
        .OnCreate((activity, bundle) => Debug.WriteLine("Android OnCreate"))
        .OnResume(activity => Debug.WriteLine("Android OnResume"))
        .OnPause(activity => Debug.WriteLine("Android OnPause"))
        .OnStop(activity => Debug.WriteLine("Android OnStop"))
        .OnDestroy(activity => Debug.WriteLine("Android OnDestroy")));
#elif IOS || MACCATALYST
    events.AddiOS(ios => ios
        .OnActivated(app => Debug.WriteLine("iOS OnActivated"))
        .OnResignActivation(app => Debug.WriteLine("iOS OnResignActivation"))
        .DidEnterBackground(app => Debug.WriteLine("iOS DidEnterBackground"))
        .WillEnterForeground(app => Debug.WriteLine("iOS WillEnterForeground")));
#elif WINDOWS
    events.AddWindows(windows => windows
        .OnLaunched((app, args) => Debug.WriteLine("Windows OnLaunched"))
        .OnActivated((window, args) => Debug.WriteLine("Windows Activated"))
        .OnClosed((window, args) => Debug.WriteLine("Windows Closed")));
#endif
});

よくある落とし穴

  1. 初回起動時に Resumed は呼び出されません。 初回のシーケンスは Created → Activated です。フォアグラウンドに入るたびに実行する必要があるロジックには、OnResumed ではなく OnActivated を使用します。

  2. Deactivated と Stopped は異なります。 ダイアログ、画面分割、通知パネルの引き下げでは、Stopped を伴わずに Deactivated が発生します。アプリが実際にバックグラウンドに移行するとは限らないため、OnDeactivated で負荷の高い保存処理を行わないでください。

  3. Android の戻るボタンでは Stopped がスキップされます。 Android では、戻るボタンを押すと Stopped を経ずに Destroying が直接呼び出されることがあります。重要な保存ロジックは OnStopped と OnDestroying の両方に配置してください。

  4. マルチウィンドウアプリではイベントが個別に発生します。 iPad、Mac Catalyst、デスクトップ Windows では、それぞれの Window インスタンスが独自のライフサイクルイベントを発生させます。グローバルなライフサイクルが 1 つだけだと想定しないでください。

  5. 処理時間の長いハンドラーはアプリの強制終了につながります。 Android では約 5 秒の ANR タイムアウトが適用され、iOS ではバックグラウンド実行時間が制限されています。ライフサイクルハンドラーは同期的かつ高速に保ち、すばやく保存するにはデータベースへの書き込みではなく Preferences を使用します。

  6. レガシーな Xamarin.Forms のライフサイクルメソッドは使用しないでください。 Application.OnStart()、Application.OnSleep()、Application.OnResume() は後方互換性のために存在しますが、Window レベルのイベントを迂回します。.NET MAUI では、マルチウィンドウで正しく動作させるために、Window のライフサイクルイベント(OnActivated、OnStopped、OnResumed など)を優先してください。

コメント