Xamarin での watchOS のバックグラウンド タスク

watchOS 3 では、ウォッチ アプリが情報を最新の状態に保つ主な方法が 3 つあります:

  • いくつかの新しいバックグラウンド タスクの 1 つを使用する。
  • 時計の顔に合併症の1つを持っている (更新するための余分な時間を与える)。
  • ユーザーが新しい Dock にアプリをピン留めする (メモリに保持され、頻繁に更新される)。

アプリを最新の状態に保つ

開発者が watchOS アプリのデータとユーザー インターフェイスを最新の状態に保ち、更新できるすべての方法について説明する前に、このセクションでは、一般的な一連の使用パターンと、ユーザーが iPhone と Apple Watch の間を移動する方法を、時間帯と現在実行しているアクティビティ (運転など) に基づいて確認します。

次に例を示します。

1 日を通してユーザーが iPhone と Apple Watch の間を行き来する様子

  1. 朝、コーヒーを待っている間に、ユーザーは自分の iPhone で現在のニュースを数分間閲覧します。
  2. コーヒー ショップを出る前に、彼らはすぐに文字盤のコンプリケーションで天気をチェックします。
  3. 昼食前に、iPhone の Maps アプリを使用して近くのレストランを見つけ、予約をしてクライアントに会います。
  4. レストランに向かう間、彼らは Apple Watch に通知を受け取り、一目で、彼らの昼食の予定が遅れているのがわかっています。
  5. 夕方には、iPhone の Maps アプリを使用して、家に運転して帰る前に交通量をチェックします。
  6. 家に帰る途中、Apple Watch で牛乳を受け取るように求める iMessage 通知を受け取り、クイック返信機能を使用して応答 "OK" を送信します。

ユーザーが Apple Watch アプリを使いたいと思うのは "早見" (3秒以内) という性質上、アプリが必要な情報を取得し、ユーザーに表示する前に UI を更新するには、通常十分な時間がありません。

Apple が watchOS 3 に含めた新しい API を使用することで、アプリはバックグラウンド更新のスケジュールを設定し、ユーザーが要求する前に必要な情報を準備できます。 上で説明した天気のコンプリケーションの例を次に示します:

天気のコンプリケーションの例

  1. アプリは、特定の時刻にシステムによって起動されるようにスケジュールされます。
  2. アプリは、更新の生成に必要な情報をフェッチします。
  3. アプリは、新しいデータを反映するようにユーザー インターフェイスを再生成します。
  4. ユーザーがアプリのコンプリケーションをひとめで確認すると、ユーザーが更新を待つ必要なく、最新の情報が表示されます。

上記のように、watchOS システムは、利用可能なプールがきわめて限られている 1 つ以上のタスクを使用してアプリを起動します:

watchOS システムは、1 つ以上のタスクを使用してアプリを起動します

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 タスクを使用すると、アプリはバックグラウンドでその状態を更新できます。 通常、これには、NSUrlSession を使用してインターネットから新しいコンテンツをダウンロードするなどの別のタスクが含まれます。
  • バックグラウンド スナップショットの更新 - WKSnapshotRefreshBackgroundTask タスクを使用すると、システムがドッキングの設定に使用されるスナップショットを取得する前に、アプリでコンテンツと UI の両方を更新できます。
  • バックグラウンドの Watch Connectivity - WKWatchConnectivityRefreshBackgroundTask タスクは、ペアになっている iPhone からバックグラウンド データを受信すると、アプリに対して開始されます。
  • バックグラウンド URL セッション - バックグラウンド転送で承認が必要な場合、または完了 (正常にまたはエラーで) すると、アプリに対して WKURLSessionRefreshBackgroundTask タスクが開始されます。

これらのタスクについては、以下のセクションで詳しく説明します。

WKApplicationRefreshBackgroundTask

WKApplicationRefreshBackgroundTask は、将来の日付にアプリを起動するようにスケジュールできる一般的なタスクです:

将来の日付に起動する WKApplicationRefreshBackgroundTask

タスクの実行時に、アプリはコンプリケーション タイムラインの更新や、NSUrlSession で必要なデータのフェッチなど、あらゆる種類のローカル処理を実行できます。

WKURLSessionRefreshBackgroundTask

データのダウンロードが完了し、アプリで処理する準備ができたら、システムは WKURLSessionRefreshBackgroundTask を送信します:

データのダウンロードが完了したときの WKURLSessionRefreshBackgroundTask

データがバックグラウンドでダウンロードされる間、アプリは実行状態のままにされません。 代わりに、アプリはデータの要求をスケジュールし、その後中断され、システムはデータのダウンロードを処理し、ダウンロードが完了したときにのみアプリを再び起動させます。

WKSnapshotRefreshBackgroundTask

Apple は、watchOS 3 でユーザーが自分のお気に入りのアプリをピン留めしてすばやくアクセスできるドッキングを追加しました。 Apple Watch でユーザーがサイド ボタンを押すと、ピン留めされたアプリ スナップショットのギャラリーが表示されます。 ユーザーは左または右にスワイプして目的のアプリを見つけ、アプリをタップして起動し、スナップショットを実行中のアプリのインターフェイスに置き換えます。

スナップショットを実行中アプリ インターフェイスに置き換える

システムは、(WKSnapshotRefreshBackgroundTask を送信することによって) アプリの UI のスナップショットを定期的に取得し、それらのスナップショットを使用して Dock にデータを設定します。 watchOS は、このスナップショットを作成する前に、アプリのコンテンツと UI を更新する機会を提供します。

スナップショットは、アプリのプレビュー イメージと起動イメージの両方として機能するため、watchOS 3 では非常に重要です。 ユーザーが Dock のアプリで解決すると、全画面表示に展開され、フォアグラウンドに入って実行が開始されるため、スナップショットを最新の状態にすることが不可欠です:

ユーザーが Dock 内のアプリに停止すると、それが全画面表示に展開されます

ここでも、スナップショットが作成される前にアプリが (データと UI を更新して) 準備できるように、システムは WKSnapshotRefreshBackgroundTask を発行します:

このアプリは、スナップショットが作成される前にデータと UI を更新することで準備できます

アプリが WKSnapshotRefreshBackgroundTask を完了済みとマークすると、システムはアプリの UI のスナップショットを自動的に取得します。

重要

アプリが新しいデータを受信してユーザー インターフェイスを更新した後、またはユーザーに変更された情報が表示されない場合は、常に WKSnapshotRefreshBackgroundTask をスケジュールすることが重要です。

さらに、ユーザーがアプリから通知を受け取り、それをタップしてアプリをフォアグラウンドに移動する場合、スナップショットは起動画面としても機能するため、最新の状態である必要があります:

ユーザーがアプリから通知を受け取り、それをタップするとアプリをフォアグラウンドが移動します

ユーザーが watchOS アプリを操作してから 1 時間以上経過している場合は、既定の状態に戻ることができます。 既定の状態は、アプリごとに異なる意味を持つ場合があり、アプリの設計に基づいて、既定の状態がまったくない可能性があります。

WKWatchConnectivityRefreshBackgroundTask

watchOS 3 では、Apple は新しい WKWatchConnectivityRefreshBackgroundTask を介して Background Refresh API と Watch Connectivity を統合しました。 この新機能を使用すると、iPhone アプリは、watchOS アプリがバックグラウンドで実行されている間、対応するウォッチ アプリに新しいデータを配信できます:

iPhone アプリは、watchOS アプリがバックグラウンドで実行されている間、対応するウォッチ アプリに新しいデータを配信できます

コンプリケーション プッシュ、アプリ コンテキスト、ファイルの送信、または iPhone アプリからのユーザー情報の更新を開始すると、Apple Watch アプリがバックグラウンドで起動します。

WKWatchConnectivityRefreshBackgroundTask を介してウォッチ アプリを起動する場合は、標準の API メソッドを使用して iPhone アプリからデータを受信する必要があります。

WKWatchConnectivityRefreshBackgroundTask データ フロー

  1. セッションがアクティブになっていることを確認します。
  2. 値が true である限り、新しい HasContentPending プロパティを監視します。アプリには処理するデータが残っています。 前と同様に、アプリは、すべてのデータの処理が完了するまでタスクを保持する必要があります。
  3. 処理するデータがなくなった場合 (HasContentPending = false)、タスクを完了済みとマークしてシステムに返します。 これを行わないと、アプリに割り当てられたバックグラウンド ランタイムが使い果たされ、クラッシュ レポートが生成されます。

