連載
» 2014年06月12日 14時00分 UPDATE

WinRT/Metro TIPS:WindowsとPhoneでバックグラウンドタスクを共有するには?[ユニバーサルWindowsアプリ開発]

ストアアプリとPhoneアプリで、共有コードを使ってバックグラウンドタスクを実装する方法を解説する。

[山本康彦(http://www.bluewatersoft.jp/),BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 Windows Phone 8.1の新しいアプリ実行環境では、バックグラウンドタスクの実装にWindowsストアアプリと同じAPIが使えるようになった。そうなると、ユニバーサルWindowsアプリを開発するときのバックグラウンドタスクは、1つの実装にして共有したくなるだろう。本稿では、例としてライブタイルを更新するバックグラウンドタスクの作り方を解説する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #78」からダウンロードできる。

事前準備

 ユニバーサルプロジェクトを使ってユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Express 2013 for Windowsを使っている。

  • SLAT対応のPC*1
  • 2014年4月のアップデート*2適用済みの64bit版Windows 8.1 Pro版以上
  • Visual Studio 2013 Update 2*3適用済みのVisual Studio 2013(以降、VS 2013)*4

*1 SLAT対応ハードウェアは、Windows Phone 8.1エミュレーターの実行に必要だ。ただし未対応でも、ソースコードのビルドと実機でのデバッグは可能だ。SLAT対応のチェック方法はMSDNブログの「Windows Phone SDK 8.0 ダウンロードポイント と Second Level Address Translation (SLAT) 対応PCかどうかを判定する方法」を参照。なお、SLAT対応ハードウェアであっても、VM上ではエミュレーターが動作しないことがあるのでご注意願いたい。

*2 事前には「Windows 8.1 Update 1」と呼ばれていたアップデート。スタート画面の右上に検索ボタンが(環境によっては電源ボタンも)表示されるようになるので、適用済みかどうかは簡単に見分けられる。ちなみに公式呼称は「the Windows RT 8.1, Windows 8.1, and Windows Server 2012 R2 update that is dated April, 2014」というようである。

*3 マイクロソフトのダウンロードページから誰でも入手できる。

*4 本稿に掲載したコードを試すだけなら、無償のExpressエディションで構わない。Visual Studio Express 2013 Update 2 for Windows(製品版)はマイクロソフトのページから無償で入手できる。Expressエディションはターゲットプラットフォームごとに製品が分かれていて紛らわしいが、Windowsストアアプリの開発には「for Windows」を使う(「for Windows Desktop」はデスクトップで動作するアプリ用)。


用語

 本稿では、紛らわしくない限り次の略称を用いる。

  • Windows:Windows 8.1とWindows RT 8.1(2014年4月のアップデートを適用済みのもの)
  • Phone:Windows Phone 8.1

サンプルコードについて

 Visual Studio 2013 Update 2のRTMがリリースされたが、残念なことに本稿執筆時点ではVB用のユニバーサルプロジェクトのテンプレートがまだ含まれていない*5。そのため、本稿で紹介するコードはC#のユニバーサルプロジェクトだけとさせていただく。別途公開しているサンプルコードには、ユニバーサルプロジェクトに似せた形のVBのコードも含めてある*6

*5 VB用のユニバーサルプロジェクトも近い将来に提供されるものと思われる。例えば、Windowsストアアプリ用のVBプロジェクトのCommonフォルダーに自動生成される「NavigationHelper.vb」ファイルには、Phoneの[戻る]ボタン(ハードウェアボタン)からの割り込みを処理するためのコードがUpdate 2ですでに追加されている。これはユニバーサルプロジェクトのために必要になるコードであり、ユニバーサルプロジェクトを提供する予定がないのなら不要なものだ。

*6 プロジェクト間のファイルリンクを使えば、VBでもユニバーサルプロジェクトに似たソリューション構成にできる。別途公開のサンプルコードでは、VBでもWindows用/Phone用/共通コードの3プロジェクトに分けてユニバーサルプロジェクトに似せて書いてみた。ただし、このような形にするにはかなりの手間が掛かった(説明するには本連載の1回分では足りないほどだ)。ユニバーサルプロジェクトテンプレートの形にこだわらず、素直に作った方がよさそうである。なお、ユニバーサルプロジェクトで作らなくてもユニバーサルWindowsアプリはリリースできるので、お間違えなきよう(「WinRT/Metro TIPS:ユニバーサルプロジェクトで開発するには?」参照)。


ライブタイルを更新するバックグラウンドタスク

 本稿で作成するバックグラウンドタスクは、ライブタイルを更新するものだ。といっても、アプリ本体では何の処理も行わず、バックグラウンドタスクが実行された時刻をスタート画面のタイルに表示するだけのシンプルなものである(次の画像)。

バックグラウンドタスクで更新されるライブタイル バックグラウンドタスクで更新されるライブタイル
左はWindowsのスタート画面(部分)、右はPhoneのスタート画面である(実際の動作を確認するには、もちろん、タイルをスタート画面にピン留め/追加しておく必要がある)。赤枠内が、本稿で作成したアプリのタイル。正方形タイルが2つずつ並んでいるが、それぞれ左がC#、右がVB(本稿では説明しない)で作成したものだ。
なお、このPhoneの画像は「Windows Phoneの画面出力アプリ」(英語名は「Project My Screen App for Windows Phone」)による実機のものだ(USB接続)。VS 2013付属のエミュレーターでは、ライブタイルがうまく更新されないようである。

 バックグラウンドタスクは、大きく次の2種類に分けられる。

  • AC電源接続時だけ動作するバックグラウンドタスク: メンテナンストリガー(システムイベントの一種*7
  • バッテリー駆動時でも動作するバックグラウンドタスク: オーディオ/コントロールチャネル(Windows専用)/チャットメッセージ通知(Phone専用)/デバイス使用トリガー/場所/プッシュ通知(Phone専用)/システムイベント(メンテナンストリガーを除く)/タイマー

 これらのうち定期的に実行できるバックグラウンドタスクは、メンテナンストリガーとタイマーだけである。タイマーは、アプリをロック画面に配置しなければならないので、少々扱いにくい。本稿では、メンテナンストリガーを使って定期的に実行されるバックグラウンドタスクを実装してみよう*8

*7 マニフェストにはシステムイベントとして宣言する(後述)。この情報はMSDNには見当たらないようであるが、次のMSDNブログに記述がある(英語)。Windows 8 app developer blog:「Being productive in the background - background tasks

*8 1日〜数日に1回程度の頻度で実行されればよいというバックグラウンドタスクなら、メンテナンストリガーで十分であろう。デスクトップPCなら、使用時にはAC電源につながっている。タブレットやPhoneでは、毎日のように充電のためにAC電源に接続することになるが、そのたびにシャットダウンするとは考えにくい。


バックグラウンドタスクのWindowsとPhoneの違い

 Windows 8.1用のWindowsストアアプリと、Windows Phone 8.1用のWindows Runtimeアプリでは、バックグラウンドタスクの実装はほとんど同じであるが、1つ大きな相違点があるので覚えておいてほしい。

 それは、Phoneではバックグラウンドタスクを登録する前にRequestAccessAsyncメソッド(Windows.ApplicationModel.Background名前空間のBackgroundExecutionManagerクラス)を呼び出す必要があるということだ。これは、Windowsではロック画面に配置する許可を求めるダイアログを表示するものだ(Windowsでメンテナンストリガーなどを登録するときに呼び出すと例外が出てしまう)。Phoneではこのメソッドは許可を求めるダイアログを表示したりはしないが、Windowsとは異なりバックグラウンドタスクを登録する前にこれを呼び出す必要があるのだ。

 従って、ユニバーサルプロジェクトで開発していて、バックグラウンドタスクの登録コードを共有プロジェクトに置く場合は、このRequestAccessAsyncメソッドを呼び出す部分を「#if」ディレクティブで切り分けることになる*9

ポータブルになったWinMD

 バックグラウンドタスクは、Windowsランタイムコンポーネント(以降、WinMD)として実装しなければならない。VS 2013 Update 2からはポータブルな(=移植可能な)WinMDを作成できるようになったので、バックグラウンドタスクの実装をWindowsとPhoneで共有できるのだ。

 その作成手順は、以降で説明する。

 ポータブルなWinMDにすることと、前述したRequestAccessAsyncメソッドを呼び出すこと。この2点が分かっていれば、後はWindows用のバックグラウンドタスクの作り方と同じである。

バックグラウンドタスクを実行するプロジェクトを作成するには?

 それでは順に実装していこう。まずWindowsとPhoneから利用できるポータブルなWinMDのプロジェクトを作らねばならない。

 VS 2013のソリューションエクスプローラーでソリューションを右クリックして[追加]−[新しいプロジェクト]を選ぶ*10。すると、[新しいプロジェクトの追加]ダイアログが表示されるので、次の画像のように[Windows ランタイム コンポーネント (ユニバーサル アプリ用ポータブル)]を選び、名前(ここでは画像と異なり「BackgroundTaskSampleCS」とする)を指定して[OK]ボタンをクリックする。WinMDプロジェクトを作成したら、WindowsとPhoneの両方のプロジェクトからWinMDプロジェクトへの参照を追加しておこう。

ポータブルなWinMDのプロジェクトをC#で作る(VS 2013) ポータブルなWinMDのプロジェクトをC#で作る(VS 2013)
C#の場合は、[新しいプロジェクトの追加]ダイアログにポータブルなWinMDを作るテンプレートがあるので、それを選ぶだけでよい。

*10 ユニバーサルプロジェクトの場合は、ソリューションではなくユニバーサルプロジェクトを右クリックしてもよい。その場合は、作成されるプロジェクトのフォルダーが、ユニバーサルプロジェクトの配下になる。上の画像ではそのようにしている。


[コラム]補足:VBの場合

 VBでは[新しいプロジェクトの追加]ダイアログで[Windows ランタイム コンポーネント (ユニバーサル アプリ用ポータブル)]プロジェクトテンプレートを選択できない。しかし、従来のWindows用(またはPhone用)WinMDのプロジェクトを作った後で、それをポータブルに変更できる(次の画像)。

VBでWinMDプロジェクトをポータブルに変更する(VS 2013) VBでWinMDプロジェクトをポータブルに変更する(VS 2013)
VBの場合は、Windows専用(またはPhone専用)のWinMDを作った後、このようにしてポータブルに変更する。
プロジェクトのプロパティ編集画面で、[ライブラリ]タブの[変更]ボタンをクリックして[ターゲットの変更]ダイアログを出し、[Windows 8.1]と[Windows Phone 8.1]の両方(赤枠内)にチェックを付けて、[OK]ボタンをクリックする。


バックグラウンドタスクを登録/実行するクラスを実装する

 上で作成したWinMDのプロジェクトに、これから2つのクラスを実装していく。1つ目は、バックグラウンドの登録と実行を受け持つ「BackgroundWorker」クラスだ(次のコード)。少々長いコードだが、紙面の都合で詳しく解説する余裕がない。MSDNの「メンテナンス トリガーの使用方法」を参考にしながら、読み取っていただきたい。

 なお、前述したように、ここにはRequestAccessAsyncメソッドを呼び出すコードは含めていない。もしもWinMD側でRequestAccessAsyncメソッドを呼び出すのならば、WindowsかPhoneかを区別するための引数をRegisterBackgroundTaskメソッドに追加することになるだろう。

using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;

namespace BackgroundTaskSampleCS
{
  public sealed class BackgroundWorker : IBackgroundTask
  {
    // バックグラウンドタスクをシステムに登録する
    public static BackgroundTaskRegistration RegisterBackgroundTask()
    {
      const uint span = 15; // 最短15分(それ未満だとバックグラウンドタスク登録時に例外)
      return RegisterBackgroundTask(
          "BackgroundTaskSampleCS.BackgroundWorker"
          "WinRT/Metro TIPS #78 (C#): Update Tile"
          new MaintenanceTrigger(span, false),
          null
          );
    }

    // バックグラウンドタスクを登録する処理の実際(汎用的なメソッド)
    // taskEntryPoint:バックグラウンドタスクのエントリポイント(完全クラス名)
    // taskName:バックグラウンドタスクに付ける名前(他と衝突しないこと!)
    // trigger:バックグラウンドタスクを起動するトリガー
    // condition:バックグラウンドタスクが起動される追加条件(オプション)
    private static BackgroundTaskRegistration RegisterBackgroundTask(
        string taskEntryPoint,
        string taskName,
        IBackgroundTrigger trigger,
        IBackgroundCondition condition)
    {
      // 既存のバックグラウンドタスクをチェックする
      foreach (var cur in BackgroundTaskRegistration.AllTasks)
      {
        if (cur.Value.Name == taskName)
        {
          // 登録済み
          return (BackgroundTaskRegistration)(cur.Value); 
        }
      }

      // バックグラウンドタスクを登録する
      var builder = new BackgroundTaskBuilder();
      builder.Name = taskName;
      builder.TaskEntryPoint = taskEntryPoint;
      builder.SetTrigger(trigger);
      if (condition != null)
        builder.AddCondition(condition);

      BackgroundTaskRegistration task = builder.Register();
      return task;
    }


    // バックグラウンドタスクとして起動されるメソッド
    // これはIBackgroundTaskインターフェースの実装である
    public async void Run(IBackgroundTaskInstance taskInstance)
    {
      taskInstance.Canceled += taskInstance_Canceled;

      // RunBackgroundTaskメソッドは非同期なので、呼び出し元に完了を通知する必要がある
      BackgroundTaskDeferral deferral = taskInstance.GetDeferral();

      await RunBackgroundTask();

      deferral.Complete();
    }

    // キャンセルイベントのハンドラー
    private void taskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
      // (今回は何もしない)
    }

    // バックグラウンドタスクで実行される処理
    // アプリ側から直接呼び出すことも可能なようにpublicにしてある。
    // ただし、publicにするには、メソッドの返す型はTaskではなくIAsyncActionでなければならない。
    public static Windows.Foundation.IAsyncAction RunBackgroundTask()
    {
      // 今回は同期で動作するUpdateLiveTileメソッドを実行しているが、
      // 一般的には非同期メソッドを実行することが多いので、非同期として書いてある。
      return Task.Run(() =>
      {
        LiveTile.UpdateLiveTile(); // 後述
      })
      .AsAsyncAction();
    }
  }
}

バックグラウンドタスクを登録/実行するクラス(C#)
このクラスはWinMDのプロジェクトに置く(BackgroundWorker.csファイル)。バックグラウンドタスクにはそういう制約があるのだ。
アプリの起動時にRegisterBackgroundTaskメソッドを呼び出せば、バックグラウンドタスクが登録される。登録したときの条件が満たされると、システムからRunメソッドが呼び出される。

ライブタイルを更新するクラスを実装する

 ライブタイルを更新するクラスは、次のコードのようになる。これもWinMDのプロジェクトに置く(LiveTile.csファイル)。

 ライブタイルを登録する方法については、「連載:Windowsストア・アプリ開発入門:第9回 効果的に情報を提示する (3/7)」を参考にしてほしい。ここでは簡単にするために、1種類のサイズだけとし、呼び出された時刻をタイルに表示するだけとした。

using System;

namespace BackgroundTaskSampleCS
{
  internal class LiveTile
  {
    // ライブタイルを更新する
    public static void UpdateLiveTile()
    {
      // TileUpdateManagerインスタンスを取得する
      var tileUpdater = Windows.UI.Notifications.TileUpdateManager.CreateTileUpdaterForApplication();

      // 150×150サイズのライブタイルを有効にし、その内容はいったんクリアする
      tileUpdater.EnableNotificationQueueForSquare150x150(true);
      tileUpdater.Clear();

      // ライブタイル通知のデータを作成する
      Windows.Data.Xml.Dom.XmlDocument tileData = CreateTileXml();

      // ライブタイル通知を登録
      var tileNotification = new Windows.UI.Notifications.TileNotification(tileData);
      tileUpdater.Update(tileNotification);
    }

    // ライブタイル通知のデータを作成する
    private static Windows.Data.Xml.Dom.XmlDocument CreateTileXml()
    {
      // タイルのテンプレートを取得する
      Windows.Data.Xml.Dom.XmlDocument tileXml
        = Windows.UI.Notifications.TileUpdateManager.GetTemplateContent(
                  Windows.UI.Notifications.TileTemplateType.TileSquare150x150Text02);

      // タイルの左下にアプリ名を出す(デフォルトはアプリのアイコン)
      var brandingAttr = tileXml.CreateAttribute("branding");
      brandingAttr.NodeValue = "name";
      tileXml.GetElementsByTagName("binding")[0].Attributes.SetNamedItem(brandingAttr);

      // 2つのtextタグを順に埋める
      // 1つ目には現在時刻(HH:mm)
      // 2つ目には説明文
      var textNodes = tileXml.GetElementsByTagName("text");
      textNodes[0].InnerText = DateTimeOffset.Now.ToString("HH:mm");
      textNodes[1].InnerText = "この時刻にバックグラウンドタスクが実行されました";

      return tileXml;
    }
  }
}

ライブタイルを更新するクラス(C#)
このコードで指定しているタイルのテンプレートは、WindowsとPhoneに共通のものである。
もしも共通でないテンプレートを使う場合には、引数としてWindowsとPhoneの区別を与える必要があるだろう(「WinRT/Metro TIPS:WindowsとPhoneでロジックを切り分けるには?[ユニバーサルWindowsアプリ開発]」に記述したように、特定の条件を仮定することで判別することも不可能ではない)。

アプリの起動時にバックグラウンドタスクを登録する

 最後に、アプリの起動時にバックグラウンドタスクを登録するコードを書けば、コーディングは完了だ(次のコード)。

  protected override
#if WINDOWS_PHONE_APP
    async
#endif
    void OnLaunched(LaunchActivatedEventArgs e)
  {
    ……省略……

    if (rootFrame == null)
    {
      // バックグラウンドタスクを登録する
#if WINDOWS_PHONE_APP
      var backgroundAccessStatus
        = await Windows.ApplicationModel.Background.BackgroundExecutionManager
                  .RequestAccessAsync();
      if (backgroundAccessStatus
          != Windows.ApplicationModel.Background.BackgroundAccessStatus.Denied)
      {
        // Phoneでは、RequestAccessAsyncに成功したときだけバックグラウンドタスクを登録する
#endif
        try
        {
          var registration
            = BackgroundTaskSampleCS.BackgroundWorker.RegisterBackgroundTask();
        }
        catch (Exception ex) 
        {
          // バックグラウンドタスクの登録に失敗した
          // 必要ならば対処する
          throw;
        }
#if WINDOWS_PHONE_APP
      }
#endif
      ……省略……

アプリの起動時にバックグラウンドタスクを登録するコード(C#)
WindowsとPhoneのアプリのプロジェクトに、前述したWinMDのプロジェクトへの参照をそれぞれ追加する。それからこのコードを記述する。
「App.xaml.cs」ファイルを開き、そのOnLaunchedメソッドに太字の部分を追加する。前述したように、PhoneのコードにはRequestAccessAsyncメソッドの呼び出しが必要である。ユニバーサルプロジェクトを想定して、「#if」ディレクティブでWindowsとPhoneのコードを切り分けている。

マニフェストにバックグラウンドタスクを宣言する

 コードは完成したが、このまま実行するとバックグラウンドタスクを登録するところで例外が出てしまう。プロジェクトのマニフェストに宣言を追加しないと、バックグラウンドタスクは利用できないのだ。

 マニフェストの[宣言]タブを開き、[使用可能な宣言]ドロップダウンで[バックグラウンド タスク]を選んで右の[追加]ボタンをクリックし、[プロパティ]の下にあるチェックボックスの中から[システム イベント]にチェックを付け、[エントリ ポイント]に「BackgroundTaskSampleCS.BackgroundWorker」と入力する(次の画像)。

マニフェストにバックグラウンドタスクの宣言を追加しているところ(Visual Studio 2013) マニフェストにバックグラウンドタスクの宣言を追加しているところ(Visual Studio 2013)
この画像はPhoneのプロジェクトのものだが、Windowsでもほぼ同じだ(設定する内容は全く同じ)。
ユニバーサルプロジェクトでも、WindowsとPhoneのそれぞれのプロジェクトで設定する。

バックグラウンドタスクをテストする

 以上で実装は全て完了だ。このアプリを起動すると、バックグラウンドタスクが登録され、だいたい15分ごとにタイルの表示が更新される(冒頭の画像。なお、後述する[ライフサイクル イベント]ドロップダウンを使えば、15分待たずとも好きなタイミングで動作を確認できる)。思い出してほしいのだが、メンテナンストリガーを使っているので、AC電源につながっているときだけ動作する。

 Windowsのシミュレーターではライブタイルが動作しないので、タイルの表示は実際のスタート画面で確認する。Phoneは、VS 2013のエミュレーターでもライブタイルが動作するはずなのだが、筆者の環境では(バックグラウンドタスクで正常に更新されているにも関わらず)タイル表示が変わらない現象が頻繁に起きている。できればPhoneでの表示テストも実機で行った方がよいだろう。

 また、デバッグするには、デバッグ実行中にバックグラウンドタスクの呼び出しをVS 2013から行う(次の画像)。

 最初にデバッグ実行を開始してちょっと経つと、バックグラウンドタスクがシステムに登録される。すると、VS 2013の[ライフサイクル イベント]ドロップダウンの最下段に、登録されたバックグラウンドタスクの名前が表示されるようになる。デバッグ実行中にそれを選択するとバックグラウンドタスクのRunメソッドが呼び出される。

VS 2013からデバッグ実行中にバックグラウンドタスクを呼び出すところ(Visual Studio 2013) VS 2013からデバッグ実行中にバックグラウンドタスクを呼び出すところ(Visual Studio 2013)

まとめ

 Windows Phone 8.1のWindows Runtimeアプリ実行環境の(Windows 8.1に対する)互換性の高さと、ポータブルなWinMDがサポートされたことによって、WindowsとPhoneのバックグラウンドタスクの実装はほぼ1つのコードで書けるようになった。PhoneではRequestAccessAsyncメソッドの呼び出しが必須であることさえ忘れなければ、従来のWindowsストアアプリと同じようにして実装できる。

 バックグラウンドタスクについては、次のドキュメントも参照してほしい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。