UWP - 介紹 Project Rome - 1

Project Rome 從 //Build 2016 發表的技術,讓 App 可以在同一個 Microsoft account (MSA) 的不同設備(Windows, Android, iOS)互相溝通。

這一篇將介紹如何操作 Remote Systems APIs 做到這些應用找尋設備,啓動遠端設備中的 App 與 App Service。

重點提醒

  • 在 Windows 10, Version 1607 開始 Remote Systems APIs 支援讓 App 從一個設備開始處理任務,最後到另一臺設備的完成它。
  • 設備能經由 bluetooth, wireless 或是 cloud (則需要相同的 Microsoft account (MSA) 連結)
  • 常見的情境:
    • 用戶可能在車上用手機聼歌曲,等他回到家裏就可以直接把目前播放的進度改交給 Xbox One 繼續播放
    • 利用 App Service 建立 channel 讓兩個設備直接互相溝通或控制
  • 開發的專案要宣告 RemoteSystem 的 capability:
    <Capabilities>
      <Capability Name="internetClient" />
      <uap3:Capability Name="remoteSystem" />
    </Capabilities>
  • 如果要允許不同帳號也可以控制你的設備 (使用 RemoteSystemAuthorizationKind.Anonymous),需要開 跨裝置體驗 的設定,如下圖:

有了觀念之後,下面介紹細部的開發。

* 如何找到遠端設備

1. 利用 RemoteSystemWatcher 找出設備:

RemoteSystemWatcher 搭配 Filter 找出符合的設備,Filter 可以指定:

private async void OnStartDiscoverClick(object sender, RoutedEventArgs e)
{
    // 要使用 RemoteSystem 前,需要先要求用戶給與權限
    var requestPermission = await RemoteSystem.RequestAccessAsync();

    if (requestPermission != RemoteSystemAccessStatus.Allowed)
    {
        return;
    }

    OnStopDiscoverClick(sender, e);
    DiscoverDevicesAsync();
}

private RemoteSystemWatcher devicesWatcher;
private ObservableCollection devicesList;

private List GetRemoteSystemFilter()
{
    List filters = new List();

    // 設定要用什麽方式找設備, 利用 Any 比較多設備可以被找到
    filters.Add(new RemoteSystemDiscoveryTypeFilter(RemoteSystemDiscoveryType.Any));

    // 設定找到的設備要是什麽狀態
    filters.Add(new RemoteSystemStatusTypeFilter(RemoteSystemStatusType.Available));

    // 設定要找尋的設備類型
    filters.Add(new RemoteSystemKindFilter(new List
    {
        RemoteSystemKinds.Desktop, RemoteSystemKinds.Laptop, RemoteSystemKinds.Tablet,
        RemoteSystemKinds.Phone,
        RemoteSystemKinds.Xbox
    }));

    // 設定是否需要驗證的設備, 如果要相同帳號可以選 SameUser,預設是 SameUser
    filters.Add(new RemoteSystemAuthorizationKindFilter(RemoteSystemAuthorizationKind.Anonymous));

    return filters;
}

private void DiscoverDevicesAsync()
{
    var filters = GetRemoteSystemFilter();

    // Filters 需要在建立 RemoteSystemWatcher 建構子一起傳入
    devicesWatcher = RemoteSystem.CreateWatcher(filters);
    
    devicesWatcher.RemoteSystemAdded += DevicesWatcher_RemoteSystemAdded;
    devicesWatcher.RemoteSystemRemoved += DevicesWatcher_RemoteSystemRemoved;
    devicesWatcher.RemoteSystemUpdated += DevicesWatcher_RemoteSystemUpdated;
    devicesWatcher.ErrorOccurred += DevicesWatcher_ErrorOccurred;

    devicesWatcher.Start();
}

[注意]

  • RemoteSystemDiscoveryType 設定 proximal 不保證物理距離接近程度
  • 如果需要可靠物理距離接近程度,可以使用 RemoteSystemDiscoveryType.SpatiallyProximal,但只有支援用藍牙找到設備
  • 可利用 RemoteSystem 的 RemoteSystem.IsAvailableBySpatialProximity 確認被找到的設備是否在物理接近範圍內
  • RemoteSystemDiscoveryType 設定 local network 時,使用的網路 Profile 是 privatedomin,設備不會在 public 被找到
  • 如果設定 RemoteSystemAuthorizationKind.Anonymous 為過濾調整,只能在 proximal 範圍找到設備

2. 利用 IP Address 找到設備

private async Task GetRemoteSystemByIp(string ipAddress)
{
    HostName host = new HostName(ipAddress);

    // 利用 IP Address 去找設備,如果找不到設備會拿到 null。
    return await RemoteSystem.FindByHostNameAsync(host);
}

* 使用 RemoteSystem 連線設備,並啓動特定的 URI