Background API のライフサイクル

新しい Background Tasks API のすべての部分を一緒に配置すると、一般的な一連の対話は次のようになります:

Background API のライフサイクル

  1. 最初に、watchOS アプリは、将来ある時点で呼び起こすバックグラウンド タスクをスケジュールします。
  2. アプリはシステムによって起動され、タスクが送信されます。
  3. アプリはタスクを処理して、必要な作業を完了します。
  4. タスクを処理した結果、アプリでは NSUrlSession を使用してコンテンツをダウンロードするなど、将来より多くの作業を完了するために、より多くのバックグラウンド タスクをスケジュールする必要がある場合があります。
  5. アプリはタスクが完了済みとマークし、システムに返します。

責任を持ってリソースを使用する

watchOS アプリは、システムの共有リソースのドレインを制限することで、このエコシステム内で責任を持って動作することが重要です。

次のシナリオを見てみましょう:

watchOS アプリは、システムの共有リソースのドレインを制限します

  1. ユーザーは、午後 1 時に watchOS アプリを起動します。
  2. アプリは、1 時間の午後 2 時に、目を覚まし、新しいコンテンツをダウンロードするタスクをスケジュールします。
  3. 午後 1 時 50 分に、ユーザーはアプリを再度開き、この時点でデータと UI を更新できます。
  4. 10 分後にタスクを再開するのではなく、1 時間後の午後 2 時 50 分に実行するように、アプリでタスクのスケジュールを変更する必要があります。

すべてのアプリに違いはありますが、Apple は、システム リソースを節約するために、上記のような使用パターンを見つけることを提案しています。

バックグラウンド タスクの実装

このドキュメントでは、例のために、ユーザーにサッカーのスコアを報告する偽の MonkeySoccer スポーツ アプリを使用します。

次の一般的な使用シナリオを見てみましょう:

一般的な使用シナリオ

ユーザーのお気に入りのサッカー チームは午後 7:00 から午後 9:00 まで大きな試合を行っているので、アプリはユーザーが定期的にスコアを確認することを期待し、30 分の更新間隔を決定します。

  1. ユーザーはアプリを開き、30 分後にバックグラウンド更新のタスクをスケジュールします。 Background API では、特定の時点で実行できるバックグラウンド タスクの種類は 1 つだけです。
  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
    }
  });
}

アプリを起動する必要があるときに、30 分後に新しい NSDate を作成し、要求されたタスクの詳細を保持する 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

    ...
  }
}

HandleBackgroundTasks メソッドは、システムが (backgroundTasks で) アプリを送信したすべてのタスクを循環して、WKUrlSessionRefreshBackgroundTask を検索します。 見つかった場合は、セッションに再び参加し、ダウンロードの完了を処理する 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 アプリがスコアを更新するためにかかった 5 分間のウィンドウの上の例に示すように、効率的に作業し、新しい watchOS 3 バックグラウンド タスクを使用することで、アプリは合計 15 秒間だけアクティブになりました:

アプリは合計 15 秒間だけアクティブでした

これにより、アプリが使用可能な Apple Watch リソースとバッテリー寿命の両方に与える影響が減少し、アプリがウォッチで実行されている他のアプリとより良く動作できるようになります。

スケジュールのしくみ

watchOS 3 アプリがフォアグラウンドにある間は、常に実行がスケジュールされ、データの更新や UI の再描画など、必要な任意の種類の処理を実行できます。 アプリがバックグラウンドに移動すると、通常はシステムによって中断され、すべてのランタイム操作が停止します。

アプリがバックグラウンドにある間は、特定のタスクをすばやく実行するために、システムのターゲットとなる場合があります。 そのため、watchOS 2 では、システムが一時的にバックグラウンド アプリを起動して、長い外観の通知の処理やアプリのコンプリケーションの更新などを行う場合があります。 watchOS 3 では、アプリをバックグラウンドで実行できる新しい方法がいくつかあります。

アプリがバックグラウンドにある間、システムではいくつかの制限が課されます:

  • 特定のタスクを完了するまでに数秒しか与えなくなります。 システムでは、渡された時間だけでなく、この制限を導き出すためにアプリが消費している CPU の電力量も考慮されます。
  • 制限を超えるアプリは、次のエラー コードで強制終了されます:
    • CPU - 0xc51bad01
    • Time - 0xc51bad02
  • システムは、実行するようにアプリに要求したバックグラウンド タスクの種類に基づいて異なる制限を課します。 たとえば、WKApplicationRefreshBackgroundTask タスクと WKURLSessionRefreshBackgroundTask タスクには、他の種類のバックグラウンド タスクよりも少し長いランタイムが与えられます。

コンプリケーションとアプリの更新

Apple が watchOS 3 に追加した新しいバックグラウンド タスクに加えて、watchOS アプリのコンプリケーションは、アプリがバックグラウンド更新を受け取る方法とタイミングに影響を与える可能性があります。

コンプリケーションは、役立つ情報をひとめで確認できる小さな視覚的要素です。 選択した文字盤に応じて、ユーザーは watchOS 3 のウォッチアプリから提供できる 1 つ以上のコンプリケーションで文字盤をカスタマイズすることができます。

ユーザーがアプリのコンプリケーションの 1 つを文字盤に含める場合は、アプリに次の更新された利点が与えられます:

  • システムがアプリを起動可能な状態に保ち、バックグラウンドでアプリの起動を試み、メモリに保持し、更新のための余分な時間を与えます。
  • コンプリケーションは、1 日あたり少なくとも 50 回のプッシュ更新が保証されます。

開発者は、上記の理由から、ユーザーを文字盤に追加するようユーザーを誘導するために、アプリに説得力のある複雑な問題を作成するよう常に努める必要があります。

watchOS 2 では、アプリがバックグラウンドでランタイムを受け取る主な方法は、コンプリケーションでした。 watchOS 3 では、コンプリケーション アプリは引き続き 1 時間に複数の更新プログラムを受け取れるように保証されますが、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 プロパティを使用して、アプリが 1 日に残した優先度の高い転送の数を確認し、その数に基づいてアクションを実行します。 アプリが転送時に実行されなくなった場合は、軽微な更新の送信を保留し、大幅な変更が発生した場合にのみ情報を送信できます。

スケジュール設定と Dock

Apple は、watchOS 3 でユーザーが自分のお気に入りのアプリをピン留めしてすばやくアクセスできるドッキングを追加しました。 Apple Watch でユーザーがサイド ボタンを押すと、ピン留めされたアプリ スナップショットのギャラリーが表示されます。 ユーザーは左または右にスワイプして目的のアプリを見つけ、アプリをタップして起動し、スナップショットを実行中のアプリのインターフェイスに置き換えます。

ドック

システムは、アプリの UI のスナップショットを定期的に取得し、それらのスナップショットを使用してドキュメントを設定します。watchOS は、このスナップショットを作成する前に、アプリのコンテンツと UI を更新する機会を提供します。

Dock にピン留めされたアプリでは、次のことが想定されます:

  • 1 時間あたり少なくとも 1 回の更新を受け取ります。 これには、アプリ更新タスクとスナップショット タスクの両方が含まれます。
  • 更新の予算は、Dock 内のすべてのアプリ間で分散されます。 そのため、ユーザーがピン留めしたアプリが少ないほど、各アプリが受け取る可能性のある更新が増えます。
  • アプリはメモリ内に保持されるため、Dock から選択すると、アプリはすぐに再開されます。

ユーザーが最後に実行したアプリは、最近使用したアプリと見なされ、Dock の最後のスロットを占有します。 そこから、ユーザーはそれを Dock に永続的にピン留めすることを選択できます。 最近使用したアプリは、ユーザーが既に Dock にピン留めしている他のお気に入りのアプリと同様に扱われます。

重要

ホーム画面にのみ追加されたアプリには、通常のスケジュール設定は適用されません。 定期的なスケジュールとバックグラウンド更新を受信するには、アプリを Dock に追加する必要があります

このドキュメントで既に説明したように、スナップショットは watchOS 3 で非常に重要です。これは、アプリのプレビューイメージと起動イメージの両方として機能するためです。 ユーザーが Dock のアプリで解決すると、全画面表示に展開され、フォアグラウンドに入って実行が開始されるため、スナップショットを最新の状態にすることが不可欠です。

