ストアアプリとPhoneアプリで、共有コードを使ってバックグラウンドタスクを実装する方法を解説する。
powered by Insider.NET
Windows Phone 8.1の新しいアプリ実行環境では、バックグラウンドタスクの実装にWindowsストアアプリと同じAPIが使えるようになった。そうなると、ユニバーサルWindowsアプリを開発するときのバックグラウンドタスクは、1つの実装にして共有したくなるだろう。本稿では、例としてライブタイルを更新するバックグラウンドタスクの作り方を解説する。なお、本稿のサンプルは「Windows Store app samples:MetroTips #78」からダウンロードできる。
ユニバーサルプロジェクトを使ってユニバーサルWindowsアプリを開発するには、以下の開発環境が必要である。本稿では、無償のVisual Studio Express 2013 for Windowsを使っている。
*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」はデスクトップで動作するアプリ用)。
本稿では、紛らわしくない限り次の略称を用いる。
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:ユニバーサルプロジェクトで開発するには?」参照)。
本稿で作成するバックグラウンドタスクは、ライブタイルを更新するものだ。といっても、アプリ本体では何の処理も行わず、バックグラウンドタスクが実行された時刻をスタート画面のタイルに表示するだけのシンプルなものである(次の画像)。
バックグラウンドタスクは、大きく次の2種類に分けられる。
これらのうち定期的に実行できるバックグラウンドタスクは、メンテナンストリガーとタイマーだけである。タイマーは、アプリをロック画面に配置しなければならないので、少々扱いにくい。本稿では、メンテナンストリガーを使って定期的に実行されるバックグラウンドタスクを実装してみよう*8。
*7 マニフェストにはシステムイベントとして宣言する(後述)。この情報はMSDNには見当たらないようであるが、次のMSDNブログに記述がある(英語)。Windows 8 app developer blog:「Being productive in the background - background tasks」
*8 1日〜数日に1回程度の頻度で実行されればよいというバックグラウンドタスクなら、メンテナンストリガーで十分であろう。デスクトップPCなら、使用時にはAC電源につながっている。タブレットやPhoneでは、毎日のように充電のためにAC電源に接続することになるが、そのたびにシャットダウンするとは考えにくい。
Windows 8.1用のWindowsストアアプリと、Windows Phone 8.1用のWindows Runtimeアプリでは、バックグラウンドタスクの実装はほとんど同じであるが、1つ大きな相違点があるので覚えておいてほしい。
それは、Phoneではバックグラウンドタスクを登録する前にRequestAccessAsyncメソッド(Windows.ApplicationModel.Background名前空間のBackgroundExecutionManagerクラス)を呼び出す必要があるということだ。これは、Windowsではロック画面に配置する許可を求めるダイアログを表示するものだ(Windowsでメンテナンストリガーなどを登録するときに呼び出すと例外が出てしまう)。Phoneではこのメソッドは許可を求めるダイアログを表示したりはしないが、Windowsとは異なりバックグラウンドタスクを登録する前にこれを呼び出す必要があるのだ。
従って、ユニバーサルプロジェクトで開発していて、バックグラウンドタスクの登録コードを共有プロジェクトに置く場合は、このRequestAccessAsyncメソッドを呼び出す部分を「#if」ディレクティブで切り分けることになる*9。
バックグラウンドタスクは、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プロジェクトへの参照を追加しておこう。
*10 ユニバーサルプロジェクトの場合は、ソリューションではなくユニバーサルプロジェクトを右クリックしてもよい。その場合は、作成されるプロジェクトのフォルダーが、ユニバーサルプロジェクトの配下になる。上の画像ではそのようにしている。
VBでは[新しいプロジェクトの追加]ダイアログで[Windows ランタイム コンポーネント (ユニバーサル アプリ用ポータブル)]プロジェクトテンプレートを選択できない。しかし、従来のWindows用(またはPhone用)WinMDのプロジェクトを作った後で、それをポータブルに変更できる(次の画像)。
上で作成した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();
}
}
}
ライブタイルを更新するクラスは、次のコードのようになる。これも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;
}
}
}
最後に、アプリの起動時にバックグラウンドタスクを登録するコードを書けば、コーディングは完了だ(次のコード)。
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
……省略……
コードは完成したが、このまま実行するとバックグラウンドタスクを登録するところで例外が出てしまう。プロジェクトのマニフェストに宣言を追加しないと、バックグラウンドタスクは利用できないのだ。
マニフェストの[宣言]タブを開き、[使用可能な宣言]ドロップダウンで[バックグラウンド タスク]を選んで右の[追加]ボタンをクリックし、[プロパティ]の下にあるチェックボックスの中から[システム イベント]にチェックを付け、[エントリ ポイント]に「BackgroundTaskSampleCS.BackgroundWorker」と入力する(次の画像)。
以上で実装は全て完了だ。このアプリを起動すると、バックグラウンドタスクが登録され、だいたい15分ごとにタイルの表示が更新される(冒頭の画像。なお、後述する[ライフサイクル イベント]ドロップダウンを使えば、15分待たずとも好きなタイミングで動作を確認できる)。思い出してほしいのだが、メンテナンストリガーを使っているので、AC電源につながっているときだけ動作する。
Windowsのシミュレーターではライブタイルが動作しないので、タイルの表示は実際のスタート画面で確認する。Phoneは、VS 2013のエミュレーターでもライブタイルが動作するはずなのだが、筆者の環境では(バックグラウンドタスクで正常に更新されているにも関わらず)タイル表示が変わらない現象が頻繁に起きている。できればPhoneでの表示テストも実機で行った方がよいだろう。
また、デバッグするには、デバッグ実行中にバックグラウンドタスクの呼び出しをVS 2013から行う(次の画像)。
最初にデバッグ実行を開始してちょっと経つと、バックグラウンドタスクがシステムに登録される。すると、VS 2013の[ライフサイクル イベント]ドロップダウンの最下段に、登録されたバックグラウンドタスクの名前が表示されるようになる。デバッグ実行中にそれを選択するとバックグラウンドタスクのRunメソッドが呼び出される。
Windows Phone 8.1のWindows Runtimeアプリ実行環境の(Windows 8.1に対する)互換性の高さと、ポータブルなWinMDがサポートされたことによって、WindowsとPhoneのバックグラウンドタスクの実装はほぼ1つのコードで書けるようになった。PhoneではRequestAccessAsyncメソッドの呼び出しが必須であることさえ忘れなければ、従来のWindowsストアアプリと同じようにして実装できる。
バックグラウンドタスクについては、次のドキュメントも参照してほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.