방법: Windows Phone의 백그라운드 에이전트 구현

2012-02-09

이 항목에서는 예약된 작업을 사용하여 백그라운드 에이전트를 등록하는 응용프로그램 구현에 대해 설명합니다.

다음 단계에서 예약된 작업을 사용하여 백그라운드 에이전트를 등록하는 간단한 응용프로그램을 만드는 과정을 안내합니다.

예약된 작업을 사용하는 응용프로그램을 만들려면

  1. Visual Studio 에서 새로운 Windows Phone 응용프로그램 프로젝트를 만듭니다. 이 템플릿은 Windows Phone용 Silverlight 카테고리에 있습니다.

  2. 다음으로 예약된 작업 프로젝트를 솔루션에 추가합니다. 파일 메뉴에서 추가->새 프로젝트…를 선택합니다. 새 프로젝트 추가 대화 상자에서 Windows Phone 예약된 작업 에이전트를 선택합니다. 기본 이름 ScheduledTaskAgent1을 그대로 두고 확인을 클릭합니다.

  3. 그런 다음 포그라운드 응용프로그램 프로젝트에서 에이전트 프로젝트에 대한 참조를 추가해야 합니다. 솔루션 탐색기에서 포그라운드 응용프로그램 프로젝트를 클릭하여 선택합니다. 다음으로 프로젝트 메뉴에서 참조 추가…를 선택합니다. 참조 추가 대화 상자에서 프로젝트 탭을 선택합니다. 에이전트 프로젝트 ScheduledTaskAgent1을 선택하고 확인을 클릭합니다.

  4. 솔루션 탐색기에서 ScheduledTaskAgent1 프로젝트 아래에 있는 ScheduledAgent.cs를 두 번 클릭하여 파일을 엽니다. 이 파일에는 기본 클래스 ScheduledTaskAgent에서 상속하는 단일 클래스인 ScheduledAgent에 대한 정의가 포함되어 있습니다. 이 예제의 경우 Shell 네임스페이스 및 System 네임스페이스에 대한 using 지시문을 파일의 맨 위에 추가합니다.

    using Microsoft.Phone.Scheduler;
    using Microsoft.Phone.Shell;
    using System;
    
    
  5. OnInvoke(ScheduledTask) 클래스에서 구현되는 메서드가 하나 있습니다. 이 메서드는 예약된 작업이 실행될 때 운영 체제에 의해 호출됩니다. 여기에 백그라운드 에이전트가 실행될 때 실행하려는 코드를 삽입합니다. 각 응용프로그램은 한 번에 하나의 ScheduledTaskAgent만 등록할 수 있지만 사용자가 이 에이전트를 리소스를 많이 사용하는 에이전트 및 정기 에이전트 둘 다로 예약할 수 있습니다. 응용프로그램에서 ResourceIntensiveTaskPeriodicTask를 모두 사용하는 경우 OnInvoke 메서드로 전달되는 ScheduledTask 개체의 유형을 확인하여 에이전트를 호출하는 작업을 파악하고 코드 실행을 필요에 따라 분기할 수 있습니다. 한 가지 유형의 에이전트만 사용하는 경우에는 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 파일을 열고 "ContentPanel"이라는 Grid 요소 내에 다음 XAML 코드를 붙여 넣습니다.

    <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>
    
    

    이 코드는 각 에이전트 유형에 대한 두 개의 컨트롤 집합을 추가합니다. 컨트롤 대부분은 백그라운드 에이전트를 나타내는 ScheduledTask 개체에 바인딩될 텍스트 블록이며, 사용자가 이들 개체의 속성을 볼 수 있게 합니다. 또한 사용하도록 설정된 각 에이전트의 상태를 표시하고 토글할 수 있는 확인란이 각 에이전트 유형에 대해 추가됩니다. 처리기는 CheckedUnchecked 이벤트에 대해 추가되며, 이 이벤트는 에이전트 켜기와 끄기를 전환하는 데 사용됩니다.

  7. MainPage.xaml.cs에서 Microsoft.Phone.Scheduler 네임스페이스에 대한 using 지시문을 페이지의 맨 위에 추가합니다.

    using Microsoft.Phone.Scheduler;
    
    
  8. 각 에이전트 유형을 나타낼 두 개의 클래스 변수를 만듭니다. 이 클래스 변수는 UI에 바인딩될 개체입니다. 예약된 작업 서비스는 Name 속성으로 예약된 작업을 고유하게 식별합니다. 에이전트에 사용될 이름을 포함하는 두 개의 변수를 만듭니다. 최종 사용자가 백그라운드 에이전트를 사용하지 않도록 설정했는지 여부를 추적할 부울 변수를 하나 만듭니다. 클래스 정의 내에 이 코드 줄을 추가합니다.

    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 오류: 작업을 사용할 수 없습니다."인 경우 사용자에게 해당 백그라운드 에이전트가 사용되지 않도록 설정되어 있음을 알려야 합니다.

    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 컨트롤에 대해 CheckedUnchecked 이벤트의 이벤트 처리기를 추가합니다. 이 처리기는 이전 단계에서 만든 시작 및 중지 도우미 메서드를 호출합니다. 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;
    
    }
    
    

표시: