Xamarin 中的 watchOS 后台任务

使用 watchOS 3 时,监视应用可主要通过三种方法来使其信息保持最新状态:

  • 使用多个新后台任务中的一个。
  • 在表盘上有它的其中一个小组件(让它有额外的时间来更新)。
  • 让用户将应用固定到新的停靠栏(在这里,它保留在内存中并经常更新)。

使应用保持最新

在讨论开发人员可将 watchOS 应用数据和用户界面保持在当前和更新状态的所有方式之前,本部分将介绍一组典型的使用模式,并介绍用户在一整天中可如何根据当天的时间和他们正在做的活动(例如开车)在 iPhone 和 Apple Watch 之间切换。

请参见以下示例:

用户如何全天在 iPhone 和 Apple Watch 之间切换

  1. 早上,在排队买咖啡时,用户会花几分钟时间在 iPhone 上浏览当前新闻。
  2. 离开咖啡店之前,他们迅速在手表上查看了一下天气情况。
  3. 午餐前,他们使用 iPhone 上的地图应用查找附近的餐厅,并预订座位去见客户。
  4. 在去餐厅的路上,他们在 Apple Watch 上收到通知,快速扫一眼,他们就知道午餐约会要迟到了。
  5. 晚上,他们在开车回家前用 iPhone 上的地图应用查看交通情况。
  6. 在回家的路上,他们在 Apple Watch 上收到一条 iMessage 通知,让他们去取一些牛奶,然后他们使用快速回复功能发送“OK”的回复。

由于用户想要如何使用 Apple Watch 应用的“快速浏览”(不到 3 秒)特性,通常没有足够的时间让应用在显示给用户之前获取所需信息并更新其 UI。

通过使用 Apple 在 watchOS 3 中提供的新 API,应用可以计划后台刷新,并在用户请求之前准备好所需的信息。 以上面讨论的天气小组件为例:

天气小组件的示例

  1. 应用计划在特定时间被系统唤醒。
  2. 应用提取生成更新所需的信息。
  3. 应用重新生成其用户界面以反映新数据。
  4. 当用户浏览查看应用的小组件时,它有最新的信息,而用户不必等待更新。

如上所示,watchOS 系统使用一个或多个任务唤醒应用,它有非常有限的可用池:

watchOS 系统使用一个或多个任务唤醒应用

Apple 建议充分利用此任务(因为此资源对应用来说很有限),保留它直到应用已完成更新自身的过程。

系统通过调用 WKExtensionDelegate 委托的新 HandleBackgroundTasks 方法来交付这些任务。 例如:

using System;
using Foundation;
using WatchKit;

namespace MonkeyWatch.MonkeySeeExtension
{
  public class ExtensionDelegate : WKExtensionDelegate
  {
    #region Constructors
    public ExtensionDelegate ()
    {
    }
    #endregion

    #region Override Methods
    public override void HandleBackgroundTasks (NSSet<WKRefreshBackgroundTask> backgroundTasks)
    {
      // Handle background request here
      ...
    }
    #endregion
  }
}

当应用完成给定任务后,它会通过将任务标记为“已完成”,将其返回给系统:

应用将任务标记为已完成,从而将其返回到系统

新的后台任务

watchOS 3 引入了几个后台任务,应用可通过这些任务来更新信息,确保在用户打开应用之前,应用便已具有用户所需的内容,例如:

这些任务将在以下部分中详细介绍。

WKApplicationRefreshBackgroundTask

WKApplicationRefreshBackgroundTask 是一个通用任务,可安排该任务来让应用在将来的日期唤醒:

一个 WKApplicationRefreshBackgroundTask 在将来的日期唤醒

在任务运行时,应用可以执行任何类型的本地处理,例如更新小组件时间线,或者使用 NSUrlSession 提取一些必需的数据。

WKURLSessionRefreshBackgroundTask

在数据完成下载并准备好由应用进行处理时,系统将发送 WKURLSessionRefreshBackgroundTask

数据完成下载后的 WKURLSessionRefreshBackgroundTask

在后台下载数据时,应用不会保持运行状态。 相反,应用会计划对数据的请求,然后暂停,系统处理数据的下载,仅在下载完成后才重新唤醒应用。

WKSnapshotRefreshBackgroundTask

在 watchOS 3 中,Apple 添加了停靠栏,用户可在这里固定他们最喜欢的应用并快速访问它们。 当用户按下 Apple Watch 上的侧边按钮时,将显示已固定的应用快照库。 用户可以向左或向右轻扫来查找所需应用,然后点击应用以启动它,将快照替换为正在运行的应用界面。

将快照替换为正在运行的应用接口

系统定期获取应用 UI 的快照(通过发送 WKSnapshotRefreshBackgroundTask),并使用这些快照填充停靠栏。 watchOS 让应用有机会在拍摄此快照之前更新其内容和 UI。

快照在 watchOS 3 中非常重要,因为它们同时充当应用的预览图像和启动图像。 如果用户在停靠栏中选择了一个应用,该应用将展开到全屏,进入前台并开始运行,因此快照必须是最新的:

如果用户打开 Dock 中的应用,该应用将展开为全屏

同样,系统将发出WKSnapshotRefreshBackgroundTask,这样应用就能在拍摄快照之前做好准备(通过更新数据和 UI):

应用可以通过在拍摄快照之前更新数据和 UI 来做准备

当应用将 WKSnapshotRefreshBackgroundTask 标记为“已完成”时,系统将自动拍摄应用 UI 的快照。

重要

请务必始终在应用收到新数据并更新其用户界面之后计划 WKSnapshotRefreshBackgroundTask,否则用户看不到修改后的信息。

此外,当用户从应用收到通知并点击它来将应用带到前台时,快照需要是最新的,因为它也充当启动屏幕:

用户从应用收到通知并点击通知,会将应用引入前台

如果用户已与 watchOS 应用交互超过 1 小时,它将能够返回到其默认状态。 对于不同的应用,默认状态可能意味着不同的内容,并且根据应用设计,它可能根本没有默认状态。

WKWatchConnectivityRefreshBackgroundTask

在 watchOS 3 中,Apple 通过新的 WKWatchConnectivityRefreshBackgroundTask 将手表连接与后台刷新 API 相集成。 使用此新功能,当 watchOS 应用在后台运行时,iPhone 应用可以将新数据传送到其手表应用对应项:

当 watchOS 应用在后台运行时,iPhone 应用可以将新数据传送到其手表应用对应项

启动小组件推送、应用上下文、发送文件或从 iPhone 应用更新用户信息将在后台唤醒 Apple Watch 应用。

手表应用通过 WKWatchConnectivityRefreshBackgroundTask 唤醒时,需要使用标准 API 方法从 iPhone 应用接收数据。

WKWatchConnectivityRefreshBackgroundTask 数据流

  1. 确保会话已激活。
  2. 只要值是 true,就监视新的 HasContentPending 属性,应用仍有数据要处理。 与以前一样,应用应保留任务,直到它处理完所有数据。
  3. 当没有更多数据需要处理时 (HasContentPending = false),将任务标记为“已完成”以将其返回给系统。 如果做不到这一点,将耗尽应用分配的后台运行时,导致崩溃报告。

后台 API 生命周期

将新的后台任务 API 的所有部分放在一起,一组典型的交互如下所示:

后台 API 生命周期

  1. 首先,watchOS 应用将后台任务安排为在未来的某个时间点被唤醒。
  2. 应用由系统唤醒并被发送一个任务。
  3. 应用处理该任务以完成所需的一切工作。
  4. 由于处理任务,应用可能需要安排更多后台任务才能在将来完成更多工作,例如使用 NSUrlSession 下载更多内容。
  5. 应用将任务标记为“已完成”,并将其返回给系统。

负责任地使用资源

至关重要的是,watchOS 应用通过限制其对系统共享资源的消耗,在此生态系统中负责任地做出行为。

请查看以下场景:

watchOS 应用会限制其对系统的共享资源的消耗

  1. 用户在下午 1:00 启动 watchOS 应用。
  2. 该应用安排了一个任务,在下午 2:00 的一小时内唤醒并下载新内容。
  3. 在下午 1:50,用户重新打开应用,允许它此时更新其数据和 UI。
  4. 应用应重新安排任务在一小时后(下午 2:50)运行,而不是让应用在 10 分钟后再次唤醒应用。

虽然应用各有不同,但 Apple 建议查找使用模式(如上所示),以帮助节省系统资源。

实现后台任务

为了便于示例,本文档将使用假的 MonkeySoccer 体育应用向用户报告足球分数。

请查看以下典型使用场景:

典型使用方案

用户最喜欢的足球队是晚上 7:00 到 9:00 有一场大型比赛,因此应用应期望用户定期检查得分,并决定 30 分钟的更新间隔。

  1. 用户打开应用,该应用安排了一个任务,在 30 分钟后进行后台更新。 后台 API 只允许在给定时间运行一种类型的后台任务。
  2. 应用接收任务并更新其数据和 UI,然后安排另一个后台任务在 30 分钟后执行。 重要的是,开发人员要记得安排另一个后台任务,否则应用永远不会重新唤醒来获取更多更新。
  3. 同样,应用接收任务并更新其数据,更新其 UI,请安排另一个后台任务在 30 分钟后执行。
  4. 相同的进程会再次重复。
  5. 接收到最后一个后台任务,应用会再次更新其数据和 UI。 由于这是最终分数,因此它不会安排新的后台刷新。

计划更新更新

鉴于上述场景,MonkeySoccer 应用可以使用以下代码来计划后台更新:

private void ScheduleNextBackgroundUpdate ()
{
  // Create a fire date 30 minutes into the future
  var fireDate = NSDate.FromTimeIntervalSinceNow (30 * 60);

  // Create
  var userInfo = new NSMutableDictionary ();
  userInfo.Add (new NSString ("LastActiveDate"), NSDate.FromTimeIntervalSinceNow(0));
  userInfo.Add (new NSString ("Reason"), new NSString ("UpdateScore"));

  // Schedule for update
  WKExtension.SharedExtension.ScheduleBackgroundRefresh (fireDate, userInfo, (error) => {
    // Was the Task successfully scheduled?
    if (error == null) {
      // Yes, handle if needed
    } else {
      // No, report error
    }
  });
}

当应用想要被唤醒时,它会创建一个新的 NSDate(30 分钟后),它还会创建一个 NSMutableDictionary 来保留所请求的任务的详细信息。 SharedExtensionScheduleBackgroundRefresh 方法用于请求要计划的任务。

如果系统无法计划所请求的任务,它将返回 NSError

处理更新

接下来,仔细看看 5 分钟时段,其中显示了更新分数所需的步骤:

显示更新分数所需的步骤的 5 分钟时段

  1. 在晚上 7:30:02,应用被系统唤醒,并接收更新后台任务。 其首要任务是从服务器获取最新分数。 请参阅下面的计划 NSUrlSession
  2. 在 7:30:05,应用完成最初的任务时,系统将应用置于睡眠状态,并继续在后台下载请求的数据。
  3. 系统完成下载后,会创建一个新的任务来唤醒应用,以便它可以处理已下载的信息。 请参阅下面的处理后台任务处理下载完成
  4. 应用保存更新后的信息并将任务标记为“已完成”。 开发人员可能会想在此时更新应用的用户界面,但是 Apple 建议安排一个快照任务来处理此过程。 请参阅下面的计划快照更新
  5. 应用接收快照任务,更新其用户界面,并将任务标记为“已完成”。 请参阅下面的处理快照更新

计划 NSUrlSession

以下代码可用于计划最新分数的下载:

private void ScheduleURLUpdateSession ()
{
  // Create new configuration
  var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration ("com.example.urlsession");

  // Create new session
  var backgroundSession = NSUrlSession.FromConfiguration (configuration);

  // Create and start download task
  var downloadTask = backgroundSession.CreateDownloadTask (new NSUrl ("https://example.com/gamexxx/currentScores.json"));
  downloadTask.Resume ();
}

它配置并创建新的 NSUrlSession,然后使用该会话通过 CreateDownloadTask 方法创建新的下载任务。 它调用下载任务的 Resume 方法来启动会话。

处理后台任务

通过重写 WKExtensionDelegateHandleBackgroundTasks 方法,应用可以处理传入的后台任务:

using System;
using System.Collections.Generic;
using Foundation;
using WatchKit;

namespace MonkeySoccer.MonkeySoccerExtension
{
  public class ExtensionDelegate : WKExtensionDelegate
  {
    #region Computed Properties
    public List<WKRefreshBackgroundTask> PendingTasks { get; set; } = new List<WKRefreshBackgroundTask> ();
    #endregion

    ...

    #region Public Methods
    public void CompleteTask (WKRefreshBackgroundTask task)
    {
      // Mark the task completed and remove from the collection
      task.SetTaskCompleted ();
      PendingTasks.Remove (task);
    }
    #endregion

    #region Override Methods
    public override void HandleBackgroundTasks (NSSet<WKRefreshBackgroundTask> backgroundTasks)
    {
      // Handle background request
      foreach (WKRefreshBackgroundTask task in backgroundTasks) {
        // Is this a background session task?
        var urlTask = task as WKUrlSessionRefreshBackgroundTask;
        if (urlTask != null) {
          // Create new configuration
          var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration (urlTask.SessionIdentifier);

          // Create new session
          var backgroundSession = NSUrlSession.FromConfiguration (configuration, new BackgroundSessionDelegate (this, task), null);

          // Keep track of all pending tasks
          PendingTasks.Add (task);
        } else {
          // Ensure that all tasks are completed
          task.SetTaskCompleted ();
        }
      }
    }
    #endregion

    ...
  }
}

通过搜索 WKUrlSessionRefreshBackgroundTaskHandleBackgroundTasks 方法循环访问系统已发送给应用的所有任务(在 backgroundTasks 中)。 如果找到任务,它将重新加入会话并附加一个 NSUrlSessionDownloadDelegate 来处理下载完成(请参阅下面的处理下载完成):

// Create new session
var backgroundSession = NSUrlSession.FromConfiguration (configuration, new BackgroundSessionDelegate (this, task), null);

它通过将任务添加到集合中,在任务上保留一个句柄,直到任务完成:

public List<WKRefreshBackgroundTask> PendingTasks { get; set; } = new List<WKRefreshBackgroundTask> ();
...

// Keep track of all pending tasks
PendingTasks.Add (task);

发送到应用的所有任务都需要完成,对于当前未处理的任何任务,请将其标记为“已完成”:

if (urlTask != null) {
  ...
} else {
  // Ensure that all tasks are completed
  task.SetTaskCompleted ();
}

处理下载完成

MonkeySoccer 应用使用以下 NSUrlSessionDownloadDelegate 委托来处理下载完成并处理请求的数据:

using System;
using Foundation;
using WatchKit;

namespace MonkeySoccer.MonkeySoccerExtension
{
  public class BackgroundSessionDelegate : NSUrlSessionDownloadDelegate
  {
    #region Computed Properties
    public ExtensionDelegate WatchExtensionDelegate { get; set; }

    public WKRefreshBackgroundTask Task { get; set; }
    #endregion

    #region Constructors
    public BackgroundSessionDelegate (ExtensionDelegate extensionDelegate, WKRefreshBackgroundTask task)
    {
      // Initialize
      this.WatchExtensionDelegate = extensionDelegate;
      this.Task = task;
    }
    #endregion

    #region Override Methods
    public override void DidFinishDownloading (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
    {
      // Handle the downloaded data
      ...

      // Mark the task completed
      WatchExtensionDelegate.CompleteTask (Task);

    }
    #endregion
  }
}

初始化时,它会保留 ExtensionDelegate 和生成它的 WKRefreshBackgroundTask 的句柄。 它会重写 DidFinishDownloading 方法来处理下载完成。 然后,使用 ExtensionDelegateCompleteTask 方法通知任务其已完成,并将其从挂起任务集合中删除。 请参阅上面的处理后台任务

计划快照更新

以下代码可用于计划快照任务,以使用最新分数更新 UI:

private void ScheduleSnapshotUpdate ()
{
  // Create a fire date of now
  var fireDate = NSDate.FromTimeIntervalSinceNow (0);

  // Create user info dictionary
  var userInfo = new NSMutableDictionary ();
  userInfo.Add (new NSString ("lastActiveDate"), NSDate.FromTimeIntervalSinceNow (0));
  userInfo.Add (new NSString ("reason"), new NSString ("UpdateScore"));

  // Schedule for update
  WKExtension.SharedExtension.ScheduleSnapshotRefresh (fireDate, userInfo, (error) => {
    // Was the Task successfully scheduled?
    if (error == null) {
      // Yes, handle if needed
    } else {
      // No, report error
    }
  });
}

就像上述 ScheduleURLUpdateSession 方法一样,它会创建一个新的 NSDate,它还会创建一个 NSMutableDictionary 来保留所请求的任务的详细信息。 SharedExtensionScheduleSnapshotRefresh 方法用于请求要计划的任务。

如果系统无法计划所请求的任务,它将返回 NSError

处理快照更新

为了处理快照任务,会修改 HandleBackgroundTasks 方法(请参阅上面的处理后台任务),使其如下所示:

public override void HandleBackgroundTasks (NSSet<WKRefreshBackgroundTask> backgroundTasks)
{
  // Handle background request
  foreach (WKRefreshBackgroundTask task in backgroundTasks) {
    // Take action based on task type
    if (task is WKUrlSessionRefreshBackgroundTask) {
      var urlTask = task as WKUrlSessionRefreshBackgroundTask;

      // Create new configuration
      var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration (urlTask.SessionIdentifier);

      // Create new session
      var backgroundSession = NSUrlSession.FromConfiguration (configuration, new BackgroundSessionDelegate (this, task), null);

      // Keep track of all pending tasks
      PendingTasks.Add (task);
    } else if (task is WKSnapshotRefreshBackgroundTask) {
      var snapshotTask = task as WKSnapshotRefreshBackgroundTask;

      // Update UI
      ...

      // Create a expiration date 30 minutes into the future
      var expirationDate = NSDate.FromTimeIntervalSinceNow (30 * 60);

      // Create user info dictionary
      var userInfo = new NSMutableDictionary ();
      userInfo.Add (new NSString ("lastActiveDate"), NSDate.FromTimeIntervalSinceNow (0));
      userInfo.Add (new NSString ("reason"), new NSString ("UpdateScore"));

      // Mark task complete
      snapshotTask.SetTaskCompleted (false, expirationDate, userInfo);
    } else {
      // Ensure that all tasks are completed
      task.SetTaskCompleted ();
    }
  }
}

该方法会测试正在处理的任务的类型。 如果它是 WKSnapshotRefreshBackgroundTask,会获取对任务的访问权限:

var snapshotTask = task as WKSnapshotRefreshBackgroundTask;

该方法将更新用户界面,然后创建一个 NSDate 命令,告知系统快照何时过期。 它使用用户信息创建一个 NSMutableDictionary 来描述新快照,并将快照任务标记为“标已完成”,显示以下信息:

// Mark task complete
snapshotTask.SetTaskCompleted (false, expirationDate, userInfo);

此外,它还告知快照任务应用未返回到默认状态(在第一个参数中)。 没有默认状态概念的应用应始终将此属性设置为 true

有效使用

如上述示例所示,MonkeySoccer 通过高效工作并使用新的 watchOS 3 后台任务在 5 分钟时段内更新其分数,应用总共只有 15 秒的时间处于活动状态:

该应用仅处于活动状态 15 秒

这降低了应用对可用 Apple Watch 资源和电池续航时间的影响,还使应用能够更好地处理在手表上运行的其他应用。

计划的工作原理

当 watchOS 3 应用处于前台时,它始终被安排运行,并且可以执行任何类型的必需处理,例如更新数据或重新绘制其 UI。 当应用进入后台时,系统通常会暂停该应用,并且所有运行时操作都会停止。

当应用处于后台时,系统可能会针对它快速运行特定任务。 因此,在 watchOS 2 中,系统可能会暂时唤醒后台应用来执行一些操作,例如处理长时间显示的通知或更新应用的小组件。 在 watchOS 3 中,可以通过多种新方式在后台运行应用。

当应用处于后台时,系统会对其施加一些限制:

