UWP開發之Mvvmlight實踐五:SuspensionManager中斷掛起以及復原處理


最近比較忙有一段時間沒有更新了,再接再厲繼續分享。

 

案例下載:https://github.com/NewBLife/UWP/tree/master/SuspendSample

先我們看看App在生命周期中會出現那些狀態:

詳細介紹參考官網:App lifecycle  https://msdn.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle

 

state diagram showing transitions between app execution states

一般情況:

比如用新聞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從它里面拷貝過來,別忘記改命名空間。

image

在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詳細使用后續章節待續…


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM