用檔案及 URI 命名方式自動啟動

綜述

你可以在設計 App 時,將程式設定為由另個 App 啟動來查看特定的檔案或瀏覽特殊的 URI。這種功能也應用在當 email 應用程式需要開啟一個 PDF 附加檔案、社群網路應用程式要瀏覽另個網頁時,以及其它很多情況。

這種情況下,Windows Phone 8 提供了根據檔案及 URI 關聯格式名稱自動啟動的功能。每個 App 都可以宣告能夠處裡的 URI 格式名稱或文件類型,並在需要時將它啟動。

URI 格式名稱是代表多種應用程式共用的溝通主題的關鍵字,如果你使用好幾個 App 處理關於飛機航班、租車、酒店住宿,可以使用“旅行 (travel)”來當作他們的 URI 格式名稱。

根據檔案及 URI 關聯格式名稱來自動啟動 App,是設計用來提高使用者體驗、提供應用程式外簡單的解決方案,以及整合開發時未知的第三方 App。

本實驗將示範如何在 ContosoCookbook 使用自動啟動文件和 URI 關聯格式名稱,並解釋如何測試這種 App。

App 最初的版本位在 Sources\EX1\Begin下方的實驗安裝資料夾,完成實驗後,在功能上等同於位在 Sources\EX2\End 的版本

目標

此實驗將引導我們達到下列目標

  • 將你的 App 連結到 URI 格式名稱

  • 將你的 App 連結到文件類型

必備條件

需具備下方的條件以獲得更多動手實驗的成果:

  • Microsoft Visual Studio Express 2012 的 Windows Phone 版本可由後方連結下載或安裝微軟的 Visual Studio2012 與 Windows Phone SDKTODO: Find out the link……

  • 之前的 Windows Phone 開發經驗。

實驗架構

本實驗包含兩個練習並完成以下任務:

  1. 設定以 URI 關聯格式名稱啟動 App

  2. 設定以文件類型啟動 App

  3. 執行特定的啟動功能

  4. 測試 App

預計完成時間

至少需要 30 分鐘完成這個實驗。

練習 1

這個練習旨在將使用 URI 關聯格式名稱啟動 App 的功能加入 ContosoCookbook App。共分成三個任務 : 註冊 URI 格式名稱、設定 URI 對應、測試 App。

任務 1– 設定 URI 關聯格式名稱

每個使用 URI 關聯的 App 會宣告它可以處理的 URI 格式名稱。ContosoCookbook App 陳列食譜,所以 App 要處裡“ recipe ”URI 格式名稱。為了處裡 URI 格式名稱,ContosoCookbook App 要使用“ recipe:// ”  URI 格式。

  1. 瀏覽到實驗室安裝資料夾 Source\EX1\Begin,並打開 Visual Studio 2012 中的啟動方案檔案 ContosoCookbook.sln。

    為了得到 App 功能、編譯、配置和運作的初步印象。App 協助使用者找到有興趣或需要的食譜。例如:App 首頁 — MainPage.xaml — 讓使用者可以挑選食譜種類以過濾選擇要的食譜。

    挑選食譜種類後,使用者瀏覽到下個頁面 — GroupDetailPage.xaml。這個頁面使用 Pivot 控制項來陳列兩種資訊:美食類別介紹與類別下擁有的食譜數量  / App 提供的食譜或使用者在 App 內購買的食譜 (另一部分研討討論到的)。

    最後,RecipeDetailPage.xaml 頁面會顯示一個單一的食譜內容,當 URI 關聯格式名稱啟動 App,這個頁面負責成列出單一食譜內容。

  2. 要建立一個 URI 關聯格式名稱,我們需要修改應用程序的清單。在方案總管中,展開 Properties 資料夾並找到 WMAppManifest.xml 檔案,按右鍵打開選單後選取 Open with 選項。

    圖 1
    打開選單項目

  3. 在“Open With”選項中選擇 XML (text) Editor,並按下 OK。

    圖 2
    打開 With XML (Text) Editor

  4. 找到“Tokens” XML 元素並增加以下代碼到“</Tokens>”右後方:

    XML
    <Extensions>
    <Protocol Name="recipe" TaskID="_default"
    NavUriFragment="encodedLaunchUri=%s" />
    </Extensions>

    這個代碼將 ContosoCookbook App 註冊成可以處理“recipe” URI 格式名稱

    註  : “擴展”元素必須加在“Tokens”元素後

    註  : 你的 App 不能註冊任意的 URI 格式名稱,部分文件和 URI 格式名稱關聯會被保留。如果之後你的 App 註冊了一個被保留的關聯,註冊將被忽略。MSDN 說明文件有完整列出已被保留的所有 URI 格式名稱。

任務 2 – 設定 URI 對應

先前的任務中,我們設定了 App 以“recipe://”URI 格式來處理開啟要求。在運行時,應用程序必須支援 URI 啟動的要求及確認 URI 是否使用了正確的 URI 格式。如果 URI 不符規則,應用程式則不動作,若符合規則,應用程序將需求重新導向到一個陳列食譜內容的內頁。

  1. 找到方案總管中的 "Common" 資料夾。

  2. 按右鍵點擊 "Common" 資料夾、點選 Add,然後點選 Class:

    圖 3
    增加一個新的Class

  3. 更改 CookbookUriMapper.cs 檔案的 class 名稱,並點選 OK

    圖 4
    在 Add New Item 對話框中幫新的 Class 命名

  4. 增加一個 “using” 陳述式到檔案中,宣告我們要使用的命名空間:

    C#
    using System.Windows.Navigation;
    using System.Net;

  5. 這個新的 class 將被註冊為 App 的 URI 對應,代表必須從 UriMapperBase 類別中被導出

    C#
    class CookbookUriMapper : UriMapperBase
    {

    }

  6. 加入稍後 class 方法要使用的資料成員:

    C#
    private static string TargetPageName = "RecipeDetailPage.xaml";
    private static string ProtocolTemplate = "/Protocol?encodedLaunchUri=";
    private static int ProtocolTemplateLength = ProtocolTempFlate.Length;

    private string tempUri;

  7. UriMapperBase 是一個抽象的類別包含單一的抽象方法,MapUri 被衍生的類別覆寫。用以下代碼覆寫此方法:

    C#
    public override Uri MapUri(Uri uri)
    {
    tempUri = uri.ToString();

    if (tempUri.Contains("/Protocol"))
    {
    tempUri = HttpUtility.UrlDecode(tempUri);

    if (tempUri.Contains("recipe:"))
    {
    return GetMappedUri(tempUri);
    }
    }
    return uri;
    }

    前方的代碼部分具有兩種用來處理發出請求的 URI:

    • App 會使用“recipe” URI 格式名稱啟動,URI 有下列格式:

      {URI scheme name}:{app-specific parameters}

      註 : Contoso Cookbook App 採用了經典的 URI 格式進行參數傳遞,與 Web 瀏覽器提供的 URI 格式類似: ?{parameter}={value}&{parameter}={value}. 你的 App 可以使用不同的方式來編參數碼,並不限制名稱/值對。

      當 URI 關聯格式名稱啟動 App,包含“/Protocol”字串的 URI 參數即為 App 判定 URI 關聯的關鍵。

      URI 對應器確定 App 應該接受要求,將導向到 App 的內頁 (在下一步驟中描述)。

    • 在 App 無法接受請求的情況下,對應器會傳回完整的 URI 避免中斷瀏覽流程。

  8. 加入下面的 helper 方法進行 URI 比對。提供特別針對 ContosoCookbook App 的私自比對功能。

    C#
    private Uri GetMappedUri(string uri)
    {
    string operation = "";
    string groupUID = "";

    // Extract parameter values from URI.
    if (uri.IndexOf(ProtocolTemplate) > -1)
    {
    int operationLen = uri.IndexOf("?", ProtocolTemplateLength);
    int groupIdLen = uri.IndexOf("=", operationLen + 1);

    operation = uri.Substring(ProtocolTemplateLength, operationLen - ProtocolTemplateLength);
    groupUID = uri.Substring(groupIdLen + 1);
    }

    string NewURI = String.Format("/{0}?ID={1}", TargetPageName, groupUID);

    return new Uri(NewURI, UriKind.Relative);
    }

    上面的方法確保 URI 是應用程式可以識別的固定樣式,並從中取得食譜操作及美食類別參數,之後傳回新的有特定參數的 URI,將應用程式導向內頁 (RecipeDetailsPage.xaml)。

  9. 只實作一個 URI 對應是不夠的,我們應該指示系統使用我們 App 的 URI 對應功能。

    註冊 URI 對應,打開 App.xaml.cs 檔案,找到 InitializePhoneApplication 方法,並加入下方代碼到註冊 RootFrame.Navigated 事件以及 RootFrame.NavigationFailed 事件之間。

    C#
    RootFrame.UriMapper = new CookbookUriMapper( );

  10. 最後,當被 URI 關聯格式名稱啟動時,我們應該初始化應用程式的數據,打開 RecipeDetailPage.xaml.cs 檔案並找到 OnNavigatedTo 方法,增加 async 關鍵字更改方法的簽名如下:

    C#
    protected async override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
    ...

  11. 在呼叫 NavigateToRecipe (UniqueId) 前插入以下代碼:

    C#
    if (!App.Recipes.IsLoaded)
    await App.Recipes.LoadLocalDataAsync( );

    NavigateToRecipe(UniqueId);

    當 App 被 URI 關聯格式名稱啟動,這確保了食譜資料被載入。確保初始化發生後,App 呼叫 NavigateToRecipe (UniqueId) 並如常運作。

    註 : 前方代碼使用兩個新的 C#5.0 關鍵字 -  async 和 await。對在 C# 內執行非同步的 App 非常重要的描述語言功能,因為超出這個研討的內容。可以由以下連結找到更多 C#5.0 的相關資訊,包含新語言的關鍵字、一般的非同步程式設計: https://msdn.microsoft.com/en-us/library/hh191443.aspx

  12. 應用程式現在被宣告為食譜 URI 格式名稱處理器並可供測試 (下一階段會提到)。

任務 3 – 測試 App

在寫完代碼設定處理 URI 格式名稱關聯的要求後,我們需要從外部模擬這些請求,有三種方式模擬啟動請求:

  • **更改項目啟動瀏覽。**預設情況下應用程式瀏覽 MainPage.xaml,更改為瀏覽 URI 格式名稱以確定 URI 對應是否正確設定,這個測試方法非常方便,但不適合測試應用程式清單中設定的URI格式名稱關聯,當提供一個適當格式化的 URI 格式名稱時,也無法分辨操作系統是否該呼叫 App。

  • **新建另一個應用程式啟動有“recipe://” 格式的 URI。**這種方法可以透過操作系統整合包含 ContosoCookbook App,從第三方 App 測試整個流程,缺點是進行除錯的解決方案:為 ContosoCookbook 應用程式除錯,你必須運作 App 然後開啟另一個應用程式觸發“recipe://” URI 格式名稱啟動功能。

  • **使用瀏覽器發出請求。**這個方法必須在內建的 IE 瀏覽器建立並瀏覽一個線上網頁,即使這可以測試整個流程,但需要廣泛的準備工作。

    註:由於這種方法是比較進階並且需要複雜的設置 (例如建造網頁),接下來的研討中將不列入討論。

用第一種方法,我們更改在應用程式清單中 (WMAppManifest.xml 檔案) 預設的瀏覽代碼來模擬要求:

  1. 找到 Tasks XML 元素並從當中找出 DefaultTask 元素

  2. 更改 DefaultTask 元素如下:

    XML
    <DefaultTask Name="_default" NavigationPage="/Protocol?encodedLaunchUri= recipe://ShowRecipe?ID=1003" />

  3. 執行 App 將呈現下方頁面。

    圖 5
    URI 關聯格式名稱啟動的食譜頁面

    使用這種方法,我們改變了應用程式的啟動設定及設置了發送協議請求給 URI 比對器讓系統啟動 App。但我們還沒有在系統層級測試應用程式的 URI 關聯格式名稱,我們需繼續尋找其他的測試替代品。

    註:記得要還原 DefaultTask 節點中的改動。

第二個測試方式是以新建一個附加 Windows Phone 8 應用程式來模擬:

  1. 新建一個新的 Windows Phone 應用程式

  2. 增加一個單獨的按鈕到主頁

  3. 在你的隱藏檔案中使用一個事件處理器訂定新按鈕的點擊事件

  4. 透過提供一個客製的 URI 到作業系統來讓你的新 App 可以自動啟動原始 App。要做到這一點,要找到並呼叫 Windows.System 命名空間中 Launcher 類別的 LaunchUriAsync (Uri) 靜態方法如下:

    C#
    Uri navigationUri = new Uri("recipe://ShowRecipe?ID=1003");
    Windows.System.Launcher.LaunchUriAsync(navigationUri);

  5. 執行新的應用程式然後單擊按鈕。ContosoCookbook App 將啟動,就會顯示出圖 3 的食譜頁面。

    使用這種方法,我們驗證了註冊 “recipe”URI 格式名稱處理器到作業系統層級中的 App。現在作業系統可以藉著 URI 格式名稱由另一個 App 啟動原來的 App。

    註:如果有一個以上的 App 被連結到相同的 URI 格式名稱,會打開下方的對話框提示用戶選擇正確的 App:


    圖 6
    從使用相同 URI 格式名稱的 App 清單中挑選一個 App

  6. 這階段的練習即結束

練習 2

本練習將介紹如何啟動你的應用程式並由另一個應用程式觸發打開文件。這功能被稱為文件的關聯,因為你的應用程式將宣告擴充可以處裡的文件而作業系統將根據關聯發送請求給應用程式。

任務 1 – 註冊文件關聯

與先前的練習類似,我們必須宣告支援檔案擴充。這一階段我們將作 ContosoCookbook App 與“.recipe”和 “.rcp”檔案連結。首先,我們必須據此修改應用程式清單。

  1. 打開 Solution/EX2/Begin 資料夾中的方案,或者開啟先前練習中的方案

  2. 展開 Properties 資料夾並利用 XML 編輯器打開 WMAppManifest.xml 檔案

  3. 瀏覽到 Extensions XML 元素並增加以下內容到 Protocol 元素前方:

    XML
    <FileTypeAssociation Name="RecipeLaunch" TaskID="_default"
    NavUriFragment="fileToken=%s">
    <SupportedFileTypes>
    <FileType>.rcp</FileType>
    <FileType>.recipe</FileType>
    </SupportedFileTypes>
    </FileTypeAssociation>

    註:Windows Phone8 可以從不同的地點打開文件,如果你的應用程式支援打開儲存在外部記憶卡(SD卡)的檔案,你應該在 FileType 註冊一個 ContentType 如下:
    <FileType ContentType="application/recipe">.recipe</FileType>

    在新添加的設定中,根元素是 FileTypeAssociation,它必須是擴充元素的直接子元素(這裡沒有顯示)並包含 App 檔案類型關聯-你可以在 SupportedFileTypes 元素下方以 FileType 元素為每個擴充宣告,最多可宣告達 20 個檔案擴充。

    FileTypeAssociation 元素描述了一組共享相同內容類型和 LOGO 圖示的檔案擴充。

    你還可以使用應用程式清單宣告檔案類型的標誌,雖然這不是強制性的。但要註明標誌圖像,則增加三個標誌尺寸的聲明:

    Small (小) : 33 X 33, 用於email夾檔。
    Medium (中) : 69 X 69, 用於Office文件顯示。
    Large (大) : 176 X 176, 在Web瀏覽器供下載。

    下面的文件關聯定義使用的標誌圖像是在前面的範例中所加入的:

    XML
    <FileTypeAssociation Name="RecipeLaunch" TaskID="_default" NavUriFragment="fileToken=%s">
    <Logos>
    <Logo Size="small">SmallFileIcon.png</Logo>
    <Logo Size="medium">MediumFileIcon.png</Logo>
    <Logo Size="large">LargeFileIcon.png</Logo>
    </Logos>
    <SupportedFileTypes>
    <FileType>.rcp</FileType>
    <FileType ContentType="application/recipe">.recipe</FileType>
    </SupportedFileTypes>
    </FileTypeAssociation>

    註:類似於 URI 格式,有些檔案擴充會被保留並連結到操作系統及內建應用程式。在 MSDN 說明文件中有被保留的檔案擴展的完整列表。

任務 2 – 設定檔案關聯的 URI 對應

在這個任務中,我們設定 URI 比對器來確定文件關聯的請求,並導向到 ContosoCookbook App 中適當的頁面。也增加了片段程式碼來讀取文件的內容並顯示所要求的食譜。

  1. 方案總管中,展開 Common 資料夾並打開 CookbookUriMapper.cs 檔案。

  2. 在 CookbookUriMapper 類別中增加以下資料成員

    C#
    private static string FileTemplate = "/FileTypeAssociation?fileToken=";

    這個 FileTemplate 用來識別 URI 是否為要求的檔案類型。

  3. 找到 MapUri 方法並增加以下代碼到“tempUri = uri.ToString( )”陳述式後方:

    C#
    if (tempUri.Contains("/FileTypeAssociation"))
    {
    tempUri = HttpUtility.UrlDecode(tempUri);

    if (tempUri.Contains("fileToken"))
    {
    return GetFileMappedUri(tempUri);
    }
    }

    代碼檢查所提供的 URI 是否表示一個文件關聯請求,並呼叫 GetFileMappedUri 以導向 App 中適當的頁面。

  4. 在類別中增加 GetFileMappedUri 方法如下:

    C#
    private Uri GetFileMappedUri(string uri)
    {
    string fileToken = "";

    // Extract parameter values from URI.
    if (uri.IndexOf(FileTemplate) > -1)
    {
    int groupIdLen = uri.IndexOf("=", 0);
    fileToken = uri.Substring(groupIdLen + 1);
    }

    string NewURI = String.Format("/{0}?ID={1}&Command=HandleFile",
    TargetPageName, fileToken);

    return new Uri(NewURI, UriKind.Relative);
    }

    這代碼將要求導向 RecipeDetailPage.xaml 頁面,它會讀取該文件的內容和顯示食譜。

  5. 打開 RecipeDetailPage.xaml.cs 檔案並增加以下 using 陳述式到檔案開端:

    C#
    using Windows.Phone.Storage.SharedAccess;
    using Windows.Storage.Streams;
    using System.Xml.Linq;
    using Windows.Storage;

  6. 接下來,因為 UriMapper 將請求導向到這個頁面,並呼叫 OnNavigatedTo 方法。更換“string UniqueId = NavigationContext.QueryString["ID"];”陳述式並加入下方代碼:

    C#
    string UniqueId = "";

    if (NavigationContext.QueryString.ContainsKey("Command"))
    {
    string fileToken = NavigationContext.QueryString["ID"];
    var filename = SharedStorageAccessManager.GetSharedFileName(fileToken);

    var file = await SharedStorageAccessManager.CopySharedFileAsync(
    ApplicationData.Current.LocalFolder,
    fileToken + ".rcp", NameCollisionOption.ReplaceExisting,
    fileToken);

    var content = await file.OpenAsync(FileAccessMode.Read);
    DataReader dr = new DataReader(content);
    await dr.LoadAsync((uint)content.Size);

    //Get XML from file content
    string xml = dr.ReadString((uint)content.Size);

    //Load XML document
    XDocument doc = XDocument.Parse(xml);
    XName attName = XName.Get("ID");
    XAttribute att = doc.Root.Attribute(attName);

    //Get UniqueId from file
    UniqueId = att.Value;
    }
    else
    UniqueId = NavigationContext.QueryString["ID"];

    這個代碼使用來自 Windows.Phone.Storage 命名空間的 SharedStorageAccessManager 類別來檢索 App 收到的檔案名稱,然後,代碼從分享位置複製檔案到可瀏覽的 App 儲存設備中。

    註:在進入前,你必須將系統使用的要做關聯的文件複製到系統提供的共享位置內。

    將文件複製好後,打開文件並下載內容。在 ContosoCookbook App 中,食譜檔案是個 XML 檔案,具有以下格式:

    XML
    <?xml version="1.0" encoding="utf-8" ?>
    <Recipe ID="1003"/>

    註:文件的格式和內容完全由應用程式決定。你的應用程式可能會使用完全不同的文件格式,這意味著複製文件的下載碼將不同於上述內容。

    App 對 XML 類別使用 LINQ (如 XDocument及XName) 從 XML 檔案中提取食譜 ID,一旦食譜 ID 被提取,App 則進行先前練習中的步驟。

