2020年的UWP(2)——In Process App Service

最先的時候App Service被定義爲一種後臺服務,相似於極簡版的Windows Service。App Service做爲Background Task在宿主UWP APP中運行,向其餘UWP APP提供服務,可用於UWP APP間通信及交換數據。html

早期的App Service應用場景較爲單一,但隨着Win10 1607版本對In Process AppService的支持,以及從Visual Studio2017開始支持的Desktop Extension和MSIX Package等一系列技術的應用,現在的App Service能夠用於UWP和非UWP程序間的直接通信,達到無限接近傳統桌面程序的能力。咱們今天就先來看一下In Process App Service。
In Process,顧名思義咱們不須要額外建立專門的Project用來寫App Service的代碼。而是直接包含在主UWP工程。首先咱們建立空的UWP工程FrontUWPApp,而後添加一個簡單的幫助類AppServiceHandler:git

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);
        }
    }

這其中最重要的方法是github

public void BackgroundActivated(IBackgroundTaskInstance taskInstance)

該方法將在App.xaml.cs經過windows

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;

來監聽消息,同時又能夠經過方法app

public IAsyncOperation<AppServiceResponse> SendMessageAsync(ValueSet message);

來發送消息。實現一個雙向的通信過程。
僅經過代碼也許不可思議要作的事情,不妨由界面來推導出邏輯,下圖是UWP工程FrontUWPApp的界面,咱們但願發送文字消息給非UWP工程BackgroundNetProcess。再由BackgroundNetProcess處理消息後,主動經AppService推給FrontUWPApp。async

首先咱們在MainPage的OnNavigatedTo方法中經過desktop extension的方式,來啓動.NET Framework的Console程序BackgroundNetProcess(若是對UWP如何使用desktop extension不夠了解,請參考這篇《遷移桌面程序到MS Store(9)——APPX With Desktop Extension》)。同時給AppServiceHandler訂閱MessageReceivedEvent。ide

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中返回的消息顯示在界面上。this

        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進程。spa

        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

相關文章
相關標籤/搜索