儲存和恢複頁面狀态
特别是當您開始使用多頁面應用程式時,将應用程式的頁面視為資料的主要存儲庫非常有用,而僅僅是作為底層資料的臨時可視化和互動式視圖。這裡的關鍵詞是暫時的。如果您在使用者與之互動時保持基礎資料是最新的,那麼頁面可以顯示和消失而不必擔心。
本系列的最後一個程式是DataTransfer6,它在程式暫停時将AppData(和其他一些資訊)的内容儲存在應用程式本地存儲中 - 是以當程式終止時 - 然後在程式下次啟動時檢索該資料起來。
除了儲存使用者辛苦輸入的資料外,您可能還希望儲存頁面導航堆棧的狀态。這意味着如果使用者在資訊頁面上輸入資料并且程式終止,則下次程式運作時,它将導航到該資訊頁面并恢複部分輸入的資料。
您可能還記得,Application類定義了一個名為Properties的屬性,它是一個包含字元串鍵和對象值的字典。您可以在App類中的OnSleep覆寫之前或期間在“屬性”字典中設定項目。然後,下次App構造函數執行時,這些項将可用。
底層平台通過将對象轉換為可以将對象儲存到檔案的形式來序列化“屬性”字典中的對象。應用程式員無論是二進制形式還是字元串形式(可能是XML或JSON)都無關緊要。
對于整數或浮點數,對于DateTime值或對于字元串,序列化很簡單。在某些平台上,可以将更複雜的類的執行個體(例如InformationViewModel)直接儲存到Properties集合中。但是,這并不适用于所有平台。将類自身序列化為XML或JSON字元串,然後将結果字元串儲存在Properties集合中會更安全。随着Xamarin.Forms可移植類庫的.NET版本,XML序列化比JSON序列化更容易,這就是DataTransfer6使用的。
執行序列化和反序列化時,您需要注意對象引用。序列化不保持對象相等。讓我們看看這可能是一個問題:
DataTransfer5中引入的AppData版本有兩個屬性:InfoCollection,它是InformationViewModel對象的集合,以及CurrentInfo,它是目前正在編輯的InformationViewModel對象。
該程式依賴于CurrentInfo對象也是InfoCollection中的項目的事實。 CurrentInfo成為info頁面的BindingContext,使用者以互動方式更改該InformationViewModel執行個體的屬性。但隻是因為同一個對象是InfoCollection的一部分,新值才會顯示在ListView中。
序列化AppData的InfoCollection和CurrentInfo屬性然後反序列化以建立新的AppData會發生什麼?
在反序列化版本中,CurrentInfo對象将具有與InfoCollection中的一個項完全相同的屬性,但它不是同一個執行個體。如果恢複程式以允許使用者繼續編輯資訊頁面上的項目,則這些編輯中的任何一個都不會反映在ListView集合中的對象中。
通過這種心理準備,現在是時候檢視DataTransfer6中的AppData版本了。
public class AppData
{
public AppData()
{
InfoCollection = new ObservableCollection<InformationViewModel>();
CurrentInfoIndex = -1;
}
public ObservableCollection<InformationViewModel> InfoCollection { private set; get; }
[XmlIgnore]
public InformationViewModel CurrentInfo { set; get; }
public int CurrentInfoIndex { set; get; }
public string Serialize()
{
// If the CurrentInfo is valid, set the CurrentInfoIndex.
if (CurrentInfo != null)
{
CurrentInfoIndex = InfoCollection.IndexOf(CurrentInfo);
}
XmlSerializer serializer = new XmlSerializer(typeof(AppData));
using (StringWriter stringWriter = new StringWriter())
{
serializer.Serialize(stringWriter, this);
return stringWriter.GetStringBuilder().ToString();
}
}
public static AppData Deserialize(string strAppData)
{
XmlSerializer serializer = new XmlSerializer(typeof(AppData));
using (StringReader stringReader = new StringReader(strAppData))
{
AppData appData = (AppData)serializer.Deserialize(stringReader);
// If the CurrentInfoIndex is valid, set the CurrentInfo.
if (appData.CurrentInfoIndex != -1)
{
appData.CurrentInfo = appData.InfoCollection[appData.CurrentInfoIndex];
}
return appData;
}
}
}
此版本具有InfoCollection屬性和CurrentInfo屬性(如前一版本),但它還包含int類型的CurrentInfoIndex屬性,并且CurrentInfo屬性使用XmlIgnore屬性進行标記,這意味着它不會被序列化。
該類還有兩個方法,名為Serialize和Deserialize。 Serialize首先将CurrentInfoIndex屬性設定為InfoCollection中CurrentInfo的索引。然後,它将類的執行個體轉換為XML字元串并傳回該字元串。
反序列化恰恰相反。它是一個帶字元串參數的靜态方法。假定該字元串是AppData對象的XML表示形式。在将其轉換為AppData執行個體後,該方法基于CurrentInfoIndex屬性設定CurrentInfo屬性。現在,CurrentInfo再次成為InfoCollection成員之一的相同對象。該方法傳回該AppData執行個體。
從DataTransfer5到DataTransfer6的唯一其他變化是App類。 OnSleep覆寫序列化AppData對象并使用“appData”鍵将其儲存在Properties字典中。但如果使用者導航到DataTransfer6InfoPage并且可能正在輸入或編輯資訊,它還會使用鍵“isInfoPageActive”儲存布爾值。
App構造函數反序列化“appData”屬性條目中可用的字元串,或者如果該字典條目不存在,則将AppData屬性設定為新執行個體。如果“isInfoPageActive”條目為true,則它不僅必須将DataTransfer6MainPage執行個體化為NavigationPage構造函數的參數(像往常一樣),還必須導航到DataTransfer6InfoPage:
public class App : Application
{
public App()
{
// Ensure link to Toolkit library.
Xamarin.FormsBook.Toolkit.Toolkit.Init;
// Load previous AppData if it exists.
if (Properties.ContainsKey("appData"))
{
AppData = AppData.Deserialize((string)Properties["appData"]);
}
else
{
AppData = new AppData();
}
// Launch home page.
Page homePage = new DataTransfer6HomePage();
MainPage = new NavigationPage(homePage);
// Possibly navigate to info page.
if (Properties.ContainsKey("isInfoPageActive") &&
(bool)Properties["isInfoPageActive"])
{
homePage.Navigation.PushAsync(new DataTransfer6InfoPage(), false);
}
}
public AppData AppData { private set; get; }
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Save AppData serialized into string.
Properties["appData"] = AppData.Serialize();
// Save Boolean for info page active.
Properties["isInfoPageActive"] =
MainPage.Navigation.NavigationStack.Last() is DataTransfer6InfoPage;
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
要測試此程式,必須以App類調用其OnSleep方法的方式終止程式。如果您在Visual Studio或Xamarin Studio調試器下運作該程式,請不要從調試器終止該程式。而是在手機上終止應用程式。
也許在手機和手機模拟器上終止程式的最佳方法是首先顯示所有目前正在運作的程式:
- 在iOS上,輕按兩下“首頁”按鈕。
- 在Android上,點選(最右側)MultiTask按鈕。
- 在Windows Phone上,按住(最左側)“後退”按鈕。
此操作會導緻調用OnSleep方法。然後,您可以終止該程式:
- 在iOS上,向上滑動應用程式。
- 在Android上,将其滑動到一邊。
- 在Windows Phone上,将其向下滑動。
在視窗中運作Windows程式時,隻需單擊“關閉”按鈕即可終止程式。在平闆電腦模式下,從頂部向下滑動程式。
然後,您可以使用Visual Studio或Xamarin Studio停止調試應用程式(如有必要)。然後再次運作該程式,看它是否“記住”它停止的位置。