任務 3 – 測試檔案關聯

有三種方法可以測試檔案關聯:

  • **傳送一個夾檔的 email。**例如:當附加一個 PDF 檔案在 email 中,使用者可以點擊 PDF 檔按圖示 (小圖示) 來觸發系統尋找檔案關聯,如果有多個關聯存在,使用者可以選擇要用哪一個 App,當只有一個註冊的 App,那個 App 將會被自動呼叫出。

    如果裝置內沒有註冊的 App 符合條件,Windows Phone 操作系統將顯示一個標準的系統對話框如下圖:


    圖 7
    系統對話框會建議從商店中搜尋

    註:雖然可以在 Windows Phone Emulator 中設定一個 email 帳號,但這需要多個設定步驟,在此研討中將不討論。

  • **從 Web 瀏覽器下載檔案。**下載檔案後,點擊檔案的圖示 (大圖示),則啟動檔案連結瀏覽。

    註:由於這種方法是比較進階的且需要複雜的設置,不會進一步在本實驗中討論。

  • **從第三方輔助應用程式啟動檔案關聯瀏覽。**我們將在後續的練習中研討這種方法。

    要從第三方輔助 App 建立檔案關聯測試則需執行下面的步驟:

    1. 建立一個新的 Windows Phone 應用程式。

    2. 要啟動檔案關聯瀏覽,應用程式必須有獨立儲存的文件。App 內新增一個新的檔案,檔案內容應適用於 ContosoCookbook App 如下:

      XML
      <?xml version="1.0" encoding="utf-8" ?>
      <Recipe ID="1003"/>

      確定你的檔案取名為“sample.rcp”(或“.recipe”,並在下方 WriteFiles 方法中使用新名字)

    3. 要編譯這個檔案,按右鍵點擊方案總管中的檔案並點選 Properties。

      XML
      <?xml version="1.0" encoding="utf-8" ?>
      <Recipe ID="1003"/>

    4. 更改 build action 為 Content。

    5. 增加一個按鈕以下載及開啟檔案。

    6. 在按鈕點擊處理事件中新增下列代碼:

      C#
      StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;

      if (local != null)
      {
      await WriteFiles();

      StorageFile recipeFile = await local.GetFileAsync("sample.rcp");
      if (recipeFile != null)
      await Windows.System.Launcher.LaunchFileAsync(recipeFile);
      }

      這個代碼呼叫 WriteFiles 方法以確定樣本檔案被初始化,並使用之前討論到的 LaunchFileAsync 方法啟動 App 間的瀏覽。

    7. 增加 WriteFiles 方法 :

      C#
      private async Task WriteFiles()
      {
      StreamReader stream = new
      StreamReader(TitleContainer.OpenStream("recipe.rcp"));

      StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;
      var file = await local.CreateFileAsync("sample.rcp",
      CreationCollisionOption.ReplaceExisting);

      string fileAsString = stream.ReadToEnd();
      byte[] fileBytes = System.Text.Encoding.UTF8.GetBytes(fileAsString);

      var outputStream = await file.OpenStreamForWriteAsync();
      outputStream.Write(fileBytes, 0, fileBytes.Length);

      stream.Close();
      outputStream.Close();
      }

      這種方法打開嵌入的食譜檔案並複製他到應用程式的資料夾內,所以可以用來作為檔案關聯啟動。

    8. 執行這個新的 App,點擊按鈕。 ContosoCookbook App 應該會啟動並顯示食譜。

    9. 這階段的練習即結束

總結

這個實驗提供了 Windows Phone 8 App 使用檔案及 URI 格式關聯的步驟。

為了實行這些關聯,你在應用程式清單中註冊關聯,實行 URI 對應器類別篩選及處理啟動 App 的功能,並增加從檔案下載資料或從 URI 檢索參數的功能。

現在,你擁有可以建立自己的應用程式並使用 URI 格式名稱和文件關聯與其他應用程式進行溝通,並擴充程式間互操作性選項的能力。

繼續下一個實驗