2020年的UWP(2)——In Process App Service
最早的時候App Service被定義為一種後臺服務,類似於極簡版的Windows Service。App Service作為Background Task在宿主UWP APP中執行,向其他UWP APP提供服務,可用於UWP APP間通訊及交換資料。
早期的App Service應用場景較為單一,但隨著Win10 1607版本對In Process AppService的支援,以及從Visual Studio2017開始支援的Desktop Extension和MSIX Package等一系列技術的應用,如今的App Service可以用於UWP和非UWP程式間的直接通訊,達到無限接近傳統桌面程式的能力。我們今天就先來看一下In Process App Service。
class AppServiceHandler { private AppServiceConnection AppServiceConnection { get; set; } private BackgroundTaskDeferral AppServiceDeferral { get; set; } public event EventHandler<string> MessageReceivedEvent; private static AppServiceHandler instance; public static AppServiceHandler Instance { get { if (instance == null) { instance = new AppServiceHandler(); } return instance; } } private AppServiceHandler() { } public void BackgroundActivated(IBackgroundTaskInstance taskInstance) { AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails; AppServiceDeferral = taskInstance.GetDeferral(); AppServiceConnection = appService.AppServiceConnection; AppServiceConnection.RequestReceived += OnAppServiceRequestReceived; AppServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed; } private void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { AppServiceDeferral messageDeferral = args.GetDeferral(); var message = args.Request.Message; string text = message["response"] as string; MessageReceivedEvent?.Invoke(this, text); messageDeferral.Complete(); } private void AppServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) { AppServiceDeferral.Complete(); } public async Task<AppServiceResponse> SendRequestAsync(string message) { var valueSet = new ValueSet(); valueSet.Add("request", message); return await AppServiceConnection.SendMessageAsync(valueSet); } }
這其中最重要的方法是
public void BackgroundActivated(IBackgroundTaskInstance taskInstance)
該方法將在App.xaml.cs通過
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args) { base.OnBackgroundActivated(args); AppServiceHandler.Instance.BackgroundActivated(args.TaskInstance); }
將BackgroundTask的例項傳遞進來。再儲存這個Instance中AppService的AppServiceConnection物件。在取得AppServiceConnection物件後,即可以通過事件
public event TypedEventHandler<AppServiceConnection, AppServiceRequestReceivedEventArgs> RequestReceived;
來監聽訊息,同時又可以通過方法
public IAsyncOperation<AppServiceResponse> SendMessageAsync(ValueSet message);
來發送訊息。實現一個雙向的通訊過程。
僅通過程式碼也許難以想象要做的事情,不妨由介面來推匯出邏輯,下圖是UWP工程FrontUWPApp的介面,我們希望傳送文字訊息給非UWP工程BackgroundNetProcess。再由BackgroundNetProcess處理訊息後,主動經AppService推給FrontUWPApp。
首先我們在MainPage的OnNavigatedTo方法中通過desktop extension的方式,來啟動.NET Framework的Console程式BackgroundNetProcess(如果對UWP如何使用desktop extension不夠了解,請參考這篇《遷移桌面程式到MS Store(9)——APPX With Desktop Extension》)。同時給AppServiceHandler訂閱MessageReceivedEvent。
protected async override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0)) { await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync(); AppServiceHandler.Instance.MessageReceivedEvent += Instance_MessageReceivedEvent; } }
Instance_MesssageReceivedEvent就是簡單的把從BackgroundNetProcess中返回的訊息顯示在介面上。
private async void Instance_MessageReceivedEvent(object sender, string e) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { textBoxResponses.Text += e + "\r\n"; }); }
同時MainPage上的Button按鈕會通過AppServiceHandler例項中儲存的AppServiceConnection物件來發送request給BackgroundNetProcess程序。
private async void Button_Click(object sender, RoutedEventArgs e) { var response = await AppServiceHandler.Instance.SendRequestAsync(textBoxRequest.Text); }
我們轉到BackgroundNetProcess工程,在Main方法中僅僅是建立類BackgroundProcess的例項,並且讓Console保持執行。
static void Main(string[] args) { var backgroundProcess = new BackgroundProcess(); Console.ReadKey(); }
而在BackgroundProcess類中,我們通過InitializeAsync方法來建立AppServiceConnection物件,在成功開啟Connection的情況下,訂閱ReqeustReceived事件。這是為了能接受到上文提到的,UWP APP傳送過來的request。
public class BackgroundProcess { private AppServiceConnection Connection { get; set; } public Task InitializeTask { get; private set; } public BackgroundProcess() { InitializeTask = InitializeAsync(); } public async Task InitializeAsync() { Connection = new AppServiceConnection(); Connection.PackageFamilyName = Package.Current.Id.FamilyName; Connection.AppServiceName = "NotificationAppService"; AppServiceConnectionStatus status = await Connection.OpenAsync(); if (status != AppServiceConnectionStatus.Success) { Console.WriteLine(status); } else { Console.WriteLine(status); Connection.RequestReceived += Connection_RequestReceived; } } private async void Connection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { var deferral = args.GetDeferral(); var content = args.Request.Message["request"]; var message = new ValueSet(); message.Add("response", $"Received request content: {content}"); await Connection.SendMessageAsync(message); deferral.Complete(); } }
這裡需要注意的是,Connection.AppServiceName需要和最終Package.appmanifest檔案中配置的ServiceName一致(appmanifest檔案的修改我們後面一點再介紹)。
在BackgroundProcess類中,一旦我們收到了UWP APP發來的request,就會觸發Connection_RequestReceived方法。在該方法裡,我們對收到的字串做了簡單處理,然後通過SendMessageAsync方法反向給UWP APP傳送訊息。
當然,並沒有規定收到request就一定要立即返回訊息。我們可以在BackgroundProcess這樣的desktop extension程序中,實現一些UWP限制的功能,諸如查詢登錄檔,啟動其他exe程式等等。甚至可以掛個鍵盤鉤子,在捕捉到熱鍵時,通知UWP APP。
前後端的FrontUWP和BackgroundNetProcess都介紹完了,接著就是通過Packaging工程將它們整合打包成MSIX package。
記得在Package工程的Applications中,新增對FrontUWPApp和BackgroundNetProcess的引用。同時設定FrontUWPApp為入口點。
最後我們來編輯Package工程的appxmanifest檔案,主要就是新增Extensions節點。
<Extensions> <uap:Extension Category="windows.appService"> <uap:AppService Name="NotificationAppService" /> </uap:Extension> <desktop:Extension Category="windows.fullTrustProcess" Executable="BackgroundNetProcess\BackgroundNetProcess.exe"></desktop:Extension> </Extensions>
在完成以上操作之後,我們的AppServiceCommunicaton工程就編寫完畢了。在Visual Studio 2019中按F5執行的話,應該可以實現FrontUWPApp和BackgroundNetProcess之間的訊息傳遞了。
本篇的示例程式碼依然放在這個Repository中,Clone後通過VS開啟,找到InProcessAppService資料夾即可。
https://github.com/manupstairs/UWPSamples
&n