private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
    var remoteSystem = e.ClickedItem as RemoteSystem;

    // 檢查設備是否支援需要的特性
    // KnownRemoteSystemCapabilities.AppService 與 KnownRemoteSystemCapabilities.LaunchUri
    var appService = await remoteSystem.GetCapabilitySupportedAsync(KnownRemoteSystemCapabilities.AppService);
    var launchUri = await remoteSystem.GetCapabilitySupportedAsync(KnownRemoteSystemCapabilities.LaunchUri);
    
    if (launchUri)
    {
        var launchRequest = new RemoteSystemConnectionRequest(remoteSystem);
        var result = await RemoteLauncher.LaunchUriAsync(launchRequest, new Uri("https://poumason.blogspot.com"));
        Debug.WriteLine(result.ToString());
    }
}

利用 KnownRemoteSystemCapabilities 定義了支援那些 Capabilities 的名稱,幫助開發人員確認 RemoteSystem 能支援什麽。

再建立 RemoteSystemConnectionRequest 物件交給 RemoteLauncher 要求 RemoteSystem 執行任務。

* 讓 App Service 支援從另一個設備來呼叫:

任何 Windows-based 的設備都可以當作 client 或是 host,表示 App Service 可以被安裝在任何一個角色來與對方互動。

由於 App Service 的 UI-less 的特性,在呼叫遠端設備中的 App Service 時就無需將應用程式帶到 foreground。

一樣使用 host 與 client 兩個角色來説明:

  1. host App 需要修改 Package.manifest 支援 SupportsRemoteSystems
    <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" 
             xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" 
             xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" 
             xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
             IgnorableNamespaces="uap mp uap">
        <Applications>
           <Application>
              <Extensions>
                <uap3:Extension Category="windows.appService" EntryPoint="MyAppService.ServiceTask">
                    <uap3:AppService Name="com.pou.MyApService" SupportsRemoteSystems="true" />
                 </uap3:Extension>
              </Extensions>
           </Application>
        </Applications>
    </Package>
    SupportsRemoteSystems 是新的 xmlns,所以要記得加入 uap3 的宣告。
  2. host App 的 App Service 加入處理來自 client 的請求,以 UWP - 介紹 App Service 與新功能 的範例爲主
  3. client App 利用 Remote Systems 呼叫遠端設備的 App Service:
    AppServiceConnection connection = new AppServiceConnection();
    connection.AppServiceName = "com.pou.MyApService";
    connection.PackageFamilyName = "f9842749-e4c8-4c15-bac8-bc018db1b2ea_s1mb6h805jdtj";
    
    RemoteSystemConnectionRequest appServiceRequest = new RemoteSystemConnectionRequest(remoteSystem);
    
    AppServiceConnectionStatus status = await connection.OpenRemoteAsync(appServiceRequest);
    
    if (status == AppServiceConnectionStatus.Success)
    {
        var message = new ValueSet();
        message.Add("cmd", "Query");
        message.Add("id", "1234");
    
        AppServiceResponse response = await connection.SendMessageAsync(message);
    
        if (response.Status == AppServiceResponseStatus.Success)
        {
            if (response.Message["status"] as string == "OK")
            {
                Debug.WriteLine(response.Message["name"] as string);
            }
        }
    }
    
    同樣利用 RemoteSystem 建立 RemoteSystemConnectionRequest,搭配 AppServiceConnection 建立連線,再把訊息送到遠端設備的 App Service 來獲取結果。

幾個重要的元素:

  1. RemoteSystems
    該類別管理被找到的遠端設備屬性與它可支援的特性。重要的内容:
    Type Name Description
    Properties IsAvailableByProximity 利用 proximal connection (近距離連線,例如:Bluetooth, local network connection)檢查給定的設備是否可以用,而不是透過雲端連線。
      IsAvailableBySpatialProximity 通過空間近距離的連接檢查給定的遠端系統是否可用。
    Methods CreateWatcher(IIterable) 搭配設定 RemoteSystemFilter 的條件, 建立 RemoteSystemWatcher 來搜尋 Remote Systems。
      FindByHostNameAsync(HostName) 企圖找到特定 IP Address 或 Host Name 的設備
      GetCapabilitySupportedAsync(String) 檢查該 RemoteSystem 是否有支援特定功能, 搭配 KnownRemoteSystemCapabilities 使用。
      RequestAccessAsync() 每次在使用 RemoteSystem 之前一定要呼叫, 讓用戶允許 App 有權限使用相關特性。
  2. RemoteSystemConnectionRequest
    代表與特定設備連線溝通的意圖。例如:要求 RemoteSystem 執行 remote launch 或 remote app service 都需要利用它來建立連線。
  3. RemoteLauncher
    啟動與遠端設備上的指定 URI 關聯的預設應用程式。 常見都是設定 URI,如果需要比較複雜的使用可以參考 LaunchUriAsync(RemoteSystemConnectionRequest, Uri, RemoteLauncherOptions):可以設定 FallbackUri 或 PreferredAppIds (給于package family names) 啓動指定的 App。

上面的範例可以到 26-RemoteSystemSample 下載來使用。

詳細説明可以參考 Windows.System.RemoteSystems Namespace ,或是範例 Remote Systems UWP sample

======

這篇的介紹是以 Windows 設備為主,如果想要 Windows 操作其他設備,可以參考微軟提供的 SDK:Announcing Project Rome Android SDKAnnouncing Project Rome iOS SDK

希望對大家有所幫助。

References: