UWP - 把 App 加入到系統自動啓動 (startup)

Desktop Bridge 爲了讓 win32 程式在轉成 UWP 運作架構後保有 win32 的一些特性,例如:自動隨系統啓動被開啓,可以常駐在 System tray 等。Fall creators update 開放給一般的 UWP 程式具有部分功能。讓我們來看看要怎麽使用吧。

參考 What's New in Windows 10 for developers, version 1709 提到幾個用來啓動 app 的方式:

  • 利用 StartupTask Class 讓 app 可以在用戶登入或是系統啓動後自動執行
  • 利用 launched from the command line 識別 app 被啓動的原因,做出對應的處理
  • 利用 RequestRestartAsync() 或 RequestRestartForUserAsync() 來重新啓動 App。需注意:
    • 只能是 app 顯示中且在 foreground 才能使用
    • 如果啓動失敗,用戶手動再啓動 app 原本使用的 restart arguments 會被忽略
    • 可利用 LaunchActivatedEventArgs.PreviousExecutionState 識別 app 被啓動是來自 restart 或 resume
    • 如果 app 有任何 in-process background tasks 呼叫該 api 會無效; out-of-process background tasks 不受影響
    • 如果 app 不是以正常方式啓動,而是用 share contract, file picker, app service 等,就不應該呼叫該 api ,因爲不會是用戶預期的
    • 由於可以送入 User 需要確認該 User 是否有權限執行
    • 如果呼叫該 api 就不應該在呼叫 Extended Execution session
    • 設定 Startup Task 之後,App 隨著用戶登入或系統重新啓動會被開啓,預設開啓後自動視窗最小化
  • 系統的 URI scheme 加入了新的内容: Launch the Windows Settings app

本篇介紹 StartupTask Class 讓 App 可以隨著用戶登入或系統自動執行。需注意:StartupTask Class 只有在 Desktop 才能使用。

實現的步驟如下:

step 1. 將專案 target version 選擇到 16299 或以上

step 2. 設定 Package.appxmanifest 加入新的 UAP namespace (contract version 5) 與 extensions

<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"
    <!-- 加入 uap5 的宣告 -->
    xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
    IgnorableNamespaces="uap mp uap5"> 
 <Applications>
   <Application>
     <Extensions>
      <!-- 加入 uap5 定義的 extension -->
   <uap5:Extension
     Category="windows.startupTask"
     Executable="MyStartup.exe"
     EntryPoint="MyStartup.App">
     <uap5:StartupTask
      TaskId="MyStartupId_UWP"
      Enabled="false"
      DisplayName="My Startup UWP" />
   </uap5:Extension>
  </Extensions>      
    </Application>
  </Applications>
  <Capabilities>
    <Capability Name="internetClient" />
  </Capabilities>
</Package>   

[注意]

  • 如果 Package.appxmanifest 有自行設定 TargetDeviceFamily 要記得也更新 MaxVersionTested 到 10.0.6299.0 以上,不然會出現參數錯誤的 System.ArgumentException: 'The parameter is incorrect.',參考如下設定:
    <Dependencies>
      <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.16299.0" />
    </Dependencies>
  • UAP namespace (contract version 5) 目前只有支援 Desktop 而已,記得要在 code 裏面做判斷
  • Category 屬性固定是 windows.startupTask
  • TaskId 用來識別 task 的唯一值,在 app 裏面利用 StartupTask Class 呼叫 Task 時需要
  • Desktop Bridge 的 EntryPoint 屬性使用 Windows.FullTrustApplication,在 UWP 程式使用 app 執行檔 (*.exe)
  • Desktop Bridge 的 Enabled 屬性可以設定 true/false,UWP 的屬性系統會自動忽略,因爲需要用戶手動同意才可以使用
  • Desktop Bridge 可以使用多組 startupTask extensions (每一個都有自己的 Executable),UWP 只能有一組

stet 3. 利用 StartupTask Class 取得狀態,如果未啓用跳出讓用戶選擇是否啓動

private async void OnRequestStartupClick(object sender, RoutedEventArgs e)
{
    // 取得目前 TaskId (MyStartupId_UWP, 根據設定在 package.appmanifest 的值) 的狀態
    var startupTask = await StartupTask.GetAsync("MyStartupId_UWP");
 
    if (startupTask.State == StartupTaskState.Disabled)
    {
       // 如果是 disabled 代表還沒有被加入到 Startup 裏面
       var newState = await startupTask.RequestEnableAsync();
       tblState.Text = newState.ToString();
    }
    else
    {
       tblState.Text = startupTask.State.ToString();
    }
}

StartupTask Class 只能在 foreground 時呼叫,可利用 RequestEnableAsync() 開啓 或 Disable() 取消 task 的使用。

StartupTaskState 的列舉有下列幾種:

Name Description
Disabled The task is disabled. 未啓用,可搭配 RequestEnableAsync() 請求授權
DisabledByPolicy The task is disabled by the administrator or group policy. Platforms that don't support startup tasks also report DisabledByPolicy. 權限不足,需要先去設定帳號或是帳號群組的權限才能使用
DisabledByUser The task was disabled by the user. It can only be re-enabled by the user. 這個很特別,如果在 RequestEnableAsync() 時按下取消,之後再詢問該 Task 就都會是 DisabledByUser; 如果從 Task Manager -> Startup 關閉該 Task 一樣會得到這個狀態
Enabled The task is enabled. 代表該 Task 已經被啓動

step 4. 處理自動啓動時傳入的 arguments 先在 App.xaml.cs 注冊處理 OnActivated 事件:

private Frame rootFrame;
protected override void OnActivated(IActivatedEventArgs args)
{
    CreateRootFrame(args.PreviousExecutionState);

    base.OnActivated(args);

    string parameters = string.Empty;

    if (args.Kind == ActivationKind.StartupTask)
    {
        // 處理來自 StartupTask 的參數
        var startupTaskArgs = args as StartupTaskActivatedEventArgs;

        // 準備需要的處理邏輯,帶入 MainPage 
        parameters = "from onactivated";
    }

    if (rootFrame.Content == null)
    {
        // When the navigation stack isn't restored navigate to the first page,
        // configuring the new page by passing required information as a navigation
        // parameter
        rootFrame.Navigate(typeof(MainPage), parameters);
    }
    // Ensure the current window is active
    Window.Current.Activate();
}

private void CreateRootFrame(ApplicationExecutionState previousState)
{
    rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        rootFrame.NavigationFailed += OnNavigationFailed;

        if (previousState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }
}

CreateRootFrame 是我自定義的來維持 rootFrame 正常被建立,因爲 Startup 之後啓動 App 是不會進去 OnLaunched 的所以需要處理 rootFrame 跟 App 初始化的邏輯。 這樣就完成了,是不是很容易。

======

UWP 支援度離 win32 又接近了一大步,希望有更多新功能的推出幫助開發。希望對大家有所幫助。

References: