読者です 読者をやめる 読者になる 読者になる

しっぽを追いかけて

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

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

Xamarin でセッションデータを保存・復元したい 【Windows Phone 8.0 編】

前回の記事 で Application.Properties を利用すればある程度セッションデータを永続化できることはわかりました

ただし、Windows Phone の場合、起動時に復元はできなかったので別の方法を試してみます

用意するのはこれ

/// <summary>
/// XML リポジトリ
/// </summary>
public class XmlRepositories : ISessionRepository
{
    /// <summary>
    /// 保存先フォルダ
    /// </summary>
    private static readonly StorageFolder Folder = ApplicationData.Current.LocalFolder;

    /// <summary>
    /// データストア
    /// </summary>
    private Dictionary<Type, object> storage = new Dictionary<Type, object>();

    /// <summary>
    /// コンストラクタ
    /// </summary>
    static XmlRepositories()
    {
            
    }

    /// <summary>
    /// データを設定します
    /// </summary>
    /// <typeparam name="T">データの型</typeparam>
    /// <param name="value">データ</param>
    public void SetValue<T>(T value) where T : class
    {
        this.storage[typeof(T)] = value;
    }

    /// <summary>
    /// データを取得します
    /// </summary>
    /// <typeparam name="T">データの型</typeparam>
    /// <returns>データ</returns>
    public T GetValue<T>() where T : class
    {
        if (!this.storage.ContainsKey(typeof(T)))
        {
            return null;
        }
        return this.storage[typeof(T)] as T;
    }

    /// <summary>
    /// 初期化します
    /// </summary>
    /// <returns>成功した場合<code>true</code>、それ以外は<code>false</code></returns>
    public bool Initilize()
    {
        try
        {
            this.storage.Clear();
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

    /// <summary>
    /// セッションデータを読み込みます
    /// </summary>
    /// <returns>成功した場合<code>true</code>、それ以外は<code>false</code></returns>
    public async Task<bool> LoadAsync()
    {
        var result = true;

        try
        {
            var types = new List<string>();
            var indexFile = await Folder.GetItemAsync("session-keys.xml");

            using (var stream = new StreamReader(indexFile.Path))
            {
                var serializer = new XmlSerializer(types.GetType());
                types = serializer.Deserialize(stream) as List<string>;
            }
            foreach (var typename in types)
            {
                this.storage[Type.GetType(typename)] = null;
            }
        }
        catch (Exception)
        {
        }

        foreach (var data in this.storage.ToList())
        {
            try
            {
                var file = await Folder.GetItemAsync(string.Format("{0}.xml", data.Key.Name));
                using (var stream = new StreamReader(file.Path))
                {
                    var serializer = new XmlSerializer(data.Key);
                    this.storage[data.Key] = serializer.Deserialize(stream);
                }

            }
            catch (FileNotFoundException)
            {
                this.storage[data.Key] = null;
                continue;
            }
            catch (InvalidOperationException)
            {
                this.storage[data.Key] = null;
                continue;
            }
            catch (Exception)
            {
                result = false;
            }
        }

        return result;
    }

    /// <summary>
    /// セッションデータを保存します
    /// </summary>
    /// <returns>成功した場合<code>true</code>、それ以外は<code>false</code></returns>
    public async Task<bool> SaveAsync()
    {
        var result = true;

        try
        {
            var types = (from t in this.storage
                            select t.Key.AssemblyQualifiedName).ToList();
            var indexFile = await Folder.CreateFileAsync("session-keys.xml", CreationCollisionOption.ReplaceExisting);
            using (var stream = new StreamWriter(indexFile.Path))
            {
                var serializer = new XmlSerializer(types.GetType());
                serializer.Serialize(stream, types);
            }
        }
        catch (Exception)
        {
        }

        foreach (var data in this.storage)
        {
            try
            {
                var file = await Folder.CreateFileAsync(string.Format("{0}.xml", data.Key.Name), CreationCollisionOption.ReplaceExisting);
                using (var stream = new StreamWriter(file.Path))
                {
                    var serializer = new XmlSerializer(data.Key);
                    serializer.Serialize(stream, data.Value);
                }
            }
            catch (FileNotFoundException)
            {
                continue;
            }
            catch (InvalidOperationException)
            {
                continue;
            }
            catch (Exception)
            {
                result = false;
            }
        }

        return result;
    }
}

Xamarin でも XmlSerializer が使えるみたいなので、キーとなる値のリストと、それぞれのキーに格納されたインスタンスごとに XML ファイルにシリアライズして保存するようにしてみました

復元する場合は反対の処理を書けばいいというわけです

あとは App.cs に記述していた

/// <summary>
/// コンストラクタ
/// </summary>
public App()
{
    this.MainPage = new TopPage();

    Container.RegisterType<ISessionRepository, ApplicationProperties>(new ContainerControlledLifetimeManager());
    this.sessionRepository = Container.Resolve<ISessionRepository>();
    this.sessionRepository.Initilize();
}

ApplicationProperties の登録コードを削除して Windows Phone の App.xaml.cs の方に

/// <summary>
/// Constructor for the Application object.
/// </summary>
public App()
{
    // Global handler for uncaught exceptions.
    UnhandledException += Application_UnhandledException;

    XamarinSessionRestore.App.Container.RegisterType<ISessionRepository, XmlRepositories>(new ContainerControlledLifetimeManager());

    // Standard XAML initialization
    InitializeComponent();

    // Phone-specific initialization
    InitializePhoneApplication();

    // Language display initialization
    InitializeLanguage();

    // Show graphics profiling information while debugging.
    if (Debugger.IsAttached)
    {
        // Display the current frame rate counters.
        Application.Current.Host.Settings.EnableFrameRateCounter = true;

        // Show the areas of the app that are being redrawn in each frame.
        //Application.Current.Host.Settings.EnableRedrawRegions = true;

        // Enable non-production analysis visualization mode,
        // which shows areas of a page that are handed off to GPU with a colored overlay.
        //Application.Current.Host.Settings.EnableCacheVisualization = true;

        // Prevent the screen from turning off while under the debugger by disabling
        // the application's idle detection.
        // Caution:- Use this under debug mode only. Application that disables user idle detection will continue to run
        // and consume battery power when the user is not using the phone.
        PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
    }

}

XmlRepositories の登録コードを追記するだけで移行完了

前回と同じように実行してみます・・・

ログの出力はこう変わりました

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

ちゃんと保存できてますね

Dictionary 型など一部のデータは別の型に移し替えて保存・復元する必要がありますが、普通に永続化できそうです