前一段开发UWP应用的时候因为系统返回按钮事件(SystemNavigationManager.GetForCurrentView().BackRequested)浪费了不少时间。现象就是在手机版的详细页面跳转到其他应用,然后再返回应用,点击系统的返回按钮时应用关闭而不是返回主页面,如果应用不跳转就没有问题。最后调查发现是注销系统返回按钮事件(SystemNavigationManager.GetForCurrentView().BackRequested)的位置放错了,特在此给各位分享一下。

备注:之所以在各个ViewModel注册注销BackRequested事件,是由于各个页面在返回时处理需求不一样(弹确认对话框等等)。

起因

我们都知道Page中如下方法可以重载:

public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
} protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
} protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
}
}

他们的执行顺序如下:

  1. 在此页将要在Frame中显示时:OnNavigatedTo
  2. 当此页即将不再是Frame中的活动页面时:OnNavigatingFrom
  3. 当此页不再在Frame中显示时:OnNavigatedFrom

根据NavigationHelper的做法在MVVMLight里面我们也会做一次封装让Viewmodel可以调用:

#region 进程生命期管理

        private String _pageKey;

        /// <summary>
/// 在当前页上注册此事件以向该页填入
/// 在导航过程中传递的内容以及任何
/// 在从以前的会话重新创建页时提供的已保存状态。
/// </summary>
public event LoadStateEventHandler LoadState;
/// <summary>
/// 在当前页上注册此事件以保留
/// 与当前页关联的状态,以防
/// 应用程序挂起或从导航缓存中丢弃
/// 该页。
/// </summary>
public event SaveStateEventHandler SaveState; /// <summary>
/// 在此页将要在 Frame 中显示时进行调用。
/// 此方法调用 <see cref="LoadState"/>,应在此处放置所有
/// 导航和进程生命周期管理逻辑。
/// </summary>
/// <param name="e">描述如何访问此页的事件数据。Parameter
/// 属性提供要显示的组。</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth; if (e.NavigationMode == NavigationMode.New)
{
// 在向导航堆栈添加新页时清除向前导航的
// 现有状态
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
} // 将导航参数传递给新页
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
}
}
else
{
// 通过将相同策略用于加载挂起状态并从缓存重新创建
// 放弃的页,将导航参数和保留页状态传递
// 给页
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]));
}
}
} /// <summary>
/// 当此页不再在 Frame 中显示时调用。
/// 此方法调用 <see cref="SaveState"/>,应在此处放置所有
/// 导航和进程生命周期管理逻辑。
/// </summary>
/// <param name="e">描述如何访问此页的事件数据。Parameter
/// 属性提供要显示的组。</param>
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
var pageState = new Dictionary<String, Object>();
if (this.SaveState != null)
{
this.SaveState(this, new SaveStateEventArgs(pageState));
}
frameState[_pageKey] = pageState;
}
#endregion

OnNavigatedFrom中调用SaveState(保存页面状态),OnNavigatedTo中调用LoadState(复原页面状态)。

在ViewModel的LoadState里面可能会写入事件绑定(例:系统返回按钮事件)、通知消息等等,自然的我们将事件、通知消息的注销处理写在SaveState里面。

        public void LoadState(object navParameter, Dictionary<string, object> state)
{
if(state!=null)
{
WelcomeTitle = state[nameof(WelcomeTitle)].ToString();
}
// 注册事件
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested
} public void SaveState(Dictionary<string, object> state)
{
state[nameof(WelcomeTitle)] = WelcomeTitle;
// 注销事件
SystemNavigationManager.GetForCurrentView().BackRequested -= OnBackRequested
}

如果应用之间不跳转确实没有问题,但是应用之间跳转就出现问题(事件无效),为什么?

原因

因为跳转的时候触发应用App.xaml.cs的Suspending事件,从而调用OnSuspending处理:

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
// 保存应用状态
await SuspensionManager.SaveAsync();
deferral.Complete();
}

而SuspensionManager.SaveAsync中最后使用frame.GetNavigationState()方法调用OnNavigatedFrom处理(即SaveState),返回的时候驻留在内存的应用直接复原不执行OnNavigatedTo处理(即LoadState)。这样在OnNavigatedFrom处理(即SaveState)中注销的事件和通知消息就得不到复原而导致应用出问题。(特殊情况:如果应用被内存回收掉的话将会执行OnNavigatedTo处理(即LoadState)而没有问题。)

模拟过程

1,启动应用

2,挂起应用(注意:不是挂起并关闭)

到达OnSuspending处理

3,执行frame.GetNavigationState()方法

直接跳转到OnNavigatedFrom处理(即SaveState)

4,复原应用

OnNavigatedTo处理(即LoadState)没有触发

总结

正是由于应用在挂起的时候会出现这个问题所以应该将事件或者通信消息的注销处理写在OnNavigatingFrom里面。Viewmodel可以封装一个SavingState方法让OnNavigatingFrom调用:

public void SavingState()
{
// 注销事件
SystemNavigationManager.GetForCurrentView().BackRequested -= OnBackRequested
}

UWP开发之Mvvmlight实践八:为什么事件注销处理要写在OnNavigatingFrom中的更多相关文章

  1. UWP开发之Mvvmlight实践七:如何查找设备(Mobile模拟器、实体手机、PC)中应用的Log等文件

    在开发中或者后期测试乃至最后交付使用的时候,如果应用出问题了我们一般的做法就是查看Log文件.上章也提到了查看Log文件,这章重点讲解下如何查看Log文件?如何找到我们需要的Packages安装包目录 ...

  2. UWP开发之Mvvmlight实践二:Mvvmlight的核心框架MVVM与MVC、MVP的区别(图文详解)

    最近UWP开发在海外很潮流,随着微软收购Xamarin,我们这些C#程序员也可以靠这杆小米枪挑战Android,IOS平台了. 那我们为什么选择MVVM做UWP开发?MVC,MVP,MVVM他们之间到 ...

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

    在前几章介绍了不少MVVM以及Mvvmlight实例,那实际企业开发中将以那种架构开发比较好?怎样分层开发才能节省成本? 本文特别分享实际企业项目开发中使用过的项目架构,欢迎参照使用!有不好的地方欢迎 ...

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

    最近比较忙有一段时间没有更新了,再接再厉继续分享. 案例下载:https://github.com/NewBLife/UWP/tree/master/SuspendSample 先我们看看App在生命 ...

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

    {x:bind}是随着UWP被推出而被添加的,可以说是Win10 UWP开发专有扩展.虽然 {x:Bind} 缺少{Binding} 中的一些功能,但它运行时所花费的时间和使用的内存量均比 {Bind ...

  6. UWP开发之Mvvmlight实践三:简单MVVM实例开发(图文详解付代码)

    在做MVVM各种框架对比之前,我觉得有必要先自己做一个简单的MVVM实现案例比较好,这样就可以看到自己实现的时候有那些不方便的地方.而各种框架又是怎么解决我们这些麻烦的. 案例介绍:用户登录画面,没有 ...

  7. UWP开发之Mvvmlight实践一:如何在项目中添加使用Mvvmlight(图文详解)

    最近一直在做UWP开发,为了节省成本等等接触到MVVMlight,觉得有必要发点时间研究它的用法与实现原理才行.如果有问题的地方或者有好的建议欢迎提出来. 随着移动开发的热门,Mvvmlight在An ...

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

    最近完成一款UWP应用,在手机端测试发布版(Release)的时候应用莫名奇妙的强行关闭,而同样的应用包在PC端一点问题都没有,而且Debug版在两个平台都没有问题,唯独手机的Release版有问题. ...

  9. UWP开发之Template10实践:本地文件与照相机文件操作的MVVM实例(图文付原代码)

    前面[UWP开发之Mvvmlight实践五:SuspensionManager中断挂起以及复原处理]章节已经提到过Template10,为了认识MvvmLight的区别特做了此实例. 原代码地址:ht ...

随机推荐

  1. ABP入门系列(2)——通过模板创建MAP版本项目

    一.从官网创建模板项目 进入官网下载模板项目 依次按下图选择: 输入验证码开始下载 下载提示: 二.启动项目 使用VS2015打开项目,还原Nuget包: 设置以Web结尾的项目,设置为启动项目: 打 ...

  2. 如何一步一步用DDD设计一个电商网站(三)—— 初涉核心域

    一.前言 结合我们本次系列的第一篇博文中提到的上下文映射图(传送门:如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域.因为电子商务是以信息网络 ...

  3. Linux中进行单文件内容的复制

    文件内容复制的常规方法: 开辟一段空间,不断读取文件的内容并写入另一文件当中,这种方法好在安全,一般在类型允许的最大范围内是安全的,缺点就是复制内容的时间长 一次性复制文件的内容,这种方法必须首先获取 ...

  4. javascript工厂模式和构造函数模式创建对象

    一.工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程(本书后面还将讨论其他设计模式及其在JavaScript 中的实现).考虑到在ECMAScript 中无法创 ...

  5. ASP.NET Core 中文文档 第五章 测试(5.2)集成测试

    原文: Integration Testing 作者: Steve Smith 翻译: 王健 校对: 孟帅洋(书缘) 集成测试确保应用程序的组件组装在一起时正常工作. ASP.NET Core支持使用 ...

  6. iOS有关横向TableView的东西

    之前看到Apple store里面有横向的tableview,当然也有可能是collectionview啦. 尤其是项目中只有一条那么需要横向滑动的东西,就没有必要使用庞大的collectionvie ...

  7. 新手学习web遇到的一些乱码问题

    在新手学习web网站学习的时候经常会遇到?????这种乱码,对于刚起步的菜鸟来说真的很头痛,很容易打击继续学的信心当然了对于菜鸟的我最近也遇到过乱码问题,沉浸其中不能自拔,爱的深啊!!!!!我所遇到的 ...

  8. SharePoint 2013: A feature with ID has already been installed in this farm

    使用Visual Studio 2013创建一个可视web 部件,当右击项目选择"部署"时报错: "Error occurred in deployment step ' ...

  9. 一点公益商城开发系统模式Ring Buffer+

    一个队列如果只生产不消费肯定不行的,那么如何及时消费Ring Buffer的数据呢?简单的方案就是当Ring Buffer"写满"的时候一次性将数据"消费"掉. ...

  10. iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

    --iOS多媒体 概览 随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制, ...