  • 它只有几秒钟的时间来完成任何给定的任务。 系统不仅考虑了经过的时间,还考虑了应用达到此限制所消耗的 CPU 功率。
  • 任何超出限制的应用都将被终止,并显示以下错误代码:
    • CPU - 0xc51bad01
    • 时间 - 0xc51bad02
  • 系统将根据它要求应用执行的后台任务的类型施加不同的限制。 例如,与其他类型的后台任务相比,向 WKApplicationRefreshBackgroundTaskWKURLSessionRefreshBackgroundTask 任务分配的运行时略长一些。

小组件和应用更新

除了 Apple 在 watchOS 3 中添加的新后台任务外,watchOS 应用的小组件还会影响应用接收后台更新的方式和时间。

小组件是一种小型视觉元素,可让你一目了然地了解有用的信息。 根据所选的表盘,用户能够使用 watchOS 3 中的手表应用可提供的一个或多个小组件自定义表盘。

如果用户在表盘上显示应用的其中一个小组件,它将为应用提供以下更新的好处:

  • 它使系统将应用保持在随时可启动状态,在该状态下,它会尝试在后台启动应用,将其保留在内存中,并为其提供额外的更新时间。
  • 保证小组件每天至少 50 个推送更新。

由于上面列出的原因,开发人员应始终努力为其应用创建吸引人的小组件,吸引用户将这些小组件添加到表盘。

在 watchOS 2 中,小组件是应用在后台接收运行时的主要方式。 在 watchOS 3 中,仍会确保小组件每小时接收多个更新,但是,它可使用 WKExtensions 请求更多运行时来更新其小组件。

请查看下列用于从连接的 iPhone 应用更新小组件的代码:

using System;
using WatchConnectivity;
using UIKit;
using Foundation;
using System.Collections.Generic;
using System.Linq;
...

private void UpdateComplication ()
{

  // Get session and the number of remaining transfers
  var session = WCSession.DefaultSession;
  var transfers = session.RemainingComplicationUserInfoTransfers;

  // Create user info dictionary
  var iconattrs = new Dictionary<NSString, NSObject>
    {
      {new NSString ("lastActiveDate"), NSDate.FromTimeIntervalSinceNow (0)},
      {new NSString ("reason"), new NSString ("UpdateScore")}
    };

  var userInfo = NSDictionary<NSString, NSObject>.FromObjectsAndKeys (iconattrs.Values.ToArray (), iconattrs.Keys.ToArray ());

  // Take action based on the number of transfers left
  if (transfers < 1) {
    // No transfers left, either attempt to send or inform
    // user of situation.
    ...
  } else if (transfers < 11) {
    // Running low on transfers, only send on important updates
    // else conserve for a significant change.
    ...
  } else {
    // Send data
    session.TransferCurrentComplicationUserInfo (userInfo);
  }
}

它使用 WCSessionRemainingComplicationUserInfoTransfers 属性来查看应用在当天还剩下多少个高优先级传输,然后根据此数量采取行动。 如果应用在传输时开始运行下降,它可以推迟发送次要更新,并且仅在有重大更改时发送信息。

计划和停靠

在 watchOS 3 中,Apple 添加了停靠栏,用户可在这里固定他们最喜欢的应用并快速访问它们。 当用户按下 Apple Watch 上的侧边按钮时,将显示已固定的应用快照库。 用户可以向左或向右轻扫来查找所需应用,然后点击应用以启动它,将快照替换为正在运行的应用界面。

Dock

系统会定期拍摄应用 UI 的快照,并使用这些快照填充文档。watchOS 让应用有机会在拍摄此快照之前更新其内容和 UI。

已固定到停靠栏的应用可期待以下内容:

