WP8 - App2App Communication - File associations

Windows Phone 8 - App2App Communication - File associations

在前一篇<Windows Phone 8 - App2App Communication - Protocol (URI Schema)>已經介紹過WP8 SDK

應用程式之間的互動的方式:(2) URI Schema;另一個要介紹的方式:File Associations,WP8允許應用程式註冊

針對某一檔案類型(file extensions)進行處理,目的在於是針對特定檔案類型時,可以觸動其他程式來進行處理的任務

另外,File Associations也被用於決定那些file types apps可以使用External storage APIs讀取SD Card中的檔案。

但要注意File associations只能註冊處理某特定檔案類型,註冊的類型不會被系統或其他應用程式拿來使用。

 

那麼File Associations運作的概念是什麼呢?

‧當沒有Apps支援特定的file extensions時,marketplace將會被啟動並允許用戶下載支援該file extensions的Apps;

‧如果系統只有一個App註冊該file extensions時,該Apps會自動被開啟,並且是使用Read-Only的copy放式傳給App;

‧如果超過一個App註冊該file extensions時,系統顯示一個對話框供用戶選擇要使用的App;

‧啟動File Associations的情境,可來自:

     ->電子郵件的附件;

     ->使用Internet Explorer瀏覽網頁中的檔案;

     ->來自Near Field Communications (NFC) tag;

     ->其他應用程式互動;

 

 

〉File Association

     運作方式是如何呢?應用程式必須註冊一個File Extension對象以處理與告知系統如何轉到該應用程式進行讀取

不過要特別注意,有些系統內定的File extensions會有自行處理的方式,例如:Internet Explorer覆寫File Associations

讓Audio/或Video/的檔案類型可以啟動內鍵的Media player(zune player)。所以自己的App如果有監聽這一類的檔案,

透過Internet Explorer來觸發file associations是不會開啟自己的App,但如果用其他App將會正常找到自己的App。

 

接下來討論要完成這樣的註冊,將分成以下幾個步驟來實作:

(1) 在WMAppManifest.xml註冊需要的File extensions與Logos(小、中、大);

(2) 處理由系統根據file associations將特定的Deep link URI送至指定的App;

 

(A) 至WMAppManifest.xml的<Extension />標籤中註冊要支援的File Extension

       以下先指定出相關設定標籤與屬性的定義說明:

Element Parent element Description
Extensions App 必須加入在<Tokens />的下方。
FileTypeAssociation Extensions 描述File association。最多可以註冊20個File associations。
‧Name屬性:必要值,可以自行定義;
‧TaskID:預設為:_default;
‧NavUriFragement:定義要傳送的Query Token;
Logos FileTypeAssociation 選擇使用的element。列出file associations使用的logos。
Logo Logos 選擇使用的Element。列出三種需要的圖示大小。如果有提供的話,IsRelative屬性必須存在,並且等於true。
SupportedFileType FileTypeAssociation 列出該file association的所有的file extensions。
FileType SupportedFileType 列出該file extension使用的file type,需包括「.」的符號,每一個file association最多具有20個file extensions。如果app讀取的檔案來自SD Card,必須指定ContentType屬性來描述該file extension。

範例如下:

   1: <Extensions>
   2:   <FileTypeAssociation Name="RecipeLaunch" TaskID="_default" 
   3:                        NavUriFragment="fileToken=%s">
   4:     <SupportedFileTypes>
   5:       <FileType>.rcp</FileType>
   6:       <FileType>.recipe</FileType>
   7:     </SupportedFileTypes>
   8:   </FileTypeAssociation>
   9: </Extensions>

在使用<FileTypeAssociation />標籤時,在<SupportedFileTypes />標籤下可以接收最多20個<FiletType />

 每一個<FileType />代表一個獨立的檔案類型

<FileTypeAssociation />下定義的這些FileType集合,使用相同的Content Type與Logo icon。這也代表說,

Content Type相同:存取的資料來源均相同;Logo icon相同:當系統出現選擇開啟檔案時,該程式的顯示Icon

當然,也可以更換File Type的Logos(並非強制),建議可以放置在Assets資料夾裡,宣告三個大小:

    ‧small:32x32,使用在email的附件顯示;

    ‧medium:69x69,使用在Office Hub的List views;

    ‧large:176x176,使用於Web Browser進行Downloads時;

另外,由於file type logos是出現在白色背景上,所以在送審前一定要測試這三個圖示呈現在白色背景上的效果

註冊相同的file extensions有超過一個以上的應用程式,那將不會看到file type logos出現,改用一個通用的圖示。

以下為範例:

   1: <FileTypeAssociation Name="RecipeLaunch" TaskID="_default" NavUriFragment="fileToken=%s">
   2:   <!-- 指定file type對應的圖示 -->
   3:   <Logos>
   4:     <Logo Size="small">SmallFileIcon.png</Logo>
   5:     <Logo Size="medium">MediumFileIcon.png</Logo>
   6:     <Logo Size="large">LargeFileIcon.png</Logo>
   7:   </Logos>
   8:   <SupportedFileTypes>
   9:     <FileType>.rcp</FileType>
  10:     <FileType ContentType="application/recipe">.recipe</FileType>
  11:   </SupportedFileTypes>
  12: </FileTypeAssociation>

 

[注意]

由於Windows Phone 8支援可從多個地方開啟檔案。如果應用程式本身支援開啟檔案是來自外部儲存空間(如:SD卡),

在宣告要支援的File Type時,要加上ContentType的註冊。如下:

   1: <!-- 宣告ContentType來自何種Location的資源 -->
   2: <FileType ContentType="application/recipe">.recipe</FileType>

 

 

(B) 處理由系統根據file associations將特定的Deep link URI送至指定的App

       當開發的App被啟動以處理特定的file type,系統會轉交一個deep link URI給App的。在這個URI裡藏了相關的資訊,如下:

‧FileTypeAssociation字段,為固定值;

‧NavUriFragment屬性指定的Parameter,以上述的範例為:fileToken=%s,該%s會被替換成實作的GUID;

二個組合起來的結果:「/FileTypeAssociation?fileToken=89819279-4fe0-4531-9f57-d633f0949a19」;

收到GUID後,開發的App需要準備一個Custom URI Mapper搭配SharedStorageAccessManager.GetSharedFileName(),以取得

該GUID實際的file type,接著往下進行需要任務邏輯。例如:如果有多個file extensions需要轉到不同的Page,即可以透過

客製的URI Mapper轉到對應的Page,但也別忘記如果不是File Associations的URI,則直接回傳喔。

 

以下透過程式碼來說明:

(B-1). 實作Custom URI Mapper

             與實作App2App的URI Schema一樣,需要繼承UriMapperBase類別,並且override它的MapUri()方法,如下:

   1: class CusUriMapper : UriMapperBase
   2: {
   3:     private string gTempUri = string.Empty;
   4:  
   5:     public override Uri MapUri(Uri uri)
   6:     {
   7:         gTempUri = uri.ToString();
   8:  
   9:         //判斷是否為File Associations
  10:         if (gTempUri.Contains("/FileTypeAssociation") == true)
  11:         {
  12:             //取得File Id(或GUID)
  13:             int tFileIdIdx = gTempUri.IndexOf("fileToken=") + 10;
  14:             string tFileID = gTempUri.Substring(tFileIdIdx);
  15:  
  16:             //取得File Name
  17:             string tIncommingFileName = SharedStorageAccessManager.GetSharedFileName(tFileID);
  18:             //取得File Extension
  19:             int tExtenIdx = tIncommingFileName.IndexOf(".");
  20:             string tIncommingFileType = tIncommingFileName.Substring(tExtenIdx).ToLower();
  21:  
  22:             //定義Map相關:.sdkTest1與.sdkTest2至不同的Page
  23:             switch (tIncommingFileType)
  24:             {
  25:                 case ".sdkTest1":
  26:                     return new Uri("/Test1Page.xaml?fileToken=" + tFileID, UriKind.Relative);
  27:                 case ".sdkTest2":
  28:                     return new Uri("/Test2Page.xaml?fileToken=" + tFileID, UriKind.Relative);
  29:                 default:
  30:                     return new Uri("/MainPage.xaml", UriKind.Relative);
  31:             }
  32:         }
  33:         //其他非File Associations的URI直接拋轉
  34:         return uri;
  35:     }
  36: }

             實作的範例,識別檔案的副檔名為:.sdkTest1與.sdkTest2分別前往不同的Page進行處理的動作;如果不屬於File

            associations的機制,則直接回傳uri不影響其他的應用邏輯。

 

(B-2). 覆寫應用程式RootFrame的UriMapper元件

            前往App.xaml.cs中的InitializePhoneApplication(),重新設定RootFrame中的UriMapper,如下:

   1: // Do not add any additional code to this method
   2: private void InitializePhoneApplication()
   3: {
   4:     if (phoneApplicationInitialized)
   5:         return;
   6:  
   7:     // Create the frame but don't set it as RootVisual yet; this allows the splash
   8:     // screen to remain active until the application is ready to render.
   9:     RootFrame = new PhoneApplicationFrame();
  10:     RootFrame.Navigated += CompleteInitializePhoneApplication;
  11:  
  12:     //增加自訂的UriMapper
  13:     RootFrame.UriMapper = new CusUriMapper();
  14:  
  15:     // Handle navigation failures
  16:     RootFrame.NavigationFailed += RootFrame_NavigationFailed;
  17:  
  18:     // Handle reset requests for clearing the backstack
  19:     RootFrame.Navigated += CheckForResetNavigation;
  20:  
  21:     // Ensure we don't initialize again
  22:     phoneApplicationInitialized = true;
  23: }

           一定要進行這樣的動作,才能讓應用程式在接收到系統傳過來的deep link URI時,有正確的UriMapper進行處理。

           而且要記得寫在RootFrame.Navigated設定完畢之後

 

實作上述二個步驟後,即完成向系統註冊要處理的File Associations與監控特定的File Extensions,並且實作了當用戶選擇

自己的應用程式時,有自訂的UriMapper來進行處理導向指定的Page,最後就是這些Page要負責的邏輯與運用了。

 

[實作範例]

1. 註冊需要的File Associations與File Extensions;(參考上述定義)

2. 客製URI Mappers與覆寫RootFrame.UriMapper;(參考上述定義)

3. 實作針對.sdkTest1與.sdkTest2不同類型處理的Page邏輯;

     3-1. .sdkTest1處理機制;

               以顯示File Token ID為主要目的,如下程式碼:

   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     if (NavigationContext.QueryString.ContainsKey("fileToken") == true)
   6:     {
   7:         string tFileID = NavigationContext.QueryString["fileToken"];
   8:         MessageBox.Show(tFileID);
   9:     }                
  10: }

 

     3-2. .sdkTest2處理機制;

               增加將File Token ID的檔案寫入Local folder,並且透過IsolatedStorageStream讀取其檔案透過MeidaElement進行播放;

   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     if (NavigationContext.QueryString.ContainsKey("fileToken") == true)
   6:     {
   7:         HandleFileAssociation();
   8:     }
   9: }
  10:  
  11: private async void HandleFileAssociation()
  12: {
  13:     // 取得File Token ID與File Name;
  14:     string tFileID = NavigationContext.QueryString["fileToken"];
  15:     string tFileName = SharedStorageAccessManager.GetSharedFileName(tFileID);
  16:  
  17:     // 取得目前的local folder;
  18:     StorageFolder tTargetFolder = await ApplicationData.Current.LocalFolder.
  19:                                         CreateFolderAsync("FileAss", CreationCollisionOption.OpenIfExists);
  20:     // 將檔案複製到指定的目錄下, 並且如果有存在的話,就直接覆寫;
  21:     IStorageFile tFileObj = await SharedStorageAccessManager.
  22:                                     CopySharedFileAsync(tTargetFolder, tFileName, 
  23:                                                         NameCollisionOption.ReplaceExisting, tFileID);            
  24:     
  25:     // 取得檔案內容;由於使用的是MediaElement需要使用IsolatedStorageStream,因此取消下方的程式段;
  26:     string tFilePath = "/FileAss/134.sdkTest2";
  27:     UseIsolatedStorage(tFilePath);
  28:     //IRandomAccessStreamWithContentType tContentStream = await tFileObj.OpenReadAsync();
  29: }
  30:  
  31: private void UseIsolatedStorage(string pFilePath)
  32: {
  33:     using (IsolatedStorageFile tIsoFile = IsolatedStorageFile.GetUserStoreForApplication())
  34:     {
  35:         // 使用MediaElement需要的IsolatedStorageStream,讀取已經寫入的檔案;
  36:         using (IsolatedStorageFileStream tIsoStream = new IsolatedStorageFileStream(
  37:                 pFilePath, FileMode.Open, tIsoFile))
  38:         {
  39:             mediaElement1.SetSource(tIsoStream);
  40:             mediaElement1.Play();
  41:  
  42:             tblStatus.Text = "Playing...";
  43:         }
  44:     }
  45: }

 

4. 實作MainPage.xaml增加LaunchFileAysnc的功能,進行測試;

    內有二個按鈕,進行.sdkTest1與.sdkTest2二個測試;

   1: private void btnOpenByLaunchUri_Click(object sender, RoutedEventArgs e)
   2: {
   3:     Button tBtn = sender as Button;
   4:     if (tBtn.Name == "btn1")
   5:         OpenFileByAsync("1");
   6:     else
   7:         OpenFileByAsync("2");
   8: }
   9:  
  10: private async void OpenFileByAsync(string pType)
  11: {
  12:     try
  13:     {
  14:         // 注意是使用「\」非「/」
  15:         string tFileName = @"Files\134.sdkTest" + pType;
  16:         var tFile = await Package.Current.InstalledLocation.GetFileAsync(tFileName);
  17:  
  18:         if (tFile != null)
  19:         {
  20:             bool tSuccess = await Windows.System.Launcher.LaunchFileAsync(tFile);
  21:             if (tSuccess)
  22:                 MessageBox.Show("成功啟動檔案");
  23:             else
  24:                 MessageBox.Show("失敗啟動檔案");
  25:         }
  26:         else
  27:         {
  28:             MessageBox.Show("沒有找到檔案");
  29:         }
  30:     }
  31:     catch (Exception ex)
  32:     {
  33:         MessageBox.Show(ex.Message);
  34:     }
  35: }

 

5. 執行結果:

      210211212

 

[範例程式]

=======

 

[重要元素]

以下針對在處理File Associations時的重要元件加以說明:

SharedStorageAccessManager

     該類別來自Windows.Storage.SharedAccess命名空間,提供連結被共享的關聯文件;提供二個方法協助在應用程式取得

來自File Associations得到的File:

Method Return Type Description
GetSharedFileName String 取得檔案的名稱,包括副檔名。
CopySharedFileAsync StorageFile 將Shared的檔案複製到指定的地方,並且取得複製後的檔案物件;

對於CopySharedFileAsync的使用方式,如下:

   1: private async void HandleFileAssociation()
   2: {
   3:     // 取得File Token ID與File Name;
   4:     string tFileID = NavigationContext.QueryString["fileToken"];
   5:     string tFileName = SharedStorageAccessManager.GetSharedFileName(tFileID);
   6:  
   7:     // 取得目前的local folder;
   8:     StorageFolder tTargetFolder = await ApplicationData.Current.LocalFolder.
   9:                                         CreateFolderAsync("FileAss", CreationCollisionOption.OpenIfExists);
  10:     // 將檔案複製到指定的目錄下, 並且如果有存在的話,就直接覆寫;
  11:     IStorageFile tFileObj = await SharedStorageAccessManager.
  12:                                     CopySharedFileAsync(tTargetFolder, tFileName, 
  13:                                                         NameCollisionOption.ReplaceExisting, tFileID);            
  14:     
  15:     // 讀取檔案,取得Stream
  16:     IRandomAccessStreamWithContentType tContentStream = await tFileObj.OpenReadAsync();
  17: }

對於SharedAccessManager的操作非常特別,在使用上要注意。

 

LaunchFileAsync

     如果有寫過Win8的程式,應該對該指令不陌生;在URI串聯則是使用LaunchUriAsync指令。在WP8也提供在程式裡,

啟動某個file extension的檔案,讓有註冊於系統的File Associations取得通知,使得用戶可以選擇需要的應用程式來執行。

擷取MSDN上的使用方法如下:

   1: async void DefaultLaunch()
   2: {
   3:    // 指定檔案的位置,以在install folder為例,
   4:    // 取得install folder下images資料夾中的檔案;注意是使用「\」,而不是之前WP中使用的「/」;
   5:    string imageFile = @"images\test.png";
   6:  
   7:    // 取得指定的檔案
   8:    var file = wait Windows.ApplicationModel.Package.Current.
   9:                    InstalledLocation.GetFileAsync(imageFile);
  10:  
  11:    if (file != null)
  12:    {
  13:       // 啟動檔案
  14:       var success = await Windows.System.Launcher.LaunchFileAsync(file);
  15:  
  16:       if (success)
  17:       {
  18:          // 檔案被執行
  19:       }
  20:       else
  21:       {
  22:          // 檔案執行失敗
  23:       }
  24:    }
  25:    else
  26:    {
  27:       // 不能找到指定的檔案
  28:    }
  29: }

透過該方法也蠻適用於測試自己應用程式的File Associations是否有設定成功;

 

======

在處理WP8中的Storage問題,如果扣掉在WP 7.1年代使用的IsolatedStorage,其實新的APIs是很特別的,

這觀念主要來自Win 8 Application的開發模式,如果上述在閱讀上對於檔案比較有興趣的話,可以另外參考

<>與<>有助於了解WP8在Storage的存取方式。

希望以上的介紹有助於大家了解Windows Phone 8 SDK在App-to-App Communication上的特性。謝謝。

 

References:

What's new in Windows Phone 8

Data for Windows Phone

Communications for Windows Phone

Auto-launching apps using file and URI associations for Windows Phone 8 (重要)

Reading from the SD card on Windows Phone 8 (重要)

windows phone 8 新增功能:从一个应用程序启动另一个程序(file association 和 Protocol association两种方式)

Windows Phone Samples

Reserved file and URI associations for Windows Phone 8 (重要)

Auto Launching Apps now possible using file associations for Windows Phone 8

URI schemes for launching built-in apps for Windows Phone 8 (重要)

如何接收影像 (使用 C#/VB/C++ 和 XAML 的 Windows 市集應用程式)

MediaElement在使用时的注意点

新时尚Windows8开发(11):共享目标示例

Playing media content on Windows Phone 7 with MediaElement & Zero Gravity: Moving to WP7

 

Dotblogs Tags: