一、“後退鍵”不會終止程序;二、應用生命週期;三、OnLaunched;四、OnSuspending;五、OnResuming;六、注意
一、“後退鍵”不會終止應用
關於 Windows Phone 8.1 的應用生命週期,第一個要知道的關鍵就是:“後退鍵”不會終止應用!
在 8.0 時代,不斷的按下“後退鍵”就可以完全的關閉並且終止應用,但在 8.1 中,這樣的行為只會讓應用處在 Suspended(掛起)狀態,可以通過長按“後退鍵”進入多任務界面查看。
那如果還想像 8.0 一樣終止應用呢?(雖然不推薦也沒有必要)可以在多任務界面點擊應用右上角的“叉叉”或者向下滑。
二、應用生命週期
應用的三個狀態分別是:
A:NotRunning
也就是還沒開啓過應用,在多任務界面沒有該應用時。
B:Running
在屏幕上顯示的應用就是 Running 狀態,同時只會有 1 個應用處於 Running 狀態。
C:Suspended
不在屏幕上顯示並能在多任務界面查看的應用則處於 Suspended(掛起)狀態。
三種狀態間切換的操作:
(1)NotRunning -> Running
要從 NotRunning 切換到 Running 狀態,其實也就是開啓應用,可通過點擊應用磁貼、應用間協議啓動、Cortana等方式。
在狀態的切換過程中會觸發 OnLaunched 事件。
(2)Running -> Suspended
當應用不再佔據屏幕時則從 Running 切換到 Suspended 狀態,可以是“Win”鍵、“返回鍵”,有電話打來時也會掛起。
在狀態的切換過程中會觸發 OnSuspending 事件。
(3)Suspended -> Running
如果在應用掛起狀態時沒有因為某些原因(比如內存不足)導致應用終止的話,點擊磁貼或者多任務切換都會讓應用從 Suspender 返回到 Running 狀態。
在狀態的切換過程中會依次觸發 OnResuming 和 OnLaunched 事件。
(4)Suspended -> NotRunning
如果在應用掛起狀態時因為某些原因(比如內存不足)導致應用終止的話,則會從 Suspended 變成 NotRunning 狀態。
在這過程不會觸發任何事件。
三、OnSuspending
因為應用在掛起狀態時,並不能預測應用是否會因為某些原因(比如內存不足)而終止,而在這終止過程中也沒有事件讓開發者處理應用數據,所以只能在應用將要掛起時準備。因此 OnSuspending 事件變得十分重要。
若要使用 OnSuspending 方法則先要在構造函數中添加對其的引用:
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
而在 OnSuspending 方法中可以根據需要保存頁面數據,比如輸入框內的文本、頁面導航歷史等,可以通過保存在應用獨立存儲中或利用 NavigationHelper 和 SuspensionManager 類等:
async void OnSuspending(object sender, SuspendingEventArgs e)
{
SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral();
await this.SaveStateToLocalFile(Data.Value);
await SuspensionManager.SaveAsync();
deferral.Complete();
}
如果只想保存某個頁面的信息則可以在 SaveState 中保存:
private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
e.PageState["isEditing"] = true;
e.PageState["currentText"] = this.viewModel.DataItem.Title;
}
NavigationHelper 和 SuspensionManager 類是添加基本頁時 Visual Studio 自動添加的:
public class NavigationHelper : DependencyObject
{
private Page Page { get; set; }
private Frame Frame { get { return this.Page.Frame; } }
public NavigationHelper(Page page)
{
this.Page = page;
this.Page.Loaded += (sender, e) =>
{
WINDOWS_PHONE_APP
Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
e
if
};
this.Page.Unloaded += (sender, e) =>
{
WINDOWS_PHONE_APP
Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
e
if
};
}
#region Navigation support
RelayCommand _goBackCommand;
RelayCommand _goForwardCommand;
public RelayCommand GoBackCommand
{
get
{
if (_goBackCommand == null)
{
_goBackCommand = new RelayCommand(
() => this.GoBack(),
() => this.CanGoBack());
}
return _goBackCommand;
}
set
{
_goBackCommand = value;
}
}
public RelayCommand GoForwardCommand
{
get
{
if (_goForwardCommand == null)
{
_goForwardCommand = new RelayCommand(
() => this.GoForward(),
() => this.CanGoForward());
}
return _goForwardCommand;
}
}
public virtual bool CanGoBack()
{
return this.Frame != null && this.Frame.CanGoBack;
}
public virtual bool CanGoForward()
{
return this.Frame != null && this.Frame.CanGoForward;
}
public virtual void GoBack()
{
if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
}
public virtual void GoForward()
{
if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
}
#if WINDOWS_PHONE_APP
private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
{
if (this.GoBackCommand.CanExecute(null))
{
e.Handled = true;
this.GoBackCommand.Execute(null);
}
}
#else
private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
AcceleratorKeyEventArgs e)
{
var virtualKey = e.VirtualKey;
if ((e.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
e.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
(virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
(int)virtualKey == 166 || (int)virtualKey == 167))
{
var coreWindow = Window.Current.CoreWindow;
var downState = CoreVirtualKeyStates.Down;
bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
bool noModifiers = !menuKey && !controlKey && !shiftKey;
bool onlyAlt = menuKey && !controlKey && !shiftKey;
if (((int)virtualKey == 166 && noModifiers) ||
(virtualKey == VirtualKey.Left && onlyAlt))
{
e.Handled = true;
this.GoBackCommand.Execute(null);
}
else if (((int)virtualKey == 167 && noModifiers) ||
(virtualKey == VirtualKey.Right && onlyAlt))
{
e.Handled = true;
this.GoForwardCommand.Execute(null);
}
}
}
private void CoreWindow_PointerPressed(CoreWindow sender,
PointerEventArgs e)
{
var properties = e.CurrentPoint.Properties;
if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
properties.IsMiddleButtonPressed) return;
bool backPressed = properties.IsXButton1Pressed;
bool forwardPressed = properties.IsXButton2Pressed;
if (backPressed ^ forwardPressed)
{
e.Handled = true;
if (backPressed) this.GoBackCommand.Execute(null);
if (forwardPressed) this.GoForwardCommand.Execute(null);
}
}
#endif
#endregion
#region Process lifetime management
private String _pageKey;
public event LoadStateEventHandler LoadState;
public event SaveStateEventHandler SaveState;
public 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]));
}
}
}
public 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
}
public delegate void LoadStateEventHandler(object sender, LoadStateEventArgs e);
public delegate void SaveStateEventHandler(object sender, SaveStateEventArgs e);
public class LoadStateEventArgs : EventArgs
{
public Object NavigationParameter { get; private set; }
public Dictionary<string, Object> PageState { get; private set; }
public LoadStateEventArgs(Object navigationParameter, Dictionary<string, Object> pageState)
: base()
{
this.NavigationParameter = navigationParameter;
this.PageState = pageState;
}
}
public class SaveStateEventArgs : EventArgs
{
public Dictionary<string, Object> PageState { get; private set; }
public SaveStateEventArgs(Dictionary<string, Object> pageState)
: base()
{
this.PageState = pageState;
}
}
NavigationHelper
internal sealed class SuspensionManager
{
private static Dictionary<string, object> _sessionState = new Dictionary<string, object>();
private static List<Type> _knownTypes = new List<Type>();
private const string sessionStateFilename = "_sessionState.xml";
public static Dictionary<string, object> SessionState
{
get { return _sessionState; }
}
public static List<Type> KnownTypes
{
get { return _knownTypes; }
}
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);
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);
}
}
public static async Task RestoreAsync(String sessionBaseKey = null)
{
_sessionState = new Dictionary<String, Object>();
try
{
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);
}
}
private static DependencyProperty FrameSessionStateKeyProperty =
DependencyProperty.RegisterAttached("_FrameSessionStateKey", typeof(String), typeof(SuspensionManager), null);
private static DependencyProperty FrameSessionBaseKeyProperty =
DependencyProperty.RegisterAttached("_FrameSessionBaseKeyParams", typeof(String), typeof(SuspensionManager), null);
private static DependencyProperty FrameSessionStateProperty =
DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary<String, Object>), typeof(SuspensionManager), null);
private static List<WeakReference<Frame>> _registeredFrames = new List<WeakReference<Frame>>();
public static void RegisterFrame(Frame frame, String sessionStateKey, String sessionBaseKey = null)
{
if (frame.GetValue(FrameSessionStateKeyProperty) != null)
{
throw new InvalidOperationException("Frames can only be registered to one session state key");
}
if (frame.GetValue(FrameSessionStateProperty) != null)
{
throw new InvalidOperationException("Frames must be either be registered before accessing frame session state, or not registered at all");
}
if (!string.IsNullOrEmpty(sessionBaseKey))
{
frame.SetValue(FrameSessionBaseKeyProperty, sessionBaseKey);
sessionStateKey = sessionBaseKey + "_" + sessionStateKey;
}
frame.SetValue(FrameSessionStateKeyProperty, sessionStateKey);
_registeredFrames.Add(new WeakReference<Frame>(frame));
RestoreFrameNavigationState(frame);
}
public static void UnregisterFrame(Frame frame)
{
SessionState.Remove((String)frame.GetValue(FrameSessionStateKeyProperty));
_registeredFrames.RemoveAll((weakFrameReference) =>
{
Frame testFrame;
return !weakFrameReference.TryGetTarget(out testFrame) || testFrame == frame;
});
}
public static Dictionary<String, Object> SessionStateForFrame(Frame frame)
{
var frameState = (Dictionary<String, Object>)frame.GetValue(FrameSessionStateProperty);
if (frameState == null)
{
var frameSessionKey = (String)frame.GetValue(FrameSessionStateKeyProperty);
if (frameSessionKey != null)
{
if (!_sessionState.ContainsKey(frameSessionKey))
{
_sessionState[frameSessionKey] = new Dictionary<String, Object>();
}
frameState = (Dictionary<String, Object>)_sessionState[frameSessionKey];
}
else
{
frameState = new Dictionary<String, Object>();
}
frame.SetValue(FrameSessionStateProperty, frameState);
}
return frameState;
}
private static void RestoreFrameNavigationState(Frame frame)
{
var frameState = SessionStateForFrame(frame);
if (frameState.ContainsKey("Navigation"))
{
frame.SetNavigationState((String)frameState["Navigation"]);
}
}
private static void SaveFrameNavigationState(Frame frame)
{
var frameState = SessionStateForFrame(frame);
frameState["Navigation"] = frame.GetNavigationState();
}
}
public class SuspensionManagerException : Exception
{
public SuspensionManagerException()
{
}
public SuspensionManagerException(Exception e)
: base("SuspensionManager failed", e)
{
}
}
SuspensionManager
四、OnResuming
既然在 OnSuspending 和 SaveState 方法中保存了必要數據,就可以在 OnResuming 和 LoadState 方法中獲取之前保存的數據:
void OnResuming(object sender, object e)
{
Data.Value += this.CalculateOffsetTimeInDecimalSeconds(this.suspensionTime);
}
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
if ((e.PageState != null) && e.PageState.ContainsKey("isEditing"))
{
this.viewModel.SetEditMode();
this.viewModel.DataItem.Title = e.PageState["currentText"] as string;
}
}
五、OnLaunched
首先,在 OnLaunched 方法中可以通過 e.PreviousExecutionState 瞭解到應用之前的狀態。
狀態包括:
(1)CloseByUser:被用户主動在多任務界面中關閉
(2)NotRunning:沒有啓動過
(3)Running:啓動中
(4)Terminated:掛起狀態時因內存不足被系統終止
(5)Suspended:掛起狀態
因此,可以通過對此的判斷,根據不同情況處理應用:
protected async override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
SuspensionManager.RegisterFrame(rootFrame, "AppFrame");
rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
try
{
await SuspensionManager.RestoreAsync();
}
catch (SuspensionManagerException)
{
}
}
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
Window.Current.Activate();
}
六、注意
以上的方法儘量使用異步操作,不要進行大量的複雜操作。