UWP Application类解析

每一个C# UWP应用中,必不可少的一个文件就是App.xaml.cs。App类是整个程序的入口,它继承了Application类。在App.xaml.cs中,我们可以通过重写Application类中的一些函数,来自定义程序启动时的行为。

在默认UWP模板中,OnLaunched函数担任了初始化程序窗口的工作。但实际上,一个UWP应用的生命周期中,OnLaunched函数可能被调用任意次(包括0次或者很多次)。所以,在初始化程序窗口的时候,必须考虑重复初始化的问题。新手很容易犯的一个错误是在OnLaunched函数中绑定OnBackRequested函数,这样会导致在某些情况下,按一次后退键后退多个页面,或是在某些情况下后退键无效的问题。

注意:本文中代码在Visual Studio 2015下测试过,面向的操作系统目标版本为10.0.14393,最低版本为10.0.10586。完整的示例代码在文末。

目录:

激活函数
处理非正常退出
预启动应用
帧频计数器
一个处理后退键的例子
总结

激活函数

App类中,通过不同方式启动或激活应用时会调用OnLaunched,OnFileActivated,OnFileSavePickerActivated等函数。在此我们将它们统称为“激活函数”。激活函数中必不可少的一项工作是初始化应用界面,如果不这么做的话,应用启动后会一直停留在初始界面没有反应。以下函数是最简单的初始化界面的函数:

        private void CreateRootFrameAndNavigate(Type page, object parameter, bool forceNavigate)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // 若不存在rootFrame,则创建一个rootFrame
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                // OnNavigationFailed函数在默认UWP模板中的App.xaml.cs中有定义
                rootFrame.NavigationFailed += OnNavigationFailed;
                Window.Current.Content = rootFrame;
            }

            // 若rootFrame是新创建的,或是强制要求导航到页面,则导航到指定的页面
            if (rootFrame.Content == null || forceNavigate)
            {
                rootFrame.Navigate(page, parameter);
            }

            Window.Current.Activate();
        }

然后,你可以将默认的OnLaunched函数替换成以下内容, 它的功能由与默认OnLaunched函数是几乎相同的:

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            // 若应用已经启动,则不强制导航到MainPage
            CreateRootFrameAndNavigate(typeof(MainPage), null, false);
        }

然后,如果你的应用支持打开文件的话,你的OnFileActivated函数应该是这样的:

        protected override void OnFileActivated(FileActivatedEventArgs e)
        {
            // 即使应用已经启动,也要跳转到查看文件的页面
            CreateRootFrameAndNavigate(typeof(ViewFilePage), e.Files, true);
        }

注意:由于在应用程序生命周期中,应用有可能会被通过任一方式激活,所以每一个函数都可能会被调用一次或多次。例如OnLaunched函数在每次用户点击开始菜单的应用图标时都会执行。所以在这些激活函数中必须做一些判断,避免重复初始化。(上方的CreateRootFrameAndNavigate函数通过判断rootFrame是否存在的方式避免的重复初始化。)

处理非正常退出

上面说到的这些激活函数都只有一个参数,类型为xxxEventArgs,它们都实现接口IActivatedEventArgs接口,拥有PreviousExecutionState这个表明之前应用状态的成员。若应用上一次运行时被非正常结束(例如关机、断电、被任务管理器结束、因为内存不足被关闭等),PreviousExecutionState的值会是Terminated,按照微软默认UWP模板的做法,这时应该尝试为用户恢复上次退出时的状态。注意:因为应用被非正常结束后,下次启动的方式是不确定的,所以你应该在你支持的任何一个激活函数中判断PreviousExecutionState,并尝试恢复非正常退出的状态。

所以,你现在要将你的激活函数改成这样:

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
            {
                // TODO: 恢复非正常退出前的状态
            }

            // 若应用已经启动,则不强制导航到MainPage
            CreateRootFrameAndNavigate(typeof(MainPage), null, false);
        }

        protected override void OnFileActivated(FileActivatedEventArgs e)
        {
            if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
            {
                // TODO: 恢复非正常退出前的状态
            }

            // 即使应用已经启动,也要跳转到查看文件的页面
            CreateRootFrameAndNavigate(typeof(ViewFilePage), e.Files, true);
        }

预启动应用

OnLaunched函数在Windows系统预启动应用时会执行。

预启动(Prelaunch)指的是Windows系统为了提高应用启动速度,在系统资源充足时主动预先启动一些应用。用户不会观察到预启动行为,预启动的应用在预启动后会马上被挂起(Suspend)。所以,预启动时,应当只加载程序,而不应该加载新闻、消息等容易变化的信息,否则用户真正启动应用时会看到过时的信息。若应用是被预启动的,args.PrelaunchActivated的值为true,否则为false。在Windows 10 1607(10.0.14393)以上版本中,只有你调用CoreApplication.EnablePrelaunch(true)表明应用支持预启动后,你的应用才会被预启动;在Windows 10 1511(10.0.10586)以下版本中,若你的应用不支持预启动,你应当在args.PrelaunchActivated为true时不执行任何操作,直接return。(参见处理应用预启动。)

为了正确处理预启动,或者是正确声明应用不支持预启动,OnLaunched函数应该改为:

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            if (e.PrelaunchActivated)
            {
                // TODO: 若要支持预启动,在此处处理预启动,否则保留原状。
                return;
            }

            if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
            {
                // TODO: 恢复非正常退出前的状态
            }

            // 若应用已经启动,则不强制导航到MainPage
            CreateRootFrameAndNavigate(typeof(MainPage), null, false);
        }

帧频计数器

默认UWP模板中,当通过调试器启动应用时,会显示帧频计数器。若要实现此功能,在你的激活函数中调用显示帧频计数器的代码:

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            // 调试状态下显示帧计数器
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            if (e.PrelaunchActivated)
            {
                // TODO: 若要支持预启动,在此处处理预启动,否则保留原状。
                return;
            }

            if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
            {
                // TODO: 恢复非正常退出前的状态
            }

            // 若应用已经启动,则不强制导航到MainPage
            CreateRootFrameAndNavigate(typeof(MainPage), null, false);
        }

 

一个处理后退键的例子

下面这个例子能够实现当可以后退时,在桌面系统中左上角显示后退键,并且通过手机后退键、平板模式任务栏后退键、桌面窗口左上角后退键、Windows + Backspace快捷键都能够正常实现后退功能。

在每一个创建RootFrame的位置,绑定Navigated函数:在

rootFrame = new Frame();

后面加上

rootFrame.Navigated += OnNavigated;

然后定义OnNavigated函数,根据当前是否可以后退的状态来改变后退键的显示状态:

        private void OnNavigated(object sender, NavigationEventArgs e)
        {
            Frame frame = sender as Frame;
            SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
                frame.CanGoBack ?
                    AppViewBackButtonVisibility.Visible :
                    AppViewBackButtonVisibility.Collapsed;
        }

由于OnWindowCreated函数对于每一个应用窗口只会执行一次,而后退键的操作也是应用于每一个窗口的,所以OnWindowCreated函数是绑定后退键事件的绝佳位置:

        protected override void OnWindowCreated(WindowCreatedEventArgs args)
        {
            SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
        }

        private void OnBackRequested(object sender, BackRequestedEventArgs e)
        {
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame.CanGoBack)
            {
                rootFrame.GoBack();
                e.Handled = true;
            }
            else
            {
                e.Handled = false;
            }
        }

这样,即使你有多个窗口,每个窗口也能很好地独立处理窗口内的后退键行为。

总结

综上所述,一个实现最简单功能,能够正确处理普通启动和文件启动,能够正确处理后退键的App类代码如下(如果你的应用不支持打开文件的话,将OnFileActivated函数删除即可)。它完成了一个基本的UWP应用在App类中该做的事。

    public sealed partial class App : Application
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            // 调试状态下显示帧计数器
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            if (e.PrelaunchActivated)
            {
                // TODO: 若要支持预启动,在此处处理预启动,否则保留原状。
                return;
            }

            if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
            {
                // TODO: 恢复非正常退出前的状态
            }

            // 若应用已经启动,则不强制导航到MainPage
            CreateRootFrameAndNavigate(typeof(MainPage), null, false);
        }

        protected override void OnFileActivated(FileActivatedEventArgs e)
        {
            // 调试状态下显示帧计数器
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
            {
                // TODO: 恢复非正常退出前的状态
            }

            // 即使应用已经启动,也要跳转到查看文件的页面
            CreateRootFrameAndNavigate(typeof(ViewFilePage), e.Files, true);
        }

        protected override void OnWindowCreated(WindowCreatedEventArgs args)
        {
            // 关联按下返回键的事件。
            SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
        }

        private void CreateRootFrameAndNavigate(Type page, object parameter, bool forceNavigate)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // 若不存在rootFrame,则创建一个rootFrame
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                rootFrame.Navigated += OnNavigated;
                rootFrame.NavigationFailed += OnNavigationFailed;
                Window.Current.Content = rootFrame;
            }

            // 若rootFrame是新创建的,或是强制要求导航到页面,则导航到指定的页面
            if (rootFrame.Content == null || forceNavigate)
            {
                rootFrame.Navigate(page, parameter);
            }

            Window.Current.Activate();
        }

        private void OnNavigated(object sender, NavigationEventArgs e)
        {
            Frame frame = sender as Frame;

            // 若可以后退,则显示后退键,否则隐藏后退键。
            SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
                frame.CanGoBack ?
                    AppViewBackButtonVisibility.Visible :
                    AppViewBackButtonVisibility.Collapsed;
        }

        private void OnBackRequested(object sender, BackRequestedEventArgs e)
        {
            // 若可以后退,则后退。若不能后退,则交给其他的程序处理。
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame.CanGoBack)
            {
                rootFrame.GoBack();
                e.Handled = true;
            }
            else
            {
                e.Handled = false;
            }
        }

        private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
        }
    }

留言

向我们提问或者评论我们的文章。您的留言不会被直接显示在网站内。
请在浏览器中启用JavaScript来完成此表单。
Email