Windows ストアアプリの中断処理は必ず呼ばれるとは限らない?
調査中ですが、一応メモ
Windows ストアアプリのライフサイクルを見るとアプリケーションを閉じる場合の挙動が以下のように書かれています。
一時停止された後に終了され、10 秒ほどで NotRunning 状態になります。アプリに Suspending | suspending イベントのイベント ハンドラーが登録されている場合は、アプリが一時停止されるとこのイベント ハンドラーが呼び出されます。このイベント ハンドラーを使って、関連するアプリ データとユーザー データを固定記憶域に保存できます。
Windows ストアアプリのプロジェクトテンプレートでは、App.xaml.cs に上述の、"Suspending | suspending イベントのイベント ハンドラー" が登録されており、例えば以下のような方法でアプリの中断処理においてアプリケーションのデータを保存することができます。
private async void OnSuspending(object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); await SuspensionManager.SaveAsync(); //アプリケーションデータの保存用ファイルを作成 var storageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync( "appData.xml", CreationCollisionOption.ReplaceExisting); //ファイルを開いて保存処理 using (var stream = await storageFile.OpenStreamForWriteAsync()) { //streamにデータを書き込む } deferral.Complete(); }
ところが実際に自分の環境で試してみたところ、VS で強制的に中断した場合は上記のハンドラが即時に呼び出されるのですが、手動でアプリケーションを終了した場合(上の方を掴んでぐいっと終了)した場合、上記ハンドラが呼ばれるまでに終了してから~10秒程のタイムラグがあります。
つまり、"一時停止された後に終了され、10 秒ほどで NotRunning 状態になります"ではなく、「10 秒ほどで一時停止された後に終了され、 NotRunning 状態になります」のような挙動なのです。
さらに困ったことに、手動でアプリケーションを終了した後に、即座に再度同じアプリケーションを起動する、つまり中断イベントのハンドラが呼び出される前にアプリケーションを再起動すると、終了したアプリケーションのインスタンスの中断イベントのハンドラは一度も呼び出されずに、新しいアプリケーションのインスタンスが起動するような挙動になり、終了した際のアプリケーションのデータが保存されませんでした。
最初は開発環境をVMで実行しているせいかなぁとか思ったんですが、以下ようにこの問題が話題に上っているスレッドもあるところを見るとこういう仕様なのかな?
Saving the application data to file in the Application.OnSuspending method does not work?
もし本当にこの挙動だとしたらアプリケーションのデータを確実に保存するには、中断イベントには依存できないって事になりますね。うーん、とりあえず実機がほしい。
追記(2013/01/17)
よく調べずに書いてしまったのですが、教えて頂きましたので、まだ学習不足のところもありますが追記します。また、あわせて教えて頂いたこちらのチュートリアルが非常に参考になりました。
Windows ストアアプリは、起動時にLaunchActivatedEventArgsが持つPreviousExecutionStateを確認することで、前回このアプリケーションがどのように終了したのかを確認することができます。
PreviousExecutionStateの値はApplicationExecutionState Enumerationですが、このトピックに説明があるように、"ユーザーが使用していた状態に復元する"必要がある(セッション データを復元する必要がある)のは、PreviousExecutionStateがTerminatedの場合(ユーザーの意志ではなく、Windowsによってアプリケーションが中断された場合)になります。
私が行った上の方を掴んでぐいっと終了や[Alt] + [F4]などのようにユーザーの意志によって、アプリケーションが終了された場合は、PreviousExecutionStateがClosedByUserとなり次回起動時には、"最初の UI を表示し、以前の状態に戻さずに初期化タスクを実行します"(ただし次回起動が10秒以内の場合、PreviousExecutionStateはNotRunningになります ※後述)。
つまりユーザーが明示的にセッションを閉じたかどうかで(アプリケーション起動時に前回のセッションを継続すべきか否かで)、データを復元する必要があるかどうかを判断します。
また、復元するデータについても、セッション データとアプリ データの定義を明確にしておく必要があります。
セッション データは、ユーザーがアプリケーションを使う1セッション内でのみ保持すべきデータであり、OnSuspendingにおいて保存し、PreviousExecutionStateがTerminatedの場合に復元します。
アプリ データは、セッションをまたいでアプリケーションがユーザーに常に提供すべきデータであり、保存はデータに変更があったタイミングで即時に行い(OnSuspendingでは行わない)、通常、毎起動ごとに復元を行います(データの特性によっては初回起動など除外すべきケースもあるとは思いますが)。
チュートリアルをやってみてだいぶすっきりしたのですが、まだ気持ち悪いのは、ユーザーが明示的にアプリケーションを終了した場合、次の再起動が10秒以内かどうかで挙動がかわる点です。
10秒以内にアプリケーションを再起動した場合、OnSuspendingが実行されずに(?)アプリケーションが終了し、PreviousExecutionStateがNotRunningでアプリケーションが起動します。
10秒以上経過してからアプリケーションを再起動した場合、OnSuspendingが実行されてアプリケーションが終了し、PreviousExecutionStateがClosedByUserでアプリケーションが起動します。
いずれにせよアプリケーションの状態の復元としては、PreviousExecutionStateがTerminatedではないので、セッション データは廃棄しアプリ データは(必要に応じて)復元するということなので、結果は同じです。ただ10秒以内の再起動だとOnSuspendingが実行されない(?)というのが、それは中断処理がスキップされるということを意味しているのか、いまいちピンときません。10秒以内の再起動は「アプリケーションをリセットする」といったような特殊な処理ととらえるべきなのかもしれませんが、この辺はまた何かわかれば追記したいです。