App Service 是一種背景工作運行的服務,提供給其他 Apps 使用就像 Web Service。它本身無使用介面(UI-less),允許 Apps 在同一個設備被引用,甚至 Windows 10 1607 開始允許 remote devices 使用它。
[重點觀念]
- Windows 10, version 1607 開始, App Service 支持新模式:
- 可以與 host App 運行在相同的 process;(一般屬於 Background Task 執行在不同的 process)
- 支援從 App 呼叫 Remote Devices 中的 App Service;
- 想要 App Service 每次被啓動都是新的 instance,在 Package.appmanifest 加入宣告;uap4:SupportsMultipleInstances="true";但需要 Windows 10, version 15063 以上才支援
- App Service 的生命周期,因爲 Process 有所不同:
- Background Task (out-of-process):
- 當它被建立時會進入 Run(),隨著 Run() 執行完畢就會被結束
- 它被啓動後,基本會維持活著約有 30 秒,可搭配呼叫 GetDeferral() 多加 5 秒來完成任務
- In-app process model:生命周期則跟著呼叫者一起共存,讓兩個 Apps 之間更容易溝通,不用再分成兩份 code 來串聯與維護
- Background Task (out-of-process):
- App Service 的 OnTaskCancel() 被觸發有幾個原因:
- Client app 釋放AppServiceConnection
- Client app 被 suspended
- 系統關閉或睡眠
- 系統執行該 Task 用過高的資源
大略有概念之後,接著介紹怎麽做基本的 App Service (two process),再介紹怎麽整合到 App 的 process 裏面;
* 如何建立 App service 並使用它:
分成兩個 App 做説明:一個是擁有 App Service 的 Host App;一個是使用 App Service 的 Client App;
- 建立一個 Windows Runtime Component,並且加入AppServiceConnection 的處理邏輯:
public sealed class ServiceTask : IBackgroundTask { private BackgroundTaskDeferral backgroundTaskDeferral; private AppServiceConnection appServiceconnection; public void Run(IBackgroundTaskInstance taskInstance) { // Background Task 被建立時,取得 deferral 拉長生命周期,避免被結束 this.backgroundTaskDeferral = taskInstance.GetDeferral(); // 一定要註冊處理 Canceled 事件來正確釋放用到的資源 taskInstance.Canceled += OnTaskCanceled; // 根據被啓動的 Instance 類型,建立 App Service Connection,並註冊 Request 事件. var details = taskInstance.TriggerDetails as AppServiceTriggerDetails; appServiceconnection = details.AppServiceConnection; appServiceconnection.RequestReceived += OnRequestReceived; } private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { if (this.backgroundTaskDeferral != null) { // Complete the service deferral. this.backgroundTaskDeferral.Complete(); } } private async void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { // 當 App Service 收到請求時,該 method 就會被觸發 // 先要求取得 取得 deferral 拉長生命周期 var requestDeferral = args.GetDeferral(); ValueSet message = args.Request.Message; string cmd = message["cmd"] as string; string id = message["id"] as string; ValueSet responseMsg = new ValueSet(); switch (cmd) { case "Query": responseMsg.Add("id", "123456"); responseMsg.Add("name", "pou"); responseMsg.Add("status", "OK"); var result = await args.Request.SendResponseAsync(responseMsg); break; } requestDeferral.Complete(); } }
- 在 Host App 的 Package.manifest 宣告 App Service 並設定 Entry Point,記得把 App Service 的專案加入到 Host App 的專案參考:
<Applications> <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="ServiceHost.App"> <uap:VisualElements /> <Extensions> <uap:Extension Category="windows.appService" EntryPoint="MyAppService.ServiceTask"> <uap:AppService Name="com.pou.MyApService" /> </uap:Extension> </Extensions> </Application> </Applications>
- 在 Client App 利用 AppServiceConnection 呼叫 App Service:
private async void MainPage_Loaded(object sender, RoutedEventArgs e) { AppServiceConnection connection = new AppServiceConnection(); connection.AppServiceName = "com.pou.MyApService"; connection.PackageFamilyName = "f9842749-e4c8-4c15-bac8-bc018db1b2ea_s1mb6h805jdtj"; var status = await connection.OpenAsync(); if (status != AppServiceConnectionStatus.Success) { Debug.WriteLine("Failed to connect"); return; } var message = new ValueSet(); message.Add("cmd", "Query"); message.Add("id", "1234"); AppServiceResponse response = await connection.SendMessageAsync(message); string result = ""; if (response.Status == AppServiceResponseStatus.Success) { if (response.Message["status"] as string == "OK") { result = response.Message["name"] as string; } } }
上面介紹的 App Service 是比較一般的用法, 把 App Service 放到 Background Task 的架構。
* 把 App Service 合併到 App.xaml.cs 裏面,作爲 Same Process:
AppServiceConnection 允許其他 App 叫醒在背景中自己的 App 並傳入指令。它與上方的 out-of-process 最大不同有兩個:
- Package.manifest 宣告 <uap:Extension Category="windows.appService"> 不用 Entry Point,改用 OnBackgroundActivated()。
<Package> <Applications> <Application> <Extensions> <uap:Extension Category="windows.appService"> <uap:AppService Name="com.pou.MyApService" /> </uap:Extension> </Extensions> </Application> </Applications>
- 在 App.xaml.cs 加入 OnBackgroundActivated() 的處理邏輯。
sealed partial class App : Application { private AppServiceConnection appServiceConnection; private BackgroundTaskDeferral appServiceDeferral; protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) { base.OnBackgroundActivated(args); AppServiceTriggerDetails appService = args.TaskInstance.TriggerDetails as AppServiceTriggerDetails; if (appService ==null) { return; } args.TaskInstance.Canceled += OnAppServicesCanceled; // appServiceDeferral 與 appServiceConnection 需要變成公用變數 // 因爲其他時間需要用到,已維持連線的一致性 appServiceDeferral = args.TaskInstance.GetDeferral(); appServiceConnection = appService.AppServiceConnection; appServiceConnection.RequestReceived += AppServiceConnection_RequestReceived; appServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed; } private async void AppServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { // 當 App Service 收到請求時,該 method 就會被觸發 // 先要求取得 取得 deferral 拉長生命周期 var requestDeferral = args.GetDeferral(); ValueSet message = args.Request.Message; string cmd = message["cmd"] as string; string id = message["id"] as string; ValueSet responseMsg = new ValueSet(); switch (cmd) { case "Query": responseMsg.Add("id", "123456"); responseMsg.Add("name", "pou"); responseMsg.Add("status", "OK"); var result = await args.Request.SendResponseAsync(responseMsg); break; } requestDeferral.Complete(); } private void AppServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) { appServiceDeferral?.Complete(); appServiceConnection?.Dispose(); } private void OnAppServicesCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { appServiceDeferral?.Complete(); appServiceConnection?.Dispose(); } }
要支援 in-process model 就是這樣簡單,而且讓原本的 App Service 邏輯回到 App 本身,讓邏輯更乾净。OnBackgroundActivated() 負責處理 App Service 的啓用,並儲存 Deferral 保持服務的生命周期,詳細可以參考 Windows 10 通用 Windows 平台 (UWP) app 週期。
介紹完怎麽實作之後,下面補充幾個重要的元件:
- AppServiceConnection
代表連線到 App Service 的端點,App Service 允許 UWP App 之間互相溝通。幾個重點:
Type Name Description Properties AppServiceName 取得或設定想要操作的 App Service Name PackageFamilyName 取得或設定該 App Service 所屬 package 的 family name Methods Dispose() Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. OpenAsync() Opens a connection to the endpoint for the app service. OpenRemoteAsync(RemoteSystemConnectionRequest) Opens a connection to the endpoint on another device for the app service. SendMessageAsync(ValueSet) 傳送 ValueSet 内容到 App Service Events RequestReceived Occurs when a message arrives from the other endpoint of the app service connection. ServiceClosed Occurs when the other endpoint closes the connection to the app service. - ValueSet Class
實現來傳遞交換訊息用的結構,利用 string 做為 Key,Value 則是 Object。這個結構不能放不可被序列化的結構。
- AppServiceResponseStatus
代表 App 傳送訊息到 App Service 的結果
- AppServiceClosedStatus
代表 App Service 被關閉連線時的描述
======
從上面的介紹,如果您的 App Service 是比較工具型跟 App 本身不需要共用資料或邏輯,建議獨立成 Background Task;相反地,需要讓 App Service 跟 App 有互動,建議改為 Same Process 的架構。更多介紹可以參考 Extend your app with services, extensions, and packages。
Connected apps and devices (Project Rome) 支援更豐富的功能,下一篇將有更詳細的説明。
希望對大家有所幫助。
References:
- Create and consume an app service
- Convert an app service to run in the same process as its host app
- Extend your app with app services, extensions, and packages
- Create and consume an app extension
- Communicate with a remote app service
- Universal Windows Platform (UWP) app samples for App Service
- Calling Windows 10 APIs From a Desktop Application
- qmatteoq/DesktopBridge
- Support your app with background tasks
- Windows 10 universal Windows platform (UWP) app lifecycle
- Communicate with a remote app service
- Connected apps and devices (Project Rome)
- [UWP] Windows 10 IoT App Service 簡單實作
- 偵錯背景工作