最近比較忙有一段時間沒有更新了,再接再厲繼續分享。 先我們看看App在生命周期中會出現那些狀態: 詳細介紹參考官網:App lifecycle https://msdn.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle 一般情況: ...
最近比較忙有一段時間沒有更新了,再接再厲繼續分享。
先我們看看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詳細使用後續章節待續…