かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

アプリケーションデータの保存場所について考える

Win8RP + VS2012RC時点の情報です

ここを見ながら整理整頓。

Metro スタイル アプリのデータの保存について少し考えてみました。まぁWindows Phone 7アプリでも同じようなことは考えないといけないのですが、ここらへんWindows Phoneアプリ開発のときにさぼってたので今考えます。

一時的に保持するデータとずっと保持するデータ

上記のページでは、ユーザーデータとセッションデータと言う表現を使ってましたが、ユーザーデータというのは、アプリケーションでず〜〜〜っと永続化しておきたいデータで、セッションデータが短期的に保持しておきたいデータっぽいですね。

そして方針としては以下のような方針で保存するのがよさそうです。

ユーザーデータ

Windows.Storage.ApplicationData.Current.LocalSettingsで取得できるApplicationDataContainer型。
この型は、基本的にはちょっとリッチなDictionaryみたいなものでValuesプロパティで以下のようにデータを入れたり出したりできます。

var settings = ApplicationData.Current.LocalSettings;
settings.Values["key"] = "value";

var value = (string) settings.Values["key"];

ただ、このValuesプロパティには基本的な型しか代入できません。間違って自作クラスを入れようとすると例外が出てしまいます…。ということで、階層的なデータを保存しようと思ったらちょっとひと手間かけないといけません。

ApplicationDataContainer型からApplicationDataContainer型を取得するためのContainersプロパティがあります。これはIReadOnlyDictionary型になってます。ReadOnlyということは、新たにアイテムを追加することが出来ないので注意が必要です。なので、新たにApplicationDataContainerのContainersに新しいApplicationDataContainerを作るにはCreateContainerメソッドを使います。

var settings = ApplicationData.Current.LocalSettings;
var newContainer = settings.CreateContainer("key", ApplicationDataCreateDisposition.Always);
newContainer.Values["key"] = "value";

CreateContainerメソッドの第一引数がキーで第二引数がどのようにコンテナ作るかというのを指定します。AlwaysとExistingを指定できて、Alwaysが無かったら作って、あればそれを返す。Existingが、無かったら例外で、あればそれを返すような感じです。あんまり使わないかな…?

ということで、ApplicationDataContainerにApplicationDataContainerを入れ子にしていくことで、複雑なデータ構造でも表現できるようになってます。

セッションデータ

セッションデータは、より短期間なデータを表す感じです。具体的にいうと、OnLaunchedでPreviousExecutionStateがApplicationExecutionState.Terminatedになったときにだけ復元したいデータです。ユーザーから見てどういう状態かというと、Metro スタイル アプリが背後に回った後にメモリが圧迫されるなどの要因で、OSによって終了させられた時に、再度前面にアプリを表示させたときです。
ユーザーから見たら、アプリは終了したんじゃなくて、前回の続きを表示してほしいケースですね。この状態に対応するためのデータの保存場所ということになります。

グローバルなデータはアプリケーションのCommonフォルダにあるSuspensionManagerクラスで管理することが出来ます。SuspensionManager.SessionStateを使ってDictionaryで管理できます。保存と復元については、空のアプリケーション以外でアプリケーションを作るとApp.xaml.csに適切なコードが埋め込まれてます。

protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
    // 実行中ならアクティブ化するだけで終わり
    if (args.PreviousExecutionState == ApplicationExecutionState.Running)
    {
        Window.Current.Activate();
        return;
    }

    // Frame内で遷移するページ単位のセッションデータを管理できるようにするおまじない
    var rootFrame = new Frame();
    SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

    if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
    {
        // OSによって終了させられた時にはデータの復元
        await SuspensionManager.RestoreAsync();
    }

    if (rootFrame.Content == null)
    {
        // このとき、必要な情報をナビゲーション パラメーターとして渡して、新しいページを
        // を構成します
        if (!rootFrame.Navigate(typeof(MainPage)))
        {
            throw new Exception("Failed to create initial page");
        }
    }

    // フレームを現在のウィンドウに配置し、アクティブであることを確認します
    Window.Current.Content = rootFrame;
    Window.Current.Activate();
}

グローバルじゃなくてページ単位のセッションデータを保持したいというときにはLayoutAwarePageクラスを継承してたら、LoadState, SaveStateメソッドのpageState引数のDictionaryにデータを突っ込めばOKです。気軽ですね。

LoadStateメソッドの時は、セッションデータがそもそも無いときはpageState引数がnullになることがあるので、そこは注意しましょう。

まとめ

ユーザーデータはApplicationData.Current.LocalSettingsで取得できるApplicationDataContainer型に保存するのが第一候補。

セッションデータは、アプリケーションのテンプレートにあるSuspensionManagerクラスを使うのが第一候補。

こいつらで要件を満たせないときは自分でどうにかしましょう(カスタムファイルに保存するとかetc…)ということでした。