しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms の App Lifecycle に Windows RT 対応させたい

前回のセッションデータ復元で見事に失敗してしまったので、リベンジすべく Windows ランタイムアプリのライフサイクルに合わせて Xamarin.Forms App Lifecyle の各イベントハンドラが呼ばれるようにしたいと思います

まぁ、そのうち正式リリースなどで対応されると思いますので臨時対応的な手法ですが

スポンサードリンク

まずは Windows ランタイムの状態遷移のおさらい!

f:id:matatabi_ux:20150321183552p:plain

前回は中断とシャットダウンで OnSleep() が呼ばれていたので、Application.Suspending の対応はできていると思います

通常の起動時は OnStart() を呼び、Terminated からの起動時や Application.Resuming イベントで再開(Activated)された場合のみ OnResume() を呼べばよさそうですね

というわけで登場するのは

f:id:matatabi_ux:20150321190458p:plain

ご存じ Prism.PubSubEvents こと EventAggregator さんです! NuGet ソリューションパッケージの管理のメニューから一発インストール

インストールが終わったら Windows RT と Xamarin.Forms の間でやりとりするイベントを PCL の共通コード側に追加

f:id:matatabi_ux:20150321194405p:plain

中身はこんな感じ・・・スッカスカで大丈夫です

amespace XamarinSessionRestore.Events
{
    /// <summary>
    /// アプリケーション状態遷移の列挙値
    /// </summary>
    public enum ApplicationState
    {
        /// <summary>
        /// 起動中
        /// </summary>
        Starting = 0,

        /// <summary>
        /// 中断中
        /// </summary>
        Sleeping = 1,

        /// <summary>
        /// 再開中
        /// </summary>
        Resuming = 2,
    }

    /// <summary>
    /// アプリケーション状態遷移イベント
    /// </summary>
    public class ApplicationStateChangedEvent : PubSubEvent<ApplicationState>
    {
    }
}

さらに Xamarin.Forms 側の App.cs を修正します

/// <summary>
/// アプリケーション基盤クラス
/// </summary>
public class App : Application
{
    /// <summary>
    /// セッションデータリポジトリ
    /// </summary>
    private ISessionRepository sessionRepository = null;

    /// <summary>
    /// DI コンテナ
    /// </summary>
    public static UnityContainer Container = new UnityContainer();

    /// <summary>
    /// コンストラクタ
    /// </summary>
    static App()
    {
        // EventAggregatorを登録
        Container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());

        // アプリケーション状態遷移イベントを購読
        Container.Resolve<IEventAggregator>().GetEvent<ApplicationStateChangedEvent>().Subscribe(
            (e) =>
            {
                switch (e)
                {
                    // 起動時
                    case ApplicationState.Starting:
                        ((App)App.Current).OnStart();
                        break;

                    // 中断時
                    case ApplicationState.Sleeping:
                        // とりあえず何もしない
                        break;

                    // 再開時
                    case ApplicationState.Resuming:
                        ((App)App.Current).OnResume();
                        break;
                }
            });
    }

    ~ 中略 ~
}

ライフサイクル管理というメタアプリ的な処理が必要なので、static コンストラクタ内で EventAggregator の登録とイベントの購読設定をしています

イベントがどこかで発行されたらそれを受信して対応するアプリケーション状態遷移のイベントハンドラを強制的に呼んでるだけ

あとは Windows RT 側の App.xaml.cs を修正します(これは Windows ストアアプリと Windows Phone 8.1 でほぼ共通コード)

/// <summary>
/// 既定の Application クラスに対してアプリケーション独自の動作を実装します。
/// </summary>
public sealed partial class App : Application
{
    private TransitionCollection transitions;

    /// <summary>
    /// イベントアグリゲーター
    /// </summary>
    private IEventAggregator eventAggregator = null;

    /// <summary>
    /// 単一アプリケーション オブジェクトを初期化します。これは、実行される作成したコードの
    /// 最初の行であり、main() または WinMain() と論理的に等価です。
    /// </summary>
    public App()
    {
        this.InitializeComponent();
        this.eventAggregator = XamarinSessionRestore.App.Container.Resolve<IEventAggregator>();
        this.Suspending += OnSuspending;
        this.Resuming += (s, e) =>
        {
            // 再開した場合は Xamarin.Forms App Lifecycle に伝える
            this.eventAggregator.GetEvent<ApplicationStateChangedEvent>().Publish(ApplicationState.Resuming);
        };
        XamarinSessionRestore.App.Container.RegisterType<ISessionRepository, XmlRepositories>(new ContainerControlledLifetimeManager());
    }

    /// <summary>
    /// アプリケーションがエンド ユーザーによって正常に起動されたときに呼び出されます。他のエントリ ポイントは、
    /// アプリケーションが特定のファイルを開くために呼び出されたときに
    /// 検索結果やその他の情報を表示するために使用されます。
    /// </summary>
    /// <param name="e">起動要求とプロセスの詳細を表示します。</param>
    protected override void OnLaunched(LaunchActivatedEventArgs e)
    {
        ~ 中略 ~

        // 現在のウィンドウがアクティブであることを確認します
        Window.Current.Activate();

        // 起動したことを Xamarin.Forms App Lifecycle に伝える
        if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Terminated からの起動は再開扱い
            this.eventAggregator.GetEvent<ApplicationStateChangedEvent>().Publish(ApplicationState.Resuming);
        }
        else
        {
            this.eventAggregator.GetEvent<ApplicationStateChangedEvent>().Publish(ApplicationState.Starting);
        }
    }

    ~ 中略 ~
}

変えているのはコンストラクタで eventAggregator を解決して Application.Resuming イベント発生時に Resuming を通知していることと、Window.Current.Activate の後にアプリ起動時の状態遷移通知を分岐させているところです

LaunchActivatedEventArgs.PreviousExecutionState が Terminated だった場合は Terminated からの復帰なので、起動時でも Resuming を通知しています

さてこれでリベンジ実行

復元できてますね

App.OnStart: activated.
TopPage.xaml.OnAppearing: TopPage Appearing.
App.OnStart: Restore session data.
App.OnSleep: sleeped.
TopPage.xaml.OnAppearing: TopPage Appearing.
App.OnResume: Restore session data.
App.OnResume: resumed.

ちゃんと OnSleep 以外も呼ばれるようになりました