最近比较忙有一段时间没有更新了,再接再厉继续分享。
先我们看看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详细使用后续章节待续…