方法: Windows Phone のバックグラウンド エージェントを実装する

2012/02/09

このトピックでは、スケジュールされたタスクを使用してバックグラウンド エージェントを登録するアプリケーションの実装について説明します。

以下の手順では、スケジュールされたタスクを使用してバックグラウンド エージェントを登録する単純なアプリケーションの作成について説明します。

スケジュールされたタスクを使用するアプリケーションを作成するには

  1. Visual Studio で、新しい Windows Phone アプリケーション プロジェクトを作成します。このテンプレートは、Silverlight for Windows Phone カテゴリにあります。

  2. 次に、スケジュールされたタスク プロジェクトをソリューションに追加します。[ファイル] メニューから、[追加] > [新しいプロジェクト] を選択します。[新しいプロジェクトの追加] ダイアログで、[Windows Phone のスケジュールされたタスク エージェント] を選択します。既定の名前である ScheduledTaskAgent1 をそのままにして、[OK] をクリックします。

  3. 次に、フォアグラウンド アプリケーション プロジェクトで、エージェント プロジェクトへの参照を追加する必要があります。ソリューション エクスプローラーで、フォアグラウンド アプリケーション プロジェクトをクリックして選択します。次に、[プロジェクト] メニューの [参照の追加] をクリックします。[参照の追加] ダイアログで、[プロジェクト] タブを選択します。エージェント プロジェクトである ScheduledTaskAgent1 を選択し、[OK] をクリックします。

  4. ソリューション エクスプローラーで、ScheduledTaskAgent1 プロジェクトの下にある ScheduledAgent.cs をダブルクリックして、ファイルを開きます。このファイルには、ScheduledAgent という 1 つのクラスの定義が含まれています。このクラスは基本クラス ScheduledTaskAgent を継承しています。この例では、Shell 名前空間および System 名前空間の using ディレクティブをファイルの先頭に追加します。

    using Microsoft.Phone.Scheduler;
    using Microsoft.Phone.Shell;
    using System;
    
    
  5. クラスには、OnInvoke(ScheduledTask) という 1 つのメソッドが実装されています。このメソッドは、スケジュールされたタスクが起動されたときにオペレーティング システムによって呼び出されます。これは、バックグラウンド エージェントが実行されたとき実行するにコードを配置する場所です。各アプリケーションは一度に 1 つの ScheduledTaskAgent のみを登録できますが、このエージェントをリソースを大量に消費するエージェントおよび定期的なエージェントの両方としてスケジュールできます。アプリケーションで ResourceIntensiveTask および PeriodicTask の両方を使用する場合は、OnInvoke メソッドに渡される ScheduledTask オブジェクトの種類を確認して、エージェントの呼び出し対象のタスクを判断し、必要に応じてコードの実行を分岐できます。1 種類のエージェントのみを使用している場合は、ScheduledTask オブジェクト型を確認する必要はありません。この例では、エージェントは OnInvoke から ShellToast オブジェクトを起動し、エージェントの呼び出し対象となったスケジュールされているタスクの種類を示します。このトーストにより、エージェントが実行中の場合はそのことが表示されます。ただし、フォアグラウンド アプリケーションの実行中は表示されません。

    スケジュールされたタスク コードが完了したときは、NotifyComplete()()()() を呼び出して、実行する必要がなくなったことをオペレーティング システムに伝える必要があります。これにより、オペレーティング システムはその他のエージェントのスケジュールを試みることができます。

    protected override void OnInvoke(ScheduledTask task)
    {
      //TODO: Add code to perform your task in background
      string toastMessage = "";
    
      // If your application uses both PeriodicTask and ResourceIntensiveTask
      // you can branch your application code here. Otherwise, you don't need to.
      if (task is PeriodicTask)
      {
        // Execute periodic task actions here.
        toastMessage = "Periodic task running.";
      }
      else
      {
        // Execute resource-intensive task actions here.
        toastMessage = "Resource-intensive task running.";
      }
    
      // Launch a toast to show that the agent is running.
      // The toast will not be shown if the foreground application is running.
      ShellToast toast = new ShellToast();
      toast.Title = "Background Agent Sample";
      toast.Content = toastMessage;
      toast.Show();
    
      // If debugging is enabled, launch the agent again in one minute.
    #if DEBUG_AGENT
      ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60));
    #endif
    
      // Call NotifyComplete to let the system know the agent is done working.
      NotifyComplete();
    }
    
    

    実際のデバイスで実行されるスケジュールよりも頻繁にエージェントを実行できるようにするため、LaunchForTest(String, TimeSpan) メソッドが用意されています。このメソッドはアプリケーション開発のみを対象としています。このメソッドは、開発ツールを使用して配置されるアプリケーション以外に対しては機能しません。運用環境のアプリケーションではこのメソッドの呼び出しを削除する必要があります。この例では、この呼び出しは、デバッグ機能と実行機能を簡単に切り替えられるようにするため、#if ブロックに配置されます。この呼び出しを有効にするには、ScheduledAgent.cs ファイルの先頭に次の行を配置します。

    #define DEBUG_AGENT
    
    
  6. 次のいくつかの手順では、フォアグラウンド アプリケーションを変更し、ユーザーが定期的なエージェントとリソースを大量に消費するエージェントを有効または無効にできるようにします。最初に、MainPage.xaml ファイルを開き、次の XAML コードを "ContentPanel" という名前の Grid 要素内に貼り付けます。

    <StackPanel>
      <StackPanel  Orientation="Vertical" Name="PeriodicStackPanel" Margin="0,0,0,40">
        <TextBlock Text="Periodic Agent" Style="{StaticResource PhoneTextTitle2Style}"/>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="name: " Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding Name}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="is enabled" VerticalAlignment="Center"  Style="{StaticResource PhoneTextAccentStyle}"/>
          <CheckBox Name="PeriodicCheckBox" IsChecked="{Binding IsEnabled}" Checked="PeriodicCheckBox_Checked" Unchecked="PeriodicCheckBox_Unchecked"/>                       
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="is scheduled: "  Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding IsScheduled}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="last scheduled time: "  Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding LastScheduledTime}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="expiration time: " Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding ExpirationTime}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="last exit reason: "  Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding LastExitReason}" />
        </StackPanel>
      </StackPanel>
      <StackPanel  Orientation="Vertical" Name="ResourceIntensiveStackPanel" Margin="0,0,0,40">
        <TextBlock Text="Resource-intensive Agent" Style="{StaticResource PhoneTextTitle2Style}"/>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="name: " Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding Name}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="is enabled" VerticalAlignment="Center"  Style="{StaticResource PhoneTextAccentStyle}"/>
          <CheckBox Name="ResourceIntensiveCheckBox" IsChecked="{Binding IsEnabled}" Checked="ResourceIntensiveCheckBox_Checked" Unchecked="ResourceIntensiveCheckBox_Unchecked"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="is scheduled: "  Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding IsScheduled}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="last scheduled time: "  Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding LastScheduledTime}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="expiration time: " Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding ExpirationTime}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="last exit reason: "  Style="{StaticResource PhoneTextAccentStyle}"/>
          <TextBlock Text="{Binding LastExitReason}" />
        </StackPanel>
      </StackPanel>
    </StackPanel>
    
    

    このコードは、エージェントの種類ごとに 1 つずつ、2 つのセットのコントロールを追加します。ほとんどのコントロールは、バックグラウンド エージェントを表す ScheduledTask オブジェクトにバインドされるテキスト ブロックであり、これらのオブジェクトのプロパティを表示できるようにします。また、各エージェントの有効の状態を表示し、ユーザーが切り替えることができるようにするチェック ボックスが、エージェントの種類ごとに追加されます。Checked イベントおよび Unchecked イベントにハンドラーが追加され、これを使用してエージェントのオンとオフを切り替えることができます。

  7. MainPage.xaml.cs で、Microsoft.Phone.Scheduler 名前空間の using ディレクティブをページの先頭に追加します。

    using Microsoft.Phone.Scheduler;
    
    
  8. 各エージェントの種類を表す 2 つのクラス変数を作成します。これらは、UI にバインドされるオブジェクトです。スケジュールされたアクション サービスは、スケジュールされたタスクを Name プロパティによって一意に識別します。エージェントに使用される名前を含む 2 つの変数を作成します。バックグラウンド エージェントがエンド ユーザーによって無効になったかどうかを追跡する 1 つのブール変数を作成します。クラス定義の内部に次のコードを追加します。

    public partial class MainPage : PhoneApplicationPage
    {
      PeriodicTask periodicTask;
      ResourceIntensiveTask resourceIntensiveTask;
    
      string periodicTaskName = "PeriodicAgent";
      string resourceIntensiveTaskName = "ResourceIntensiveAgent";
      public bool agentsAreEnabled = true;
    
    
    
  9. 次に、StartPeriodicAgent というヘルパー メソッドを実装します。このメソッドは最初に Find(String) メソッドを使用して、指定された名前の PeriodicTask への参照を取得します。スケジュールされたタスク オブジェクトが Null でない場合は、Remove(String) を呼び出してシステムへのエージェントの登録を解除します。エージェントを直接更新することはできません。削除してから追加する必要があります。次に、新しい PeriodicTask オブジェクトを作成し、コンストラクターでその名前を割り当てます。次に、Description プロパティを設定します。このプロパティは定期的なエージェントに必要であり、デバイスのバックグラウンド タスクの設定ページでユーザーにエージェントを説明するために使用されます。次に、Add(ScheduledAction) を呼び出してシステムに定期的なエージェントを登録します。ユーザーがアプリケーションに対してバックグラウンド エージェントを無効にした場合、Add を呼び出すと InvalidOperationException がスローされます。このため、呼び出しは try ブロックに配置する必要があります。呼び出しに成功した場合は、関連する UI 要素のデータ コンテキストを設定し、データ バインドを更新してオブジェクトのプロパティをユーザーに表示します。呼び出しで例外がスローされる場合は、例外のメッセージ文字列を確認します。"BNS Error: The action is disabled" という文字列の場合は、バックグラウンド エージェントが無効になったことをユーザーに警告する必要があります。

    LaunchForTest(String, TimeSpan) メソッドを含めることで、このメソッドを呼び出した 1 分後にエージェントが起動されます。このメソッドの前の呼び出しと同じように、#if ブロックに配置され、アプリケーションがデバッグ モードと実行モードを簡単に切り替えられるようにします。このメソッドを有効にするには、次の行を MainPage.xaml.cs の先頭に含めます。

    #define DEBUG_AGENT
    
    

    次のメソッドを MainPage クラスの定義に貼り付けます。

    private void StartPeriodicAgent()
    {
      // Variable for tracking enabled status of background agents for this app.
      agentsAreEnabled = true;
    
      // Obtain a reference to the period task, if one exists
      periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
    
      // If the task already exists and background agents are enabled for the
      // application, you must remove the task and then add it again to update 
      // the schedule
      if (periodicTask != null)
      {
        RemoveAgent(periodicTaskName);
      }
    
      periodicTask = new PeriodicTask(periodicTaskName);
    
      // The description is required for periodic agents. This is the string that the user
      // will see in the background services Settings page on the device.
      periodicTask.Description = "This demonstrates a periodic task.";
    
      // Place the call to Add in a try block in case the user has disabled agents.
      try
      {
        ScheduledActionService.Add(periodicTask);
        PeriodicStackPanel.DataContext = periodicTask;
    
        // If debugging is enabled, use LaunchForTest to launch the agent in one minute.
    #if(DEBUG_AGENT)
        ScheduledActionService.LaunchForTest(periodicTaskName, TimeSpan.FromSeconds(60));
    #endif
      }
      catch (InvalidOperationException exception)
      {
        if (exception.Message.Contains("BNS Error: The action is disabled"))
        {
          MessageBox.Show("Background agents for this application have been disabled by the user.");
          agentsAreEnabled = false;
          PeriodicCheckBox.IsChecked = false;
        }
        
        if (exception.Message.Contains("BNS Error: The maximum number of ScheduledActions of this type have already been added."))
        {
          // No user action required. The system prompts the user when the hard limit of periodic tasks has been reached.
          
        }
        PeriodicCheckBox.IsChecked = false;
      }
      catch (SchedulerServiceException)
      {
        // No user action required.
        PeriodicCheckBox.IsChecked = false;
      }
    }
    
    
  10. 次に、リソースを大量に消費するタスクの起動ヘルパー メソッドを実装します。このメソッドは、ResourceIntensiveTask クラスを使用してエージェントをスケジュールし、別の名前が使用されることを除いて、定期的なエージェントのメソッドと同じです。

    private void StartResourceIntensiveAgent()
    {
      // Variable for tracking enabled status of background agents for this app.
      agentsAreEnabled = true;
    
      resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask;
    
      // If the task already exists and background agents are enabled for the
      // application, you must remove the task and then add it again to update 
      // the schedule.
      if (resourceIntensiveTask != null)
      {
        RemoveAgent(resourceIntensiveTaskName);
      }
    
      resourceIntensiveTask = new ResourceIntensiveTask(resourceIntensiveTaskName);
    
      // The description is required for periodic agents. This is the string that the user
      // will see in the background services Settings page on the device.
      resourceIntensiveTask.Description = "This demonstrates a resource-intensive task.";
    
      // Place the call to Add in a try block in case the user has disabled agents.
      try
      {
        ScheduledActionService.Add(resourceIntensiveTask);
        ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask;
    
        // If debugging is enabled, use LaunchForTest to launch the agent in one minute.
    #if(DEBUG_AGENT)
        ScheduledActionService.LaunchForTest(resourceIntensiveTaskName, TimeSpan.FromSeconds(60));
    #endif
      }
      catch (InvalidOperationException exception)
      {
        if (exception.Message.Contains("BNS Error: The action is disabled"))
        {
          MessageBox.Show("Background agents for this application have been disabled by the user.");
          agentsAreEnabled = false;
          
        }
        ResourceIntensiveCheckBox.IsChecked = false;
      }
      catch (SchedulerServiceException)
      {
        // No user action required.
        ResourceIntensiveCheckBox.IsChecked = false;
      }
    
    
    }
    
    
  11. ブール クラス変数 ignoreCheckBoxEvents を追加します。この変数は、ページの初期化時に CheckBox イベントをオフにするために使用されます。

    bool ignoreCheckBoxEvents = false;
    
    
  12. 次に、CheckBox コントロールの Checked イベントおよび Unchecked イベントのイベント ハンドラーを追加します。これらのハンドラーは、前の手順で作成した起動および停止ヘルパー メソッドを呼び出します。ignoreCheckBoxEvents が true の場合、ハンドラーは何も行わずに戻ります。

    private void PeriodicCheckBox_Checked(object sender, RoutedEventArgs e)
    {
      if (ignoreCheckBoxEvents) 
        return;
      StartPeriodicAgent();
    }
    private void PeriodicCheckBox_Unchecked(object sender, RoutedEventArgs e)
    {
      if (ignoreCheckBoxEvents)
       return;
      RemoveAgent(periodicTaskName);
    }
    private void ResourceIntensiveCheckBox_Checked(object sender, RoutedEventArgs e)
    {
      if (ignoreCheckBoxEvents)
        return;
      StartResourceIntensiveAgent();
    }
    private void ResourceIntensiveCheckBox_Unchecked(object sender, RoutedEventArgs e)
    {
      if (ignoreCheckBoxEvents)
        return;
      RemoveAgent(resourceIntensiveTaskName);
    }
    
    
  13. RemoveAgent ヘルパー メソッドは、try ブロックで単純に Remove(String) を呼び出し、例外によってアプリケーションが終了しないようにします。

    private void RemoveAgent(string name)
    {
      try
      {
        ScheduledActionService.Remove(name);
      }
      catch (Exception)
      {
      }
    }
    
    
  14. 最後の手順は PhoneApplicationPage クラスの OnNavigatedTo(NavigationEventArgs) メソッドをオーバーライドすることです。このメソッドは、ユーザーがこのページに移動したときに常に呼び出されます。ignoreCheckBoxEvents を true に設定し、CheckBox イベント ハンドラーが実質的に無効になるようにします。次に、Find(String) メソッドを使用して、アプリケーションの定期的なエージェントおよびリソースを大量に消費するエージェントがシステムに登録されているかどうかを確認し、CheckBox コントロールを更新してアプリケーションの現在の状態を反映します。最後に、ignoreCheckBoxEvents を false に戻します。

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
      ignoreCheckBoxEvents = true;
    
      periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
    
      if (periodicTask != null)
      {
        PeriodicStackPanel.DataContext = periodicTask;
      }
    
      resourceIntensiveTask = ScheduledActionService.Find(resourceIntensiveTaskName) as ResourceIntensiveTask;
      if (resourceIntensiveTask != null)
      {
        ResourceIntensiveStackPanel.DataContext = resourceIntensiveTask;
      }
    
      ignoreCheckBoxEvents = false;
    
    }
    
    

表示: