在后台中高效工作 - 后台任务

在我此前所发布的博文(在后台保持高工作效率)中,我向各位解释了 Windows 8 的后台模型,以及如何让您的应用程序即使在屏幕之外也能以节能高效的方式运行。而在今天的博文中,我将谈谈后台任务,以及如何让您的应用程序即使在挂起时也可于后台中运行代码。我将向您介绍两个常见的应用场景,并利用示例代码向您展示如何在后台中运行自己的应用程序代码;此外,本篇博文的内容还将包括借助支持锁屏的应用程序每 15 分钟下载一次 POP 电子邮件,以及当设备采用交流供电时,如何确保所有应用程序可在后台运行。

简介

后台任务触发器是为大量应用场景和应用程序而设计,因此其拥有大量不同的要求和资源管理限制。其中某些后台任务触发器是为某些需要始终保持最新的应用程序(例如电子邮件、VOIP)而设计;而其他后台任务触发器则为条件性更强的应用场景(例如在交流电源上或某些系统条件发生变更时运行维护任务)而设计。需要始终保持最新的应用程序需要显示于锁屏中,这一要求仅限于 7 个应用程序;本博文的稍后部分将详细阐述这一问题。相反,如果您希望进行条件性的操作,那么任何应用程序都可使用交流供电的维护触发器或某些系统触发器。应用程序在使用这些触发器时无需显示于锁屏中。显示于锁屏中的应用程序拥有更为宽松的资源管理限制,这是因为他们运行得更为频繁(因此我们限定了使用数量,并为用户提供了控制权!)。我将在稍后为您详细介绍这些内容。

如果您只是希望确保应用程序的内容保持最新,那么您没有必要使用后台任务。正如我们在博文打造卓越的磁贴体验中所解释的一样,您可始终使用动态磁贴或已计划的通知。接下来让我们利用这一简介,深入研究代码的相关内容!

当设备使用交流电源时,进行后台操作

正如我们在上一篇博文中所了解的,您的应用程序有时需要其自身的代码来提供后台操作功能。例如,假设您需要向您的应用程序数据库中添加图片库内的所有照片,或使用某种方式来处理这些照片(例如生成缩略图)。您可在当应用程序于前台运行,并与用户交互时进行这一操作,或者您也可使用仅在设备使用交流电源时于后台运行的维护触发器,进行后台操作。所有人都可使用维护触发器,而且您的应用程序不需要显示于锁屏之中。使用维护触发器后台任务的优势在于其可确保不中断用户的活动,并仅在交流电源供电时运行。因此,您不必担心电池使用过量。

这一代码示例显示了维护后台任务在任务运行时调用 ProcessPictures 类别来处理文件。该任务将每 8 小时运行一次,以查找需要处理的新文件。后台任务同时将注册取消的处理程序,这是因为设备切换至电池供电模式时,维护后台操作将被取消。在取消处理程序中,文件处理将被取消。如果您未在 5 秒内从取消处理程序内返回,那么 Windows 将终止该应用程序。请注意这些代码示例假定了后台任务的知识和触发器的类别。有关这些触发器类别的详细信息,请参阅开发人员中心内 Windows.ApplicationModel.Background 命名空间的文档。

C#
 public sealed class MaintenanceBackgroundTask: IBackgroundTask
{
    private ProcessPictures processPic; 
 
    public MaintenanceBackgroundTask()
    {
        // Code to process the pictures 
processPic = new ProcessPictures(); 
    }

    //Main Run method which is activated every 8 hours
   async void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance)
    {
        taskInstance.Canceled += taskInstance_Canceled;

// Because these methods are async, you must use a deferral 
        // to wait for all of them to complete
        BackgroundTaskDeferral deferral = taskInstance.GetDeferral(); 
        List<StorageFile> list = new List<StorageFile>();
        int count = await processPic.EnumerateFiles(list);
        bool retval = await processPic.ProcessFiles(list); 
        
        deferral.Complete(); 
    }

    // Cancel handler, called whenever the task is canceled
    void taskInstance_Canceled(IBackgroundTaskInstance sender, 
            BackgroundTaskCancellationReason reason)
    {
        // Device is now on DC power, cancel processing of files 
        processPic.Cancel = true; 
    }
}

 

JavaScript
 // This JS lives within maintenanceBackgroundTask.js
var processPic = new processPictures();
var count = 0;
var retval = false;

function onCanceled(cancelSender, cancelReason) {
    // Device is now on DC power, cancel processing of files
    processPic.cancel = true;
}
backgroundTaskInstance.addEventListener("canceled", onCanceled);

var list = [];
processPic.enumerateFiles(list).then(function (value) {
    count = value;
    processPic.processFiles(list).then(function (value) {
        retval = value;
// Call close() to indicate the task is complete when 
// all async methods have completed
        close();
    });
});

                   

这一代码示例显示了如何从主应用程序中注册维护后台任务。这一后台任务将每 8 小时启动一次来处理图片文档库中的所有图片。如果您不再需要进行这一操作,您可使用 IBackgroundTaskRegistration 类别中的取消注册方法来取消注册后台任务。

C#
 //Registering the maintenance trigger background task 
private bool RegisterMaintenanceBackgroundTask()
        {
            BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
            builder.Name = "Maintenance background task"; 
            builder.TaskEntryPoint = "MaintenanceTask.MaintenaceBackgroundTask";
            // Run every 8 hours if the device is on AC power 
            IBackgroundTrigger trigger = new MaintenanceTrigger(480, false);
            builder.SetTrigger(trigger); 
            IBackgroundTaskRegistration task = builder.Register(); 

            return true;
        }

 

JavaScript
 function registerMaintenanceBackgroundTask() 
{
    var builder = new Windows.ApplicationModel.Background.BackgroundTaskBuilder();
    builder.name = "Maintenance background task";
    builder.taskEntryPoint = "js\\maintenanceBackgroundTask.js";
    //Run every 8 hours if the device is on AC power
    var trigger = new Windows.ApplicationModel.Background.MaintenanceTrigger(480, false);
    builder.setTrigger(trigger);
    var task = builder.register();

    return true;
}

您必须在您的应用程序清单中声明后台任务。首先在 Visual Studio 中打开您应用程序的清单,并在“声明”选项卡中从下拉菜单内添加后台任务声明。请选择适用的任务类型,并指定您的入口点(后台任务的类别名称),或者如果您使用的是 JavaScript 后台任务,那么请选择“开始”页面。

后台任务清单声明 
图 1:后台任务清单声明

您可右键单击清单并选择“查看代码”来查看清单的内容。请注意:维护任务仅可在系统提供的主机(backgroundTaskHost.exe,JavaScript 中为 wwahost.exe)中运行,因此您无法指定任何可执行的属性。正如您可从此处的清单代码段中看见的,维护触发器的任务类型是 systemEvent。

 <Extension Category="windows.backgroundTasks" EntryPoint="MaintenanceTask.MaintenaceBackgroundTask">
         <BackgroundTasks>
           <Task Type="systemEvent" />
         </BackgroundTasks>
       </Extension>

在 JavaScript 中,EntryPoint 属性将被 StartPage 属性取代。

 <Extension Category="windows.backgroundTasks" StartPage="js\maintenaceBackgroundTask.js">

有关使用后台任务的详细信息,请参阅后台任务简介白皮书和开发人员中心内有关 API 使用的内容。

每 15 分钟下载一次 POP 邮件

在这一示例中,我们需要应用程序以可预测的方式在后台中频繁运行。您可将您的应用程序置于锁屏中,进而获得这一效果。

由于应用程序将使用后台任务来始终保持最新,即使当用户并未使用其 Windows 8 设备时依旧如此,用户通过授予应用程序显示于锁屏中的权限,进而控制哪些应用程序可使用这些后台任务。由于锁屏的设计意图是为用户提供有关其应用程序的信息,消除用户解锁 Windows 8 设备的需求,因此,这一方式将是一个天作之合。这一关系就像一条双向街道:您的应用程序仅可在其显示于锁屏中时使用这些后台任务类型,类似地,您的应用程序也仅可在其请求使用这些后台任务类型时显示于锁屏中。

background2_img2

图 2:锁屏个性化 UI 和支持锁屏的应用程序

由于相对而言,仅有一小部分应用程序可置于锁屏中,因此确保您的应用程序可为用户提供一个良好的锁屏体验将至关重要。如果其无法为用户提供良好的锁屏体验,那么用户将有可能删除该应用程序,而替换为其他内容。通信应用程序就是符合这一条件的应用程序示例;例如邮件应用程序可显示未读电子邮件的数量,日历应用程序能以详细的状态段显示即将发生的约会,而消息传递应用程序则可显示用户错过的消息数量。有关锁屏的详细信息(包括如何让应用程序脱颖而出,进而显示于锁屏中),请参阅开发人员中心

使用时间触发器每 15 分钟下载一次邮件

这一示例代码显示了如何注册时间触发器后台任务,如果 Internet 可用,那么该任务将使用 BackgroundTaskBuilder 类别每 15 分钟运行一次。如果 Internet 不可用,那么您的后台任务将不会运行。相反,其将等待 Internet 可用,再自动运行。上述内容是后台任务的另一有用特性,其将阻止不必要操作的发生,并保留设备的电池使用时间。如果没有该条件,应用程序代码将需要运行,再检测出无网络连接,然后再由于无法下载邮件而报错。无论应用程序是否处于前台,其都将下载邮件。如果设备处于连接的待机状态,应用程序也将进行下载。

C#
 private bool RegisterTimeTriggerBackgroundTask()
        {
            BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
            builder.Name = "Pop mail background task";
            builder.TaskEntryPoint = "MailClient.PopMailBackgroundTask";
            // Run every 15 minutes if the device has internet connectivity
            IBackgroundTrigger trigger = new TimeTrigger(15, false);
            builder.SetTrigger(trigger);
            IBackgroundCondition condition = 
                new SystemCondition(SystemConditionType.InternetAvailable);
            builder.AddCondition(condition); 
            IBackgroundTaskRegistration task = builder.Register();

            return true;
        }
JavaScript
 function registerTimeTriggerBackgroundTask() 
   {
       var builder = new Windows.ApplicationModel.Background.BackgroundTaskBuilder();
       builder.name = "Pop mail background task";
       builder.taskEntryPoint = "js\\popMailBackgroundTask.js";
       //Run every 15 minutes if the device has internet connectivity
       var trigger = new Windows.ApplicationModel.Background.TimeTrigger(15, false);
       builder.setTrigger(trigger);
       var condition = new Windows.ApplicationModel.Background.SystemCondition(                             
Windows.ApplicationModel.Background.SystemConditionType.internetAvailable);
       builder.addCondition(condition);
       var task = builder.register();

       return true;
   }

如上所述,时间触发器仅对锁屏中的应用程序可用。为了以编程方式请求在锁屏中放置内容,您需要使用 BackgroundExecutionManager 类别。如果用户未将您的应用程序置于锁屏中,那么您的后台任务将不会运行。如果是这样,您可能需要回滚使用不需要锁屏的后台任务,或在用户使用您的应用程序时执行该操作。因此系统将不会反复询问用户,而只是提示用户一次。如果用户给予否定答案,并希望稍后添加,那么他们可手动进行该操作。

C#
 public async Task<bool> ObtainLockScreenAccess()
        {
            BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync();
            
            if (status == BackgroundAccessStatus.Denied || status == BackgroundAccessStatus.Unspecified)
            {
                return false; 
            }

            return true; 
        }

 

JavaScript
 function obtainLockScreenAccess()
   {
       Windows.ApplicationModel.Background.BackgroundExecutionManager.requestAccessAsync().then(function (status) {
           if (status === Windows.ApplicationModel.Background.BackgroundAccessStatus.denied || 
               status === Windows.ApplicationModel.Background.BackgroundAccessStatus.unspecified){
               return false;
           }
           return true;
       });
   }

这一代码示例显示了可每 15 分钟下载一次邮件的主后台任务。由于此前的博文打造卓越的磁贴体验曾详细介绍如何更新您应用程序的磁贴和锁屏提醒,因而此处并未详细介绍这一内容。

C#
 void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance)
        {
            int count = popmailClient.GetNewMails(); 
            // Update badge on lock screen with the mail count 
            popmailClient.UpdateLockScreenBadgeWithNewMailCount(count);

            IList<string> mailheaders = popmailClient.GetNewMailHeaders(); 
            // Update app tile with the subjects of the email 
            popmailClient.UpdateTileWithSubjects(mailheaders); 
        }

 

JavaScript
 //This JS lives within popMailBackgroundTask.js
    var count = popmailClient.getNewMails();
    // Update badge on lock screen with the mail count
    popmailClient.updateLockScreenBadgeWithNewmailCount(count);

    var mailheaders = popmailClient.getnewMailHeaders();
    // Update app tile with the subjects of the email
    popmailClient.updatetileWithSubjects(mailheaders);
    close();

为了将您的应用程序添加至锁屏中,开发人员需要指定清单的“应用程序 UI”选项卡中的锁屏通知类型。


图 3:后台任务清单声明

这是某一应用程序(向导所生成的)清单的代码段,该应用程序需要显示于锁屏中,并在锁屏中显示锁屏提醒和 toast。时间触发器任务仅可在提供主机(backgroundTaskHost.exe,在 JavaScript 为 wwahost.exe)的系统中运行,因此您无法指定任何可执行的属性。任务类型是您可从清单中查看到的计时器。

 <LockScreen Notification="badgeAndTileText" BadgeLogo="Images\badgelogo.png" />
      <DefaultTile ShowName="allLogos" WideLogo="Images\tile-sdk.png" ShortName="LockScreen CS" />
      <SplashScreen Image="Images\splash-sdk.png" BackgroundColor="#FFFFFF" />
    </VisualElements>
    <Extensions>
      <Extension Category="windows.backgroundTasks" EntryPoint="MailClient.PopMailBackgroundTask">
        <BackgroundTasks>
          <Task Type="timer" />
        </BackgroundTasks>
      </Extension>

