UWP开发之Mvvmlight实践五:SuspensionManager中断挂起以及复原处理

最近比较忙有一段时间没有更新了,再接再厉继续分享。

先我们看看App在生命周期中会出现那些状态:

详细介绍参考官网:App lifecycle  https://msdn.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle

一般情况:

比如用新闻APP看新闻的时候突然收到邮件,然后跳转到邮件APP查看邮件,查看完了再回到APP继续看新闻。

这个时候如果不做中断挂起处理的话,是很难保证APP会恢复到跳转之前的状态。之所以说很难保证是因为如果手机内存够大够用系统就不会关闭没用的app让它驻留在内存中,下次直接从内存恢复,这样可以恢复到跳转之前的状态。如果内存不够用系统会关闭app回收资源,这个时候没有中断保存进度处理再次启动的时候就会从新开始,无法恢复到跳转前的状态。

正是为了这样的人性化处理才有中断恢复处理的必要性。

中断复原的原理:

数据保存时机:从Running--->Suspended的时候触发Suspending事件做画面进度保存处理,

数据恢复时机:如果内存没回收直接触发Resuming事件不需要做任何事情,如果回收的情况下在调用app的OnLaunched中通过判断Terminated状态做数据恢复处理。

中断复原实现:(MVVM实现方式以后待续)

在Win8.1开发StoreApp的时候默认会在项目中添加SuspensionManager.cs文件并且配置好,然而在UWP开发中模板没有自带这个文件,为什么?难道还有其他处理方法?

在网上找了下没找到原因,如果有知道欢迎介绍。虽然没找到结果无意发现了一个很好的开源框架:Template10,UWP很多常用的开发技巧都封装好了,减少不少工作量。Template10详细介绍参考:https://github.com/Windows-XAML/Template10/wiki

那我们的中断处理就有两种方法:

  • SuspensionManager.cs形式
  • Template10 插件

第一种SuspensionManager.cs形式实现:

找到SuspensionManager.cs文件,在微软的UWP例子里头有:https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/ApplicationData/cs/SuspensionManager.cs。把这个文件趴下来或者新建一个win8.1的app从它里面拷贝过来,别忘记改命名空间。

在App.xaml.cs文件确认OnSuspending事件是否注册了。

public App()
        {
            Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync(
                Microsoft.ApplicationInsights.WindowsCollectors.Metadata |
                Microsoft.ApplicationInsights.WindowsCollectors.Session);
            this.InitializeComponent();
            this.Suspending += OnSuspending;
        }

在App.xaml.cs中找到OnSuspending事件添加中断保存处理SuspensionManager.SaveAsync()。由于是非同期操作事件名前记得加上async。

/// <summary>
        /// 在将要挂起应用程序执行时调用。  在不知道应用程序
        /// 无需知道应用程序会被终止还是会恢复,
        /// 并让内存内容保持不变。
        /// </summary>
        /// <param name="sender">挂起的请求的源。</param>
        /// <param name="e">有关挂起请求的详细信息。</param>
        private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            // 保存应用状态
            await SuspensionManager.SaveAsync();
            deferral.Complete();
        }

在App.xaml.cs中添加恢复处理。默认OnLaunched方法中是已经留好位置的,找到【//TODO: 从之前挂起的应用程序加载状态】替换为SuspensionManager.RestoreAsync()。以及关联设置SuspensionManager.RegisterFrame(rootFrame, "AppFrame")。由于是非同期操作事件名前记得加上async。

/// <summary>
        /// 在应用程序由最终用户正常启动时进行调用。
        /// 将在启动应用程序以打开特定文件等情况下使用。
        /// </summary>
        /// <param name="e">有关启动请求和过程的详细信息。</param>
        protected async override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = false;
            }
#endif
            Frame rootFrame = Window.Current.Content as Frame;

            // 不要在窗口已包含内容时重复应用程序初始化,
            // 只需确保窗口处于活动状态
            if (rootFrame == null)
            {
                // 创建要充当导航上下文的框架,并导航到第一页
                rootFrame = new Frame();

                //将框架与 SuspensionManager 键关联
                SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

                rootFrame.NavigationFailed += OnNavigationFailed;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    // 数据恢复
                    await SuspensionManager.RestoreAsync();
                }

                // 将框架放在当前窗口中
                Window.Current.Content = rootFrame;
            }

            if (e.PrelaunchActivated == false)
            {
                if (rootFrame.Content == null)
                {
                    // 当导航堆栈尚未还原时,导航到第一页,
                    // 并通过将所需信息作为导航参数传入来配置
                    // 参数
                    rootFrame.Navigate(typeof(MainPage), e.Arguments);
                }
                // 确保当前窗口处于活动状态
                Window.Current.Activate();
            }
        }

这样简单的中断挂机以及恢复处理就配置完成了。

简单使用,在MainPage.xaml.cs里面重载OnNavigatedFrom和OnNavigatedTo方法。

/// <summary>
        /// 保存数据
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);
            var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
            var _pageKey = "Page-" + this.GetType().ToString();
            var pageState = new Dictionary<String, Object>();

            pageState[nameof(txtInput)] = txtInput.Text;

            frameState[_pageKey] = pageState;
        }
        /// <summary>
        /// 恢复数据
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (e.NavigationMode == NavigationMode.New)
            {

            }
            else
            {
                var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
                var _pageKey = "Page-" + this.GetType().ToString();

                var data = (Dictionary<String, Object>)frameState[_pageKey];
                if (data != null && data.ContainsKey(nameof(txtInput)))
                {
                    txtInput.Text = data[nameof(txtInput)].ToString();
                }
            }
        }

SuspensionManager.SaveAsync()代码如下:

主要原理—>通过遍历每个Frame调用frame.GetNavigationState()方法收集画面数据到_sessionState,然后将_sessionState字典数据序列化保存到LocalState下的_sessionState.xml文件中。

/// <summary>
        /// 保存当前 <see cref="SessionState"/>。  任何 <see cref="Frame"/> 实例
        /// (已向 <see cref="RegisterFrame"/> 注册)都还将保留其当前的
        /// 导航堆栈,从而使其活动 <see cref="Page"/> 可以
        /// 保存其状态。
        /// </summary>
        /// <returns>反映会话状态保存时间的异步任务。</returns>
        public static async Task SaveAsync()
        {
            try
            {
                // 保存所有已注册框架的导航状态
                foreach (var weakFrameReference in _registeredFrames)
                {
                    Frame frame;
                    if (weakFrameReference.TryGetTarget(out frame))
                    {
                        SaveFrameNavigationState(frame);
                    }
                }

                // 以同步方式序列化会话状态以避免对共享
                // 状态
                MemoryStream sessionData = new MemoryStream();
                DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
                serializer.WriteObject(sessionData, _sessionState);

                // 获取 SessionState 文件的输出流并以异步方式写入状态
                StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(sessionStateFilename, CreationCollisionOption.ReplaceExisting);
                using (Stream fileStream = await file.OpenStreamForWriteAsync())
                {
                    sessionData.Seek(0, SeekOrigin.Begin);
                    await sessionData.CopyToAsync(fileStream);
                }
            }
            catch (Exception e)
            {
                throw new SuspensionManagerException(e);
            }
        }

SuspensionManager.RestoreAsync()代码如下:

主要原理—>读取LocalState下_sessionState.xml文件的内容反序列化保存到_sessionState字典中。然后调用frame.SetNavigationState((String)frameState["Navigation"])重新加载数据。

/// <summary>
        /// 还原之前保存的 <see cref="SessionState"/>。  任何 <see cref="Frame"/> 实例
        /// (已向 <see cref="RegisterFrame"/> 注册)都还将还原其先前的导航
        /// 状态,从而使其活动 <see cref="Page"/> 可以还原其
        /// 状态。
        /// </summary>
        /// <param name="sessionBaseKey">标识会话类型的可选密钥。
        /// 这可用于区分多个应用程序启动方案。</param>
        /// <returns>反映何时读取会话状态的异步任务。
        /// 在此任务完成之前,不应依赖 <see cref="SessionState"/>
        /// 完成。</returns>
        public static async Task RestoreAsync(String sessionBaseKey = null)
        {
            _sessionState = new Dictionary<String, Object>();

            try
            {
                // 获取 SessionState 文件的输入流
                StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename);
                using (IInputStream inStream = await file.OpenSequentialReadAsync())
                {
                    // 反序列化会话状态
                    DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
                    _sessionState = (Dictionary<string, object>)serializer.ReadObject(inStream.AsStreamForRead());
                }

                // 将任何已注册框架还原为其已保存状态
                foreach (var weakFrameReference in _registeredFrames)
                {
                    Frame frame;
                    if (weakFrameReference.TryGetTarget(out frame) && (string)frame.GetValue(FrameSessionBaseKeyProperty) == sessionBaseKey)
                    {
                        frame.ClearValue(FrameSessionStateProperty);
                        RestoreFrameNavigationState(frame);
                    }
                }
            }
            catch (Exception e)
            {
                throw new SuspensionManagerException(e);
            }
        }

★做序列化和反序列化的时候用到了_knownTypes,这个是重点。如果要对自定义类型,列表数据做中断保存处理时需要添加自定义类型的Type到_knownTypes,否则序列化会失败。

_knownTypes详细使用后续章节待续…

时间: 2024-10-12 08:08:46

UWP开发之Mvvmlight实践五:SuspensionManager中断挂起以及复原处理的相关文章

UWP开发之Mvvmlight实践七:寻找Mobile模拟器或者实体机中Packages文件夹小技巧

在开发中或者后期测试乃至最后交付使用的时候,如果应用出问题了我们一般的做法就是查看Log文件.上章也提到了查看Log文件,这章重点讲解下如何查看Log文件?如何找到我们需要的Packages安装包目录? 桌面系统应用 安装在桌面系统上的应用,我们是很容易就可以找到安装包的目录. 目录结构:C:\Users\{用户名}\AppData\Local\Packages\{UWP应用包名}\ {用户名}=系统登录用户名,{UWP应用包名}=UWP应用打包时的名称. 比如查看[UWP开发之Mvvmligh

UWP开发之Mvvmlight实践九:基于MVVM的项目架构分享

在前几章介绍了不少MVVM以及Mvvmlight实例,那实际企业开发中将以那种架构开发比较好?怎样分层开发才能节省成本? 本文特别分享实际企业项目开发中使用过的项目架构,欢迎参照使用!有不好的地方欢迎指点! 基于MVVM的UWP项目架构 每个项目或者目录下需要什么文件如下图所示: 项目参照关系: 备注:如果使用Entity Framework Core做ORM持久层,只需要将UA.DataAccess层做替换就可以.希望本文能对您的团队开发带来一定功效.

UWP开发之Mvvmlight实践六:MissingMetadataException解决办法(.Net Native下Default.rd.xml配置问题)

最近完成一款UWP应用,在手机端测试发布版(Release)的时候应用莫名奇妙的强行关闭,而同样的应用包在PC端一点问题都没有,而且Debug版在两个平台都没有问题,唯独手机的Release版有问题.实在没办法只能记录每个步骤的Log,通过查看Log发现是SuspensionManager的DataContractSerializer序列化抛出了ArgumentNullException异常. 常见.NET Native引发异常: 例1: System.InvalidCastException:

UWP开发之Mvvmlight实践四:{x:bind}和{Binding}区别详解

{x:bind}是随着UWP被推出而被添加的,可以说是Win10 UWP开发专有扩展.虽然 {x:Bind} 缺少{Binding} 中的一些功能,但它运行时所花费的时间和使用的内存量均比 {Binding} 要少,且支持更好的调试. 参照网址:{x:Bind} 标记扩展,GitHub微软UWP实例之XamlBind 1,{x:Bind} 基本原理 在 XAML 加载时,{x:Bind} 将转换为你所需的绑定对象,此对象将从数据源上的某一属性中获取相关值.绑定对象可以配置为观察数据源属性值的更改

UWP开发之Template10实践二:拍照功能你合理使用了吗?(TempState临时目录问题)

最近在忙Asp.Net MVC开发一直没空更新UWP这块,不过有时间的话还是需要将自己的经验和大家分享下,以求共同进步. 在上章[UWP开发之Template10实践:本地文件与照相机文件操作的MVVM实例(图文付原代码)]已经谈到了使用FileOpenPicker进行文件选择,以及CameraCaptureUI进行拍照. 对于文件选择一般进行如下设置就能实现: // 选择多个文件 FileOpenPicker openPicker = new FileOpenPicker(); openPic

UWP开发之ORM实践:如何使用Entity Framework Core做SQLite数据持久层?

选择SQLite的理由 在做UWP开发的时候我们首选的本地数据库一般都是Sqlite,我以前也不知道为啥?后来仔细研究了一下也是有原因的: 1,微软做的UWP应用大部分也是用Sqlite.或者说是微软推荐使用Sqlite吧! 2,简单!就只有一个类库没有多余的参照什么的.不像其他数据库还得做复杂配置什么的麻烦! 3,不需要数据库服务,数据服务和客户都在同一个进程里面.如下图: 4,作为存储系统它只支持一个用户一个数据实体. 5,跨平台跨结构,这个好! Sqlite主要使用内容 如果想充分使用好S

android开发之Animation(五)

android开发之Animation的使用(五) 本博文主要讲述的是Animation中的AnimationLisenter的使用方法,以及此类的一些生命周期函数的调用,代码实例如下: MainActivity.java: package com.example.animationlistener; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.vi

jQuery开发之DOM操作五

CSS-DOM操作 CSS-DOM技术简单来说就是读取和设置style对象的各种属性.在jQuery中,可以直接利用css()方法获取元素的样式属性,jQuery示例代码如下: $("p").css("color"); //获取<p>元素的样式颜色 无论color属性是外部css导入,或者是直接拼接在HTML元素里(内联),css()方法都可以获取到属性style里的其他属性的值. css()方法设置单个样式属性,代码如下: $("p"

jz2440裸板开发之:外部中断

实验目的:   利用外部中断的方式,来实现点亮对应的LED 实验原理:中断的最大好处就是让CPU避免了采用查询的方式来处理中断处理程序要干的事,中断的三个必要元素:中断源.中断控制器.中断处理函数.在arm9上有七种异常, (这里把重启也包括在内,另外还有一个reversed,加起来应该有八个,只是这个中断向量地址没有用而已).当中断发生时,CPU会自动跳到中断向量地址处执行程序,由于每个中断向量都只有4字节的地址空间,所以我们经常在此处放一个跳转语句.上面CPU的自动跳转是硬件自动完成的,我们