  • 它们每小时至少会收到一次更新。 这包括应用刷新任务和快照任务。
  • 更新预算在停靠栏中的所有应用之间分布。 因此,用户固定的应用越少,每个应用将接收的潜在更新越多。
  • 应用将保留在内存中,因此从停靠栏中选择后,应用将快速恢复。

用户运行的最后一个应用将被视为最近使用的应用,并将占用停靠栏中的最后一个槽。 从这里,用户可以选择将其永久固定到停靠栏。 最近使用的应用将被视为用户已固定到停靠栏的任何其他收藏夹应用。

重要

只有添加到主屏幕的应用不会具有任何定期计划。 若要接收定期计划和后台更新,必须将应用添加到停靠栏。

正如本文档前面所述,快照在 watchOS 3 中非常重要,因为它们同时充当应用的预览图像和启动图像。 如果用户在停靠栏中选择了一个应用,该应用将展开到全屏,进入前台并开始运行,因此快照必须是最新的。

有时候,系统可能会决定它需要应用的 UI 的全新快照。 在这种情况下,快照请求不会计入应用的运行时预算。 下面将触发系统快照请求:

  • 小组件时间线更新。
  • 用户与应用的通知交互。
  • 从前台状态切换到后台状态。
  • 处于后台状态一小时后,应用可以返回到默认状态。
  • watchOS 首次启动时。

最佳方案

Apple 建议在处理后台任务时采用以下最佳做法:

  • 按照应用需要更新的频率进行计划。 每次运行应用时,它都应重新评估其未来需求,并根据需要调整此计划。
  • 如果系统发送后台刷新任务,并且应用不需要更新,请推迟工作,直到真正需要更新。
  • 请考虑应用可用的所有运行时机会:
    • 停靠和前台激活。
    • 通知。
    • 小组件更新。
    • 后台刷新。
  • 对常规用途后台运行时使用 ScheduleBackgroundRefresh,例如:
    • 轮询系统以获取信息。
    • 计划将来的 NSURLSessions 来请求后台数据。
    • 已知时间转换。
    • 触发小组件更新。

快照最佳做法

使用快照更新时,Apple 给出以下建议:

  • 仅当需要时才使快照失效,例如,当发生重大内容更改时。
  • 避免高频快照失效。 例如,计时器应用不应每秒更新一次快照,而是应该只在计时器结束时进行更新。

应用数据流

Apple 建议通过以下项来使用数据流:

应用数据流关系图

外部事件(例如手表连接)唤醒应用。 这会强制应用更新其数据模型(表示应用当前状态)。 由于数据模型更改,应用需要更新其小组件,请求新的快照,可能会启动后台 NSURLSession 来拉取更多数据并计划进一步的后台刷新。

应用生命周期

由于停靠栏和将喜欢的应用固定到停靠栏上的功能,Apple 认为与使用 watchOS 2 相比,用户将在更多应用之间更频繁地切换。 因此,应用应准备好处理此更改,并快速在前台和后台状态之间移动。

Apple 提供以下建议:

  • 确保应用在进入前台激活后尽快完成任何后台任务。
  • 通过调用 NSProcessInfo.PerformExpiringActivity,确保在进入后台之前完成所有前台工作。
  • 在 watchOS 模拟器中测试应用时,不会强制实施任何任务预算,因此应用可以尽可能多地刷新以正确测试功能。
  • 在发布到 iTunes Connect 之前,请始终在实际 Apple Watch 硬件上测试,确保应用未超过其预算。
  • Apple 建议在测试和调试时将 Apple Watch 保留在充电器上。
  • 确保对应用的冷启动和恢复进行全面测试。
  • 验证是否已完成所有应用任务。
  • 根据停靠栏中已固定的应用数来测试最佳和最坏的情况。

总结

本文介绍了 Apple 对 watchOS 所做的增强,说明了如何使用这些增强来让手表应用保持最新状态。 首先,它介绍了 Apple 在 watchOS 3 中添加的所有新的后台任务。 然后,它介绍了后台 API 生命周期,还说明了如何在 Xamarin watchOS 应用中实现后台任务。 最后,它介绍了计划的工作原理,并提供了一些最佳做法。