本文章是由機器翻譯。
Windows Phone 7 應用程式
使用 Windows Azure 和 Windows Phone 7 構建資料驅動的應用程式
Danilo Diaz
在過去 30 年,我們經歷了電腦硬體行業的爆炸式增長。從大型機到桌上型電腦再到手持設備,雖然硬體的體積縮小了,但功能卻越來越強大。開發人員在某種程度上有點被計算能力的這種持續增長寵壞了,現在他們希望自己為其編寫應用程式的每台設備都擁有無限的電腦資源。過去,代碼的大小和效率曾經是程式設計的重要考慮因素,很多年輕的開發人員對這段歷史沒有任何印象。
最新的開發趨勢是追隨智慧手機日益流行的腳步。在為智慧手機設備編寫代碼時,許多開發人員必須適應這樣一個現實:儘管今天的手機功能要比幾年前的設備強大很多,但還是面臨限制。這些限制與大小、處理器能力、記憶體和連線性有關。您需要瞭解在創建移動應用程式時如何突破這些限制,從而確保提供良好的性能和最佳的使用者體驗。
導致應用程式的性能不甚理想的某些原因與開發人員糟糕的設計決策有直接的關係。但在其他情況下,其中有些因素不受開發人員的直接控制。協力廠商服務較慢或離線、移動寬頻連線斷開或您所處理資料的特性(如流媒體檔或大型資料集)可能會導致應用程式性能較差。
無論原因是什麼,應用程式最終使用者感知的性能必須是任何軟體發展人員所關注的頭等大事之一。在本文中,我們將介紹一些有關以一種可提供完美使用者體驗和輕鬆縮放功能的方式設計可靠的資料驅動 Windows Phone 7 應用程式的首要注意事項。
讓我們先花一點時間設置一種方案,我們可以在這個方案中考察一些設計和編碼選擇。舉個例子,我們將使用一個虛構的旅行資訊應用程式,該應用程式提供有關使用者選擇的航班的資訊。如圖 1 所示,該應用程式的主螢幕上顯示一些資料元素,包括當前的天氣和航班狀態。您可以看到,隨著應用程式變得更具表現力且越來越以資料為中心,開發這樣的應用程式也變得更具挑戰性。在越來越多的方面,您的代碼已經無能為力了。
圖 1 航班資訊示例應用程式
UI 執行緒阻塞
首先,我們來看一下 UI。如果像對桌上型電腦編碼那樣設計應用程式,那麼很容易就會將模式搞錯,因此讓我們先瞭解一些手機特定的 UI 問題。
當應用程式未按預期對使用者命令做出回應時,此時給整體使用者體驗帶來的影響是顯著的。對滑擦、點按或擠壓操作回應緩慢可能會對應用程式的整體吸引力不利。但這些是可以預期並解決的相當簡單的問題,正如您將要看到的。
考慮使用 ListBox。當 ItemTemplate 包含圖像或從源載入資料時,UI 執行緒很有可能將被阻塞,UI 在請求或計算完成前將一直暫停。因此,當您開發 UI 時,一種方法就是在 UI 執行緒外執行長時間計算(包括 WebRequest)。實際上,這對任何應用程式(移動或非移動)來說都是一種好方法。
當您將大量的專案綁定到 ItemSource 而對注入 ListBox 控制項的專案數沒有限制時,也可能會產生性能問題。一種更好的方法是綁定一個 ObservableCollection,然後每隔 20 至 30 毫秒向該集合填充一些項。這將解除 UI 執行緒的鎖定以回應使用者。
在我們的示例應用程式中,我們還在螢幕上使用了大量圖像。ListBox 需要實際下載圖像才能顯示相應資料。這種方法看似不錯,但在 UI 執行緒上執行此工作將阻止使用者進行任何手勢輸入。在後臺執行緒上載入圖像將解決很多記憶體要求和釋放 UI 執行緒方面的問題,同時也使應用程式速度加快。
必須呈現我們向使用者顯示的全部內容。呈現需要佈局、對齊和計算才能正確顯示。隨著越來越多的層添加到 UI 中,計算和整體呈現成本也隨之增加。儘管 Silverlight 已虛擬化 UI,但未虛擬化要綁定的資料。這意味著,如果我們將 10,000 個專案綁定到 ListBox,Silverlight 將需要產生實體所有 10,000 個 ListItem,然後它們才會呈現出來。
請注意您正在資料綁定的專案並保持綁定集盡可能小。如果需要處理大型資料繫結項目目集,請考慮在後臺動態處理呈現。當然,桌面應用程式同樣如此,只是這些選擇的影響在手機上有所擴大而已。
ValueConverter 可能會對呈現性能產生巨大的影響,因為它們是使用自訂代碼定義的,無法在實際元素呈現和佈局之前預先確定和緩存呈現。
處理資料
接下來,我們需要討論 Windows Phone 7 中的資料存儲。讓我們直奔主題:沒有任何關聯式資料庫引擎可供開發人員使用。SQL Server Compact (SQL CE) 隨 Windows Phone 7 作業系統一起安裝,但當前沒有任何 API 可供開發人員使用。因此,創建一個資料庫用來存儲應用程式資料(在我們的示例中為旅行資訊)行不通。
也就是說,可使用各種不同的選項使資料進出我們的應用程式。常用方法是使用雲服務(如 Windows Azure)來持久保留資料。用於生成應用程式的服務層的技術有很多,REST 和 SOAP 是最受歡迎的。很多開發人員都首選 SOAP,但我們認為 REST 可提供一種更高效且更易於實現的方法用來生成資料請求。
我們採用幾種方法向應用程式提供資料,通過使用下麵這樣的 REST 運算式可以訪問這些方法:
/Trip/Create/PHL-BOS-SEA/xxxx/2010-04-10
/Flight/CheckStatus/US743
利用 REST,我們可將 XML 或 JSON 用作消息格式。
從 Web 前端的角度出發,我們選擇了 ASP.NET MVC 框架 (asp. net/mvc),因為它允許我們使用自訂視圖處理請求並返回任何類型的標記。
我們的示例應用程式需要處理旅行和航班資訊,因此,我們創建一個 FlightController 和一個 TripController,用來截獲對此類資訊的請求:
// GET: /Flight/CheckStatus/US743
public ActionResult CheckStatusByFlight(
string flightNumber) {
return CheckStatus(flightNumber, DateTime.Now);
}
// GET: /Flight/RegisterInterest/US743/2010-04-12
public ActionResult CheckStatus(
string flightNumber, DateTime date) {
Flight f = new Flight(flightNumber, date);
GetFlightStatus(f);
return new XmlResultView<Flight>(f);
}
為了提供簡化的存取方法並節省幾個位元組的頻寬,若日期是今天,我們可能會設計一種快捷方法用來獲取此資料,而不用隱式指定今天的日期。
緩存和持久的資料
航班狀態服務是我們應用程式中不受我們控制的一個元素,因此它將是性能難題的一部分。由於一個成功的應用程式可能會接收到相當多的請求,因此考慮使用緩存策略十分重要。
通常,航班越臨近起飛,對航班資訊的請求數量預計也會越來越多。較多的幾乎併發的請求不僅影回應用程式的性能,還會影響存儲和運算元據關聯的成本。一般而言,Windows Azure 應用程式會累計請求和返回時的頻寬費用,而航班資訊服務也會帶來訪問費用。返回的資料量需要不超過應用程式所需的數量。
Windows Azure 平臺提供範圍廣泛的資料存儲選項,從表、Blob 和佇列到通過 SQL Azure 實現的類似關聯式資料庫的存儲。我們決定使用 SQL Azure,因為它使用熟悉的 SQL Server 程式設計技術,並且使我們能夠輕鬆存儲和訪問緩存的航班資料和持久的旅行資訊。
圖 2 顯示我們使用“實體框架”設計的簡單存儲層。
圖 2 航班資料存儲架構
返回資料
我們通過自訂視圖將資料返回到用戶端。由於我們使用的是 ASP.NET MVC,因此每個視圖都需要從 ActionResult 派生並實現 ExecuteResult。
前面曾提到,我們可以通過 REST 服務提供 XML 或 JSON 表示形式的航班資訊。首先,我們看一下 XML 選項。生成 XML 的序列化程式需要一種類型,因此我們創建一個泛型類,如圖 3 中所示。
圖 3 序列化 XML
public class XmlResultView<T> : ActionResult {
object _model = null;
public XmlResultView(object model) {
this._model = model;
}
public override void ExecuteResult(ControllerContext context) {
// Create where to write
MemoryStream mem = new MemoryStream();
// Pack characters as compact as possible,
// remove the decl, do not indent.
XmlWriterSettings settings = new XmlWriterSettings() {
Encoding = System.Text.Encoding.UTF8,
Indent = false, OmitXmlDeclaration = true };
XmlWriter writer = XmlTextWriter.Create(mem, settings);
// Create a type serializer
XmlSerializer ser = new XmlSerializer(typeof(T));
// Write the model to the stream
ser.Serialize(writer, _model);
context.HttpContext.Response.OutputStream.Write(
mem.ToArray(), 0, (int)mem.Length);
}
}
我們同樣可以輕鬆地將 JSON 用於我們的資料。 解決方案中唯一會改變的元素將是 ExecuteResult 方法的內容。 使用 JsonResult,只需幾行代碼即可從我們的服務中生成 JSON 返回結果:
// Create the serializer
var result = new JsonResult();
// Enable the requests that originate from an HTTP GET
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
// Set data to return
result.Data = _model;
// Write the data to the stream
result.ExecuteResult(context);
將資料保存到實際設備中會怎麼樣? 每次使用者需要訪問旅行資訊時都強制應用程式從服務中提取資料沒有意義。 雖然 Windows Phone 7 中不存在關係資料存儲,但開發人員卻可以訪問一種稱為“獨立存儲”的功能。 此功能的工作方式與 Silverlight 4 獨立存儲相似,但沒有大小限制。
在手機上保存和檢索資料需要兩種主要方法:SaveData 和 GetSavedData。 圖 4 中所示示例演示我們如何將這兩種方法用於航班資訊應用程式。
圖 4 保存並檢索本地資料
public static IEnumerable<Trips> GetSavedData() {
IEnumerable<Trips> trips = new List<Trips>();
try {
using (var store =
IsolatedStorageFile.GetUserStoreForApplication()) {
string offlineData =
Path.Combine("TravelBuddy", "Offline");
string offlineDataFile =
Path.Combine(offlineData, "offline.xml");
IsolatedStorageFileStream dataFile = null;
if (store.FileExists(offlineDataFile)) {
dataFile =
store.OpenFile(offlineDataFile, FileMode.Open);
DataContractSerializer ser =
new DataContractSerializer(
typeof(IEnumerable<Trips>));
// Deserialize the data and read it
trips =
(IEnumerable<Trips>)ser.ReadObject(dataFile);
dataFile.Close();
}
else
MessageBox.Show("No data available");
}
}
catch (IsolatedStorageException) {
// Fail gracefully
}
return trips;
}
public static void SaveOfflineData(IEnumerable<Trips> trip) {
try {
using (var store =
IsolatedStorageFile.GetUserStoreForApplication()) {
// Create three directories in the root.
store.CreateDirectory("TravelBuddy");
// Create three subdirectories under MyApp1.
string offlineData =
Path.Combine("TravelBuddy", "Offline");
if (!store.DirectoryExists(offlineData))
store.CreateDirectory(offlineData);
string offlineDataFile =
Path.Combine(offlineData, "offline.xml");
IsolatedStorageFileStream dataFile =
dataFile = store.OpenFile(offlineDataFile,
FileMode.OpenOrCreate);
DataContractSerializer ser =
new DataContractSerializer(typeof(IEnumerable<Trip>));
ser.WriteObject(dataFile, trip);
dataFile.Close();
}
}
catch (IsolatedStorageException) {
// Fail gracefully
}
}
處理網路故障
移動設備使用的網路可能有各種可變的連接,有時會因位置、擁塞甚至是使用者手動斷開連接而變得完全不可用(例如,當使用航班模式時)。作為生活中的現實情況,您必須接受這一點。作為移動應用程式開發人員,我們必須在生成應用程式時將這點考慮在內。
另一種類型的網路故障是服務層失敗。很多移動應用程式都使用協力廠商服務的資料。這些服務可能沒有附帶服務級協定,這樣您的應用程式就受控于提供商了。換句話說,它不受您的控制,您必須準備好處理中斷情況。
無論網路故障的來源是什麼,您都仍然需要盡可能提供最佳使用者體驗。發生任意類型的網路故障時,您都需要提供某一級別的功能。對於我們的航班狀態應用程式,這意味著我們希望允許使用者訪問盡可能多的資訊,即使在伺服器或用戶端的網路連接斷開的情況下。
可通過多種方法實現這一點。現在,我們將集中討論三種可用來實現這一點的簡單方法:在資料可用時獲取資料,本地緩存資料,在您控制的伺服器上緩存資料。
使用推送通知
當使用者將旅行資訊輸入到應用程式時,該資訊將上載到雲服務。然後,該服務將不斷輪詢提供其航班和天氣資料的各種服務。它還查找隨時間變化的資料更改,如航班狀態更改或報告延誤的機場。
發現更改時,您需要盡可能迅速高效地將該資訊提供給使用者。為此,一種方法是讓該服務將資訊推送至用戶端應用程式。這將在資料變得可用時為使用者提供對最新可用資料集的訪問。由於將資料推送至了用戶端,因此即使使用者丟失其網路連接,資料也可用。
我們可借助 Windows Azure 服務通過使用 Windows Phone 推送通知實現這一點。Windows Phone 推送通知功能由三部分組成:監視服務、Microsoft 推送通知服務和消息處理方法。
監視服務是一種雲服務,它不斷查找有關我們的應用程式的新資訊。我們將在稍後詳細討論這個問題。
推送通知服務是 Microsoft 託管服務的一部分,用於將消息中繼到 Windows Phone 7 設備。該服務可供所有 Windows Phone 7 應用程式開發人員使用。
消息處理常式方法執行其名稱所暗示的操作:只接收來自推送通知服務的消息。
Windows Phone 7 中存在三種預設通知類型:Tile、Push 和 Toast 通知。通知是使用者體驗中的重要組成部分,您需要仔細考慮它的使用方式。重複通知或侵入式通知會降低您的應用程式及設備上運行的其他程式的性能。這些通知還會打擾使用者。請考慮發送通知的頻率以及您希望引起使用者注意的事件類型。
在 Windows Phone 7 中,通知是通過批次處理方式傳遞的,因此事務可能不是即時的。通知的及時性將得不到保證,而且將由該服務決定如何將通知傳遞給用戶端;該服務會盡力確定要多快才能將消息傳遞到手機。
推送通知的工作流是:
- 用戶端應用程式請求與推送通知服務建立通道連接。
- 推送通知服務使用通道 URI 回應。
- 用戶端應用程式向監視服務發送包含推送通知服務通道 URI 以及負載的消息。
- 當監視服務檢測到資訊更改時(在我們的示例應用程式中為航班取消、航班延期或天氣警報),它會終止向推送通知服務發送消息。
- 推送通知服務將消息中繼到 Windows Phone 7 設備。
- 消息處理常式處理設備上的消息。
本地緩存資料
使資料可用於應用程式的另一種方法是本地緩存資料,這樣 UI 中將始終存在一些資料。然後,您可以通過其他方式在後臺更新本地資料(如果可能)。此方法的好處是,應用程式在載入後迅速可用,即使必須在後臺以非同步方式更新資訊也是如此。
簡而言之,使用“獨立存儲”可保存最新的資料集。當應用程式打開時,它立即獲取本地“獨立存儲”中可用的所有資料並進行呈現。與此同時,應用程式還調用 Windows Azure 服務來獲取更新的資訊。如果發現新資訊,則序列化新資訊並傳送至設備,獨立存儲得到更新,而您再次用更新後的資訊呈現 UI。為了獲得更好的使用者體驗,您可能需要在 UI 中指定刷新資訊的時間和日期。
順便提下,如果應用程式使用的是 Model-View-ViewModel (MVVM) 設計模式,則可通過 Silverlight 資料綁定功能自動更新 UI。有關 MVVM 和 Silverlight 的詳細資訊,請參閱 Robert McCarter 的文章“使用 Model-View-ViewModel 的問題和解決方案”(位於 msdn.microsoft.com/magazine/ff798279 上)。
在伺服器上緩存資料
在資料變得可用時將資料直接推送到應用程式與將資料存儲在設備上之間還有一個中間過程:從協力廠商服務獲取資料,並將資料緩存在雲應用程式中,直到 Windows Phone 7 應用程式請求資料。
這項技術要求在應用程式中有一個新的抽象層。實際上,這裡的目標是從應用程式中刪除協力廠商服務的依賴項。您的服務提取並緩存協力廠商服務依賴項的資料。如果協力廠商服務出現故障,您至少在緩存中有一些資料可提供給設備上的應用程式。
像這樣的服務很容易克隆或擴展,以便從各種服務中提取資料,這樣可減少對任一供應商或資料來源的依賴,從而使得更換供應商容易多了。
有關如何在 Windows Azure 中設置注重資料的解決方案的詳細資訊,請參閱 Kevin Hoffman 和 Nathan Dudek 發表的“使用 Windows Azure 存儲增強應用程式的引擎”(msdn.microsoft.com/magazine/ee335721)。此外,Paul Stubb 的文章“創建用於 SharePoint 2010 的 Silverlight 4 Web 部件”雖然未直接重點介紹 Windows Phone 7 方案,但對於瞭解 Silverlight 和 Web 服務的資料綁定設計來說也是一篇好文章 (msdn.microsoft.com/magazine/ff956224)。
監視服務
前面曾提到,通知功能是航班狀態應用程式的重要組成部分。這項功能實際由該應用程式中的幾種不同服務組成。監視服務對於該應用程式發揮作用可能最為重要,該服務定期輪詢協力廠商資料服務,並將像航班延期、機場延誤和天氣警報這樣的資訊中繼到設備。
在我們的應用程式中,監視服務讀取航班和機場代號的當前清單並使用此資訊來收集相關資料。然後,將此資訊作為緩存項存儲回 SQL Azure 資料庫中,以便可由前面所示的 /Flight/CheckStatus 服務檢索。我們的監視服務是使用 Windows Azure 工作者角色實現的。此工作者角色的主要目標是獲取有關航班延誤和機場狀態的狀態資訊,方便每個使用者收集航班資訊。隨著安排的航班接近起飛時間,更新頻率隨之增加。
有關如何實現這類服務的一些經驗,請務必查看 CodePlex (azurepubsub.codeplex.com) 上的“Azure 發佈-訂閱”專案,或者閱讀 Joseph Fultz 的博客文章“將 Windows 服務遷移到 Azure 工作者角色:使用存儲的映射轉換示例”(bit.ly/aKY8iv)。
綜合講述
希望我們已經向您綜述了設計資料驅動的 Windows Phone 7 應用程式時需要考慮的問題。UI 回應和及時訪問資料來源有助於實現完美的應用程式使用者體驗。
若要更深入學習,請先閱讀 Joshua Partlow 的文章“Windows Phone 開發工具入門”(msdn.microsoft.com/magazine/gg232764)。您還需要查看 Jim Nakashima、Hani Atassi 和 Danny Thorpe 發表的文章“如何在 Visual Studio 2010 中開發和部署 Windows Azure 應用程式”(msdn.microsoft.com/magazine/ee336122)。
若要將 Windows Azure 和 Windows Phone 7 開發結合在一起,請閱讀 Ramon Arjona 的文章“Windows Phone 與雲 - 簡介”(msdn.microsoft.com/magazine/ff872395)。
Danilo Diaz 是 Microsoft 在大西洋中部各州的開發推廣人員。他的職責是説明開發人員瞭解 Microsoft 產品服務和策略。
Max Zilberman* 是在紐約和大西洋中部各州的架構推廣人員。在加入 Microsoft 前,Zilberman 已經在一家頂級健康保險公司擔任過各種高級技術職位。*
衷心感謝以下技術專家對本文的審閱:Ramon Arjona