Windows Phone 7 – TitleContainer.OpenStream與App.GetResourceStream - 讀取XAP內資源的方法
在撰寫這篇文章之前,也許你可以想一下,在學習撰寫WP7程式的時候,你有聽過Isolated Storage,
知道Isolated Storage是Silverlight特殊的一個儲存媒介,裡面可放置任何的資料類型,只要不超過指定的大小限制。
到此,這些是在學習Silverlight寫WP7程式會有的觀念,但也許你心裡會有一個跟我一樣的疑問:
「如果專案檔中增加一個文字檔或XML檔,但不是要在程式產生時才加入到Isolated Storage中;
要直接使用,那我該怎麼取得檔案的內容?」
這樣的問題一直是我想搞清楚的,因此,下列將介紹幾個我閱讀過可以直接處理上述需求的方法:
*往下說明之前,要先了解以下所舉的範例與說明,均是以要讀取的資源已經被放置於XAP檔中,
並且該資源的Build Action屬性也設定成Content。
至於資源檔該選擇「Resource」或「Content」,這二者的差異,可以先參考該文章的說明:
<WP7 Development Tip of the Day: Understanding the difference between content and resources>
“Returns a resource file from a location in the application package.”該段所指的application package就是封裝
所有資源與Silverlight執行.dll檔的XAP檔案。然而,該方法指定的Uri參數,使用的是相對的路徑,它自動
由根目錄(Root)直接往下延伸,因此,假設XAP檔中有一資料夾Resources裡有一個圖片MyPhoto.jpg,那該
Uri的寫法為:
1: App.GetResourceStream(new Uri("Resources/MyPhoto.jpg", UriKind.Relative);
但根據MSDN上的說明,其實GetResourceStream支援三種不同的地點:
(1) Embedded in the application assembly in the application package.
(2) Embedded in a library assembly that is included in the application package.
(3) Included in the application package.
以上三個地點其實不難了解,只要能包裝在XAP裡,不管是被嵌入至Assembly檔(.dll)的資源一樣可以使用,
只需要更換相關的Uri路徑,例如:假設XAP檔中有一圖片MyPhoto.jpg,Build Action選擇Resource,那Uri要改成:
1: //[ApplicationName]代表該WP7程式的名稱(Assembly檔案名)
2: App.GetResourceStream(new Uri("[ApplicationName];component/Resources/MyPhoto.png", UriKind.Relative);
=>「;component」這個是根目錄,代表由Assemply往下的嵌入元素;
該類別被定義於「Microsoft.XNA.Framework」中,專用於協助XNA撰寫讀取指定資源的方法。常見的範例,
就是透過TitleContainer讀取XNA Game中的音樂檔或圖片檔。例如:<Streaming Data from a WAV File>。
“Provides file stream access to the title's default storage location.”這一段是MSDN對於TitleContainer的定義,
然而「storage location」指的是否與上述App.GetResourceStream中讀取的地點是一樣的呢?讓我們往下看:
根據MSDN針對XNA 4.0說明:<What Is Storage?>一文中,可了解到:
「Storage location所指的是被用於讀取/寫入資料的記憶體區域。」,由二個部分組成:
a. Title storage -
該區域是WP7預設遊戲存取空間(default game storage location),本身屬於唯讀(Read-Only)空間,內容包括:
儲存了主要可執行的程式和支援檔案(圖檔、字型、素材等)。
要存取Title storage中的內容,分成2個方法:
a-1. Content Pipeline
通常情況下,管理遊戲使用到的素材與資源都是透過Content Pipeline來負責,常見的例子就是XNA Game Studio
即是透過Content Pipeline來管理製作遊戲時所使用的資產,例如:字型、圖形元件、遊戲素材等。
為何會需要透過Content Pipeline呢?主要因為XNA不允許直接操作這些資產,因此需要透過Content Piepline在
Build-time時進行Loaded與Unloaded的任務。詳細Content Pipeline的操作可以參考:<Adding Content to a Game>。
至於要怎麼使用Content Pipeline前,我建議先了解一下Content在XNA Game Studio代表的是何種意義會比較好。
建議可以先閱讀<XNA-Content Pipeline - help- 點部落 >了解Content importer與Content processor的不同。
a-2. File Stream Access
該方法是支援上述Content Piepline不能處理的例外情形,例如:當遊戲過程中需要管理特定的遊戲資料或資源時,
就可以簡單透過File Stream Access的方法來方便處理。不過,在開發XNA上是建議使用Content Pipeline來處理所有遊
戲資源。該方法也支援程式運行時,直接透過TitleContainer存取Title Storage裡的資料。
那該怎麼存取呢?可以參考下方的二個連結:
‧Adding Game Data Files to Title Storage
‧Reading Game Data from Title Storage (透過TitleContainer.OpenStream儲存Title storage中Ready-only的資料)
b. User storage -
該存取區域屬於一個可供讀寫(Read-Write)的範圍,支援遊戲在執行期間可存取遊戲的資料,然而這些資料的存取權限,
可随需求,限制於特定的遊戲玩家或全部的玩家。
在Windows Phone中儲存這範圍的方法,由於XNA Game Studio沒有提供直接存取該空間的功能,因此,需透過Silverlight
中的(System.IO.IsolatedStorage)來處理Isolated Storage。
[注意]
在使用IsolatedStorage時,常見會透過「IsolatedStorageFile.GetUserStoreForApplication」取得IsolatedStorage Object,
但該指令如果用於XNA遊戲是Run在Windows平台上時,將會出現「InvalidOperationException」,為了避免這樣的錯誤,
需改成「GetUserStoreForDomain」來取得Object。如需發開發跨平台遊戲,需必讀該篇文章<conditional compilation symbols>,
其中一個重要的指令如下:
5:
1: #if WINDOWS
2:
3: // Execute code that is specific to Windows
4: IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForDomain();
6: #elif XBOX
7:
8: // Execute code that is specific to Xbox 360
9: IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForApplication();
10:
11: #elif WINDOWS_PHONE
12:
13: // Execute code that is specific to Windows Phone
14: IsolatedStorageFile savegameStorage = IsolatedStorageFile.GetUserStoreForApplication();
15:
16: #else
17:
18: // Print a compile-time error message
19: #error The platform is not specified or is unsupported by this game.
20:
21: #endif
在操作User Storage時, 如果開發的對象是非Windows Phone版本(如:Xbox 360, Windows 7, Windows Vista, Windows XP),
可以透過二個步驟來進行存取:
(1) 取得StorageDevice物件來連結指定的User storage device
StorageDevice代表User storage device:可能是Memory Unit(記憶卡)或是硬碟(Hard device)。
透過BeginShowSelector方法彈出個Dialog Box呈現目前該XNA遊戲支援的Storage Device清單,該方法屬於非同步(asynchronously)模式。
當用戶選擇完畢後,會觸發一個IAsyncResult或Callback方法,進行接下來要完成取得Device的功能。由於該方法是屬於非同步運作,
可透過確認該IAsyncResult是否被完成與使用EndShowSelector二個方法的合作,取得StorageDevice物件。如下程式碼:
1: //在公用變數區中宣告一個IAsyncResult變數來擷取當BeginShowSelector完成後回傳的事件
2: private IAsyncResult result;
3:
4: public Game1(){ ... 內容省略 }
5:
6: protected override void Update(GameTime gameTime)
7: {
8: if (btnChoiceDevice == ButtonState.Pressed) {
9: //使用BeginShowSelector進行StorageDevice的選擇
10: result = StorageDevice.BeginShowSelector(
11: PlayerIndex.One, null, null);
12: }
13:
14: //如果用戶選擇完畢之後該屬性會變成true
15: if (result.IsCompleted)
16: {
17: //取得StorageDevice物件
18: StorageDevice device = StorageDevice.EndShowSelector(result);
19: }
20:
21: base.Update(gameTime);
22: }
更詳細的操作說明,可以參考該篇文章<Getting a StorageDevice Asynchronously>,裡面也含有完整的Sample code。
(2) 連結User storage device進行資料的讀寫
取得StorageDevice物件之後,接下來透過StorageDevice物件的BeginOpenContainer方法開啟該Device的儲存集合,進行與StorageDevice物件連結。
在完成StorageDevice資料的存取之後,需再補上一個EndOpenContainer方法來結束連結。然後在操作Device中的資料,是透過一個重要的類別:
「StorageContainer Class」。StorageContainer是用來連結Device與操作Device中的所有資源,因此,使用前需建立一個StorageContainer物件:
1: // Open a storage container.
2: IAsyncResult result =
3: device.BeginOpenContainer("StorageDemo", null, null);
4:
5: // Wait for the WaitHandle to become signaled.
6: result.AsyncWaitHandle.WaitOne();
7:
8: StorageContainer container = device.EndOpenContainer(result);
9:
10: // Close the wait handle.
11: result.AsyncWaitHandle.Close();
然而,StorageContainer提供了熟悉操作檔案系統的功能,包括:
最常使用的就是讀取檔案的功能:OpenFile()。它回傳值為Stream,可參考以下程式片段:
1: //取得檔案內容
2: Stream stream = container.CreateFile(filename);
3:
4: //XML檔案一樣可以透過這樣的方式取得
5: XDocument tXDocument = XDocument.Load(Stream);
更詳細的操作範例可以參考<Saving Data to a Save Game File>(一樣有Sample Code)。
====
至於為何會談論到TitleContainer呢?
只要是因為我在WP7想要讀取自己加入的XML內容,使用的是XDocument類別,但在網路上看到這麼一篇文章:
<XDocument versus TitleContainer / StorageContainer>。該篇文章是在說明,該作者使用XDocument來儲存相關玩家
在WP7上遊戲的記錄,該作法可以運作於所有的平台上,然而看到一篇有談論相關在開始XBOX 360時建議使用
TitleContainer/StorageContainer來讀取相關XML的內容。
看完App.GetResourceStream與TitleContainer二個類別之後,也許你心中有個疑問:「到底我該使用那一種呢?」,
簡單的分類來說:如果今天開發的是屬於Silverlight for WP7的話,建議使用App.GetResourceStream。
因為,當開發Silverlight for WP7的程式時,引入Microsoft.XNA.Framework參考時,自動會彈出提示訊息告訴你,
使用XNA的參考在Silverlight程式中,可能會出現一些不可預期的例外。
另外,由於我們操作的資源與XNA使用的資源比較不相似,也無需管理XNA針對畫面處理等相關的問題,因此,
建議使用App.GetResourceStream即可。
但是如果今天要存取的資料是XML,那可以直接透過XDocument.Load()(System.Xml.Ling)來取得資料即可。如下範例:
1: //假設Silverlight for Windows Phone專案
2: //有一個資料夾:Resourcess;其中有一個檔案MyProfile.xml;路徑.. Resources/MyProfiles.xml
3:
4: //讀取XAP中的XML檔案
5: XDocument tXDoc = XDocument.Load("Resources/MyProfile.xml");
至於要存取Isolated Storage中的XML內容,可以參考<HOW TO:使用 LINQ to XML 對隔離儲存區進行儲存和載入>。
以上是介紹存取XAP的方法,希望對大家有些幫助,在於XNA相關的部分可能介紹的不是非常詳細,
因為我本身還沒有開發過相關XNA的程式也研究的不深,如有遺漏或寫錯的地方請指導我一下。非常感謝
References:
‧[Silverlight][Windows Phone 7] 模擬器中將測試圖片存入圖庫 & Silverlight从DLL中读取XAML
‧5+ Ways to Reduce your .Xap Size - 了解XAP檔案內容必讀
‧How to use TitleContainer in XNA
‧Windows Phone 7-下載檔案至Isolated Storage
‧binding a Linq datasource to a listbox
‧Are there any best practices for app configuration in wp7
‧HOW TO:使用 LINQ to XML 對隔離儲存區進行儲存和載入
‧Benefits of the Content Pipeline
‧WP7 Isolated Storage详解(8)-读取、保存二进制文件 (重要)