2013年10月26日 星期六

URI Associations

各 OS 上讓 App 與 App 溝通的方法幾乎都有 URI Associations 和 File Associations 兩種,Windows Phone 8.0 上面也支援這兩種方式,File Associations 我個人覺得在 Windows Phone 上面的應用場合比較低,這裡僅記錄在 Windows Phone 上面實作 URI Associations 流程的經驗。

要讓 Windows Phone App 能夠接受某個 Protocol 必須做幾個步驟

1. 以文字編輯器開啟 WMAppManifest.xml,註冊你的 Protocol 名稱

<Extensions>
  <Protocol Name="myapp" TaskID="_default" NavUriFragment="LaunchUri=%s"/>
</Extensions>
<!--
Protocol 的三個 attribute 都必須填寫,NavUriFragment 的部份可以寫 %s 就行了
但建議再多一些加強識別的字串,例如我在前面加上 LaunchUri 的字串
收到 Association 時就會長得類似 /Protocol?LaunchUri=myapp://open 這個樣子
若只填 %s 就類似於 /Protocol?myapp://open 這個樣子
-->

2. 新增一個類別並繼承自 UriMapperBase 例如

public class AssociationManager : UriMapperBase
{
    public AssociationManager()
    {
    }

    public const String PROTOCOL_BASE = "/Protocol?LaunchUri=myapp://";

    public override Uri MapUri(Uri uri)
    {
        String strUri = HttpUtility.UrlDecode(uri.ToString());
        if (!String.IsNullOrEmpty(strUri))
        {
            if (strUri.Contains(PROTOCOL_BASE))
            {
                if (strUri.Contains("signin"))
                {
                    String strNewUri = String.Format("/SignInPage.xaml?userid={0}", strUri.Replace(PROTOCOL_BASE, "").Replace("signin/", "").Trim('/'));
                    uri = new Uri(strNewUri , UriKind.Relative);
                }
                else if (strUri.Contains("signup"))
                {
                    uri = new Uri("/SignUpPage.xaml", UriKind.Relative);
                }
            }
        }
        return uri;
    }
}

3. 在 App class 中的 InitializePhoneApplication Method 中對 RootFrame 的 UriMpper 加入處理器

private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
    {
        return;
    }

    RootFrame = new PhoneApplicationFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;
    RootFrame.Navigated += CheckForResetNavigation;
    RootFrame.NavigationFailed += OnRootFrameNavigationFailed;
    RootFrame.Navigating += OnRootFrameNavigating;

    RootFrame.UriMapper = new AssociationManager(); // 將剛才建立的類別設為處理器

    phoneApplicationInitialized = true;
}

一旦使用自定義的 AssociationManager 處理 UriMapper 後,會發現所有的頁面導覽都會進到該類別的 MapUri Method 中,RootFrame 會根據 MapUri 處理過後的 uri 回傳值做導覽的動作。

舉例來說,當應用程式裡有某個按鍵按下後會做以下動作

Uri showUri = new Uri("SecondPage.xaml", UriKind.Relative);
NavigationService.Navigate(showUri);

此時 UriMap 這個 Method 也會被執行且參數就是 showUri,而其他 App 或網頁使用像 myapp://show/ascii 啟動我們的 App 時,UriMap 會收到這樣的 Uri 參數 /Protocol?LaunchUri=myapp://show/ascii

所以在上面我所寫的程式碼片段可以發現我們只對包含 /Protocol?LaunchUri=myapp:// 做處理,其餘的 Uri 都不做任何處理,因為 App 中的正常導覽流程不必被處理,而 /Protocol?LaunchUri 這類的 Uri 若不處理則會在 NavigationService 中發生 Exception

這代表的是,一旦你的 App 要開始支援 URI Associations 後,所有可能的 command 都必須列舉出來,否則就會發生沒辦法把 /Protocol?LaunchUri... 解晰為正常可導覽 Uri 的狀況而 Crash,但即便是把所有的 command 都列舉清楚也是有可能發生 Exception 的狀況,因為我們無法保證其他網頁會不會有某個連結指向 myapp://沒定義過的字。

除了上述的 Uri 問題之外,如果是在應用程式尚常開啟的狀態下,經由 UriMap Method 所指定的頁面,會成為應用程式的 Page Stack 的第一頁,以上面的 SignUpPage.xaml 為例,用戶在經過多頁層層的導覽後,再利用 Back 鍵回到 SingUpPage 時,再按 Back 就會離開應用程式。
用戶想要回到應用程式真正的首頁必須經過一些特殊的判斷加上設計,所以在 Windows Phone 上的 Uri Associations 算是一個會破壞預期導覽流程的行為,目前我的解決方法是只要是收到 /Protocol?LaunchUri=myapp:// 的 Uri 一律都先將 command 儲存下來,再導到應用程式的首頁後讀出此 command 做導覽,或是在導到首頁時將此 command 串在後面當作參數也行,如此才不用在各頁面加特殊的判斷增加複雜度。

順代一提,若要測試已實作 URI Associations 的應用程式是否正常,有兩種簡單的方式,一個是寫一個網頁,再利用瀏灠器打開此網頁,只要上面有連結是連至 myapp://open 即可開啟我們的應用程式,或是撰寫另一支應用程式,在按下按鍵後執行以下指令

private void OnButtonClicked(object sender, RoutedEventArgs e)
{
    Launcher.LaunchUriAsync(new Uri("myapp://open", UriKind.Absolute));
}


沒有留言:

張貼留言