システムがアプリの UI の新しいスナップショットが必要であると判断する場合があります。 この状況では、スナップショット要求はアプリのランタイム予算に対してカウントされません。 システム スナップショット要求は次のようにトリガーされます:

  • コンプリケーション タイムラインの更新。
  • アプリの通知に対するユーザー操作。
  • フォアグラウンドからバックグラウンド状態への切り替え。
  • 1 時間後にバックグラウンド状態になるため、アプリは既定の状態に戻ることができます。
  • watchOS の初回起動時。

ベスト プラクティス

Apple は、バックグラウンド タスクを操作する際に、次のベスト プラクティスを提案します:

  • アプリを更新する必要がある頻度でスケジュールします。 アプリを実行するたびに、将来のニーズを再評価し、必要に応じてこのスケジュールを調整する必要があります。
  • システムがバックグラウンド更新タスクを送信し、アプリが更新を必要としない場合は、更新が実際に必要になるまで作業を延期します。
  • アプリで使用できるすべてのランタイム 機会を検討します:
    • Dock とフォアグラウンドのアクティブ化。
    • 通知。
    • コンプリケーションの更新。
    • バックグラウンド更新。
  • 次のような汎用バックグラウンド ランタイムには、ScheduleBackgroundRefresh を使用します:
    • 情報を得るためのシステムのポーリング。
    • バックグラウンド データを要求するように将来の NSURLSessions をスケジュールします。
    • 既知の時間遷移。
    • コンプリケーション更新のトリガー。

スナップショットのベスト プラクティス

スナップショットの更新を使用する場合、Apple は次の提案を行います:

  • 重要なコンテンツ変更がある場合など、必要な場合にのみスナップショットを無効にします。
  • 頻度の高いスナップショットの無効化を回避します。 たとえば、タイマー アプリは、スナップショットを毎秒更新する必要はありません。タイマーが終了したときにのみ実行する必要があります。

アプリ データ フロー

Apple では、データ フローを操作するために次の方法を提案しています:

アプリのデータ フロー ダイアグラム

外部イベント (Watch Connectivity など) がアプリを起動します。 これにより、アプリはデータ モデル (アプリの現在の状態を表す) を強制的に更新します。 データ モデルの変更の結果、アプリは複雑さを更新し、新しいスナップショットを要求し、バックグラウンド NSURLSession を開始してより多くのデータをプルし、さらにバックグラウンド更新をスケジュールする必要があります。

アプリのライフサイクル

Dock とそれにお気に入りのアプリをピン留めする機能のために、Apple は watchOS 2 のときよりもユーザーがはるかに多くのアプリ間をはるかに頻繁に移動すると考えています。 その結果、アプリはこの変更を処理し、フォアグラウンド状態とバックグラウンド状態の間をすばやく移動する準備が整う必要があります。

Apple には、次の提案があります:

  • フォアグラウンドのアクティブ化に入ったら、できるだけ早くアプリがバックグラウンド タスクを完了していることを確認します。
  • NSProcessInfo.PerformExpiringActivity を呼び出して、バックグラウンドに入る前にすべてのフォアグラウンド作業を完了してください。
  • watchOS シミュレーターでアプリをテストする場合、タスクの予算は適用されないため、アプリは機能を適切にテストするために必要なだけ更新できます。
  • iTunes Connect に公開する前に、実際の Apple Watch ハードウェアで常にテストして、アプリが予算を超えて実行されていないことを確認します。
  • Apple は、テストとデバッグ中に Apple Watch を充電器につないでおくことを提案します。
  • コールド起動とアプリの再開の両方が完全にテストされていることを確認します。
  • すべてのアプリ タスクが完了していることを確認します。
  • Dock にピン留めするアプリの数を変えて、最良と最悪のシナリオの両方をテストします。

まとめ

この記事では、Apple が watchOS に対して行った機能強化と、ウォッチ アプリを最新の状態に保つために使用する方法について説明しました。 まず、Apple が watchOS 3 で追加したすべての新しいバックグラウンド タスクについて説明しました。 次に、バックグラウンド API ライフサイクルと、Xamarin watchOS アプリでバックグラウンド タスクを実装する方法について説明しました。 最後に、スケジュールのしくみについて説明し、いくつかのベスト プラクティスを示しました。