Fork me on GitHub

UWP-Suspending

Week4

需求如下:

  1. (Mainpage.xaml.cs)在 MainPage 中点击 checkbox 出现横线,输入数据(选择图片),挂起并关闭程序,重新启动时,程序显示在 Mainpage 界面,并且点击的checkbox与对应横线,数据与图片都存在。
  2. (Newpage.xaml.cs)在 NewPage 中输入数据(或选择图片),挂起并关闭程序,重新启动时,程序显示在 Newpage 界面,数据与图片都存在。

完成图片的挂起恢复,规避路径权限问题

Suspending

首先,了解一下应用的生命周期

在 Windows8 之前,应用的生命周期非常简单,应用始终处于运行状态或未运行状态。 当用户最小化应用或离开应用时,它们将继续运行。 随着便携设备和电源管理变得日益重要,这种情况不再可行。
Win8 引入了新的应用模型:在高级别上,添加新的已暂停状态。 当用户最小化 Windows 应用商店应用或切换到其他应用后,该应用会立刻处于暂停状态。 这意味着应用的线程已停止,并且应用保留在内存中(除非操作系统需要回收资源)。 当用户切换回该应用时,该应用可快速还原到正在运行状态。

AppLifeCycle

   Picture1   AppLifeCycle

——MicrosoftDocs

我们这次需要实现的就是这个应用的挂起状态(Suspending state)。

首先,在 App.xmal.cs 代码中,已经有了关于挂起状态的函数。这是 windows 自己的考虑,所有应用都可以具备挂起状态,而且对其有需求。我们要做的事情,就是为其注册挂起事件,然后实现这个挂起事件。

  1. 注册挂起事件

    1
    2
    3
    4
    public App() {
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    }
  2. 挂起事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private void OnSuspending(object sender, SuspendingEventArgs e) {
    var deferral = e.SuspendingOperation.GetDeferral();
    // TODO: 保存应用程序状态并停止任何后台活动
    IsSuspending = true;

    // Get the frame navigation state serialized as a string and save in settings
    Frame frame = Window.Current.Content as Frame;
    ApplicationData.Current.LocalSettings.Values["NavigationState"] = frame.GetNavigationState();
    deferral.Complete();
    }
  3. 在页面启动判断之前是否为挂起状态

    1
    2
    3
    4
    5
    6
    if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) {
    //TODO: 从之前挂起的应用程序加载状态
    if (ApplicationData.Current.LocalSettings.Values.ContainsKey("NavigationState")) {
    rootFrame.SetNavigationState((string)ApplicationData.Current.LocalSettings.Values["NavigationState"]);
    }
    }

在 App 代码中为所有页面注册了挂起事件,接下来就是为不同页面实现各自的挂起需求,即存储哪些数据。

首先,OnNavigatedFrom 是从当前页面跳转,那么这就是我们要进行判断是否进入挂起状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected override void OnNavigatedFrom(NavigationEventArgs e) {
bool suspending = ((App)App.Current).IsSuspending;
if (suspending) {
// Save volatile state in case we get terminated later on, then
// we can restore as if we'd never been gone :)
var composite = new ApplicationDataCompositeValue {
["title"] = Title.Text,
["description"] = Description.Text,
["date"] = Date.Date
//...
};
ApplicationData.Current.LocalSettings.Values["Mainpage"] = composite;
}
}

然后,OnNavigatedTo 是跳转到当前页面,在这里,我们要进行一个判断,当前页面是否是从挂起状态恢复,以此决定是否恢复数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected async override void OnNavigatedTo(NavigationEventArgs e) {
Frame rootFrame = Window.Current.Content as Frame;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
if (e.NavigationMode == NavigationMode.New) {
ApplicationData.Current.LocalSettings.Values.Remove("Mainpage");
} else {
// Try to restore state if any, in case we were terminated
if (ApplicationData.Current.LocalSettings.Values.ContainsKey("Mainpage")) {
var composite = ApplicationData.Current.LocalSettings.Values["Mainpage"] as ApplicationDataCompositeValue;
Title.Text = (string)composite["title"];
Description.Text = (string)composite["description"];
Date.Date = (DateTimeOffset)composite["date"];
//...

// We're done with it, so remove it
ApplicationData.Current.LocalSettings.Values.Remove("Mainpage");
}
}
}

至此,简单的挂起关闭恢复数据就做好了。

图片的恢复

在进行挂起并挂壁恢复数据的过程中,最让人头疼的莫过于,图片问题。以往也一定遇到了这个问题。这终归都是 UWP 的权限问题。UWP 规定应用程序的权限仅限于其安装目录路径,所以对于任意路径的文件获取,其实很难做到。在这里,我们采用规避路径权限问题的做法,实现图片的恢复。

在 Stack Overflow 查询到 UWP 中有 FutureAccessList 和 MostRecentlyUsedList 两个记录 StorageItems 的工具。

When you add an item to such list, you obtain a token and this is what you should remember in your LocalSettings (for example). Then you can reuse such token to access the file/folder

From Stack Overflow

下面是实现代码:

  • 存储图片 Token
1
2
3
4
5
6
7
8
9
10
11
private async void Select_Photo(object sender, RoutedEventArgs e) {
FileOpenPicker picker = new FileOpenPicker();
// Initialize the picture file type to take
StorageFile file = await picker.PickSingleFileAsync();

if (file != null) {
ApplicationData.Current.LocalSettings.Values["MyToken"] =
StorageApplicationPermissions.FutureAccessList.Add(file);
// Load the selected picture
}
}
  • 恢复图片文件
1
2
3
4
5
6
7
8
9
if (ApplicationData.Current.LocalSettings.Values.ContainsKey("MyToken")) {
if ((string)ApplicationData.Current.LocalSettings.Values["MyToken"] != "") {
StorageFile theFile = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(
(string)ApplicationData.Current.LocalSettings.Values["MyToken"]);
if (theFile != null) {
// Load the saved picture
}
}
}

这个 FutureAccessList 是实现规避图片路径权限问题的一个方法。很好奇,有没有解决这个权限问题的方法,因为网易云 UWP 的本地音乐就不存在这个权限问题,极其膜拜。

后记

对于这个作业要求,和别人有过讨论。对于实现恢复 checkbox 和 line 的绑定 UI,这个需求有些异议。如果从恢复应用 UI 的角度来说,这是合理的。但是,从恢复输入现场来说,这个就是多余的。

基于其挂起存储空间的限制,我们认为,挂起恢复的目的,应该是保存用户的输入,避免崩溃或其他原因导致应用挂起然后用户再回到页面的时候,其输入数据丢失。这样需要用户重新输入,体验不佳。所以,对于我们应该存储的,就是选中 Item 的 id 和对其进行的编辑。其余数据的丢失,数据库中依旧存有,并不应该都由挂起存储空间负责。

我们的数据更新和 Item 状态,应该由用户点击保存数据到数据库来存储。我们的挂起存储的数据,应该只是输入数据,保证良好的用户体验。