在 JavaScript 中,EntryPoint 属性将被 StartPage 属性取代。

 <Extension Category="windows.backgroundTasks" StartPage="js\popMailBackgroundTask.js"

 

高级应用场景

您可使用诸如控制通道或推送通知等其他后台任务触发器来构建更为高级的 VOIP、即时消息或推动邮件应用程序。这些触发器的使用范围远远不止本篇博文所提到的内容,有关这些触发器的详细信息,请参阅后台网络连接白皮书。

对于后台任务的资源管理

正如此前所提到的,后台任务的设计过程中充分考虑了节能性,因此我们对 CPU 和网络资源的使用设定的数项限制。这将阻止后台中的应用程序在用户不知情的情况下耗尽设备的电池。如果某一应用程序处于运行状态,且用户在前台与其进行交互,那么 CPU 和网络资源的限制将不会被应用至应用程序的后台任务。

CPU 资源限制

锁屏中的所有应用程序将每 15 分钟接收 2 秒 CPU 时间,这段时间可用于进行应用程序的全部后台任务。在 15 分钟结束时,锁屏中的每个应用程序将接收另外 2 秒 CPU 时间,用于进行后台任务。15 分钟间隔中所有未使用的 CPU 时间将丢失,而且应用程序无法将其累积。锁屏中的所有应用程序将每 2 小时接收 1 秒 CPU 时间。如果应用程序使用了其所有可用的 CPU 时间,那么其后台任务将被挂起,直至系统下一次进行 CPU 配额更新时补充应用程序的 CPU 配额。

CPU 的使用时间是指应用程序所使用的实际 CPU 时间,而不是后台任务的时钟时间。例如,如果后台任务等待其代码获取远程服务器的响应,而其实际上并未使用 CPU,那么这段等待时间就不能纳入 CPU 配额计算。

有关后台任务的 CPU 资源计算

 

CPU 资源配额

刷新时间段

支持锁屏的应用程序

2 CPU 秒

15 分钟

不支持锁屏的应用程序

1 CPU 秒

2 小时

网络资源限制

网络使用将大量消耗设备的电池电量,因此后台任务执行期间也将禁止网络使用。但是如果某一设备使用交流电源运行,那么后台任务中的网络连接将不受限制。后台任务的 CPU 使用将始终限制资源使用,即使设备使用交流电源运行。

以下表格显示了某一资源受限的 WiFi 网络的网络数据吞吐量(假定已最大程度降低干预程度)。

网络接口的平均吞吐量

支持锁屏的应用程序的 数据吞吐量,单位:兆字节 (MB)

不支持锁屏的应用程序的数据吞吐量,单位:MB

每 15 分钟

每天

每天

10 Mbps

1.875

180

30

 

共用的资源库

尽管每个应用程序都拥有分配的配额,但是有些时候这些固定的资源限制仍显得捉襟见肘。对于这些情形,应用程序可从一个共享的共用资源库中请求 CPU 和网络资源。

有关后台任务、共用资源库、及其资源管理限制和最佳做法的详细信息,请参阅后台任务简介白皮书。其同时包含项目示例,及其源代码。

总结

因此,对于问题“我的应用程序能否在不显示于屏幕中时执行操作”,我们将掷地有声地给出肯定答案。Windows 8 中的后台模型可让您的应用程序完成关键的最终用户应用场景,例如下载文件、播放音乐、在后台更新电子邮件或当设备使用交流电源时执行维护任务。而且由于平台将密切监控这些活动,因此您的后台操作将在最大程度上减少对前台应用程序响应性或设备电池使用时间的影响。

有关 Windows 8 中后台模型的详细技术信息,请参阅开发人员中心,并查看其中大量的白皮书。如果您有任何疑问,请随时将其发布于此处的评论栏中,我们将尽可能迅速、详细地给您回复。

-- Windows 项目经理 Hari Pulapaka

特别感谢 Jake Sabulsky、Johnny Bregar、Kyle Beck 和 Suhail Khalid 对本篇博文撰写所做出的极大贡献,同时也特别感谢 Alexander Corradini、Arun Kishan、Ben Srour、Ian LeGrow、Jamie Schwartz 和 John Sheehan 等人提供的宝贵反馈意见。

资源

链接

类型

要点

后台任务简介

白皮书

介绍后台任务

后台模型 API 命名空间

文档

后台模型 API 命名空间

后台任务示例项目

示例项目

演示后台任务的使用

锁屏概述

概念性文档

解释锁屏及其最佳做法

后台网络连接

白皮书

展示如何开发高级的应用程序,例如 VOIP、使用后台任务的即时消息传送。

打造卓越的磁贴体验

博文

展示如何创建卓越的